Spring框架(三)

news2025/7/19 3:24:49

1、代理模式:

        二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

        为什么使用代理模式:可以增强功能,可以保护代理目标,可以让两个不能直接交互的目标进行交互。

1、静态代理:

初始:

package com.songzhishu.proxy.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  11:49
 * @Description: TODO
 * @Version: 1.0
 */
public class OrderServiceImpl  implements  OrderService{
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

现在想统计每一个模块的耗时,怎么办,直接在原来的代码上修改!

方法一:硬编码

这样可以是可以,但是每一个模块都要写重复的代码,而且违背OCP的开闭原则!

方法二:继承重写方法

创建一个子类,然后继承实现类后重写方法也可以实现功能的拓展。

这种解决了问题,没有违背OCP原则,但是使用继承增强了耦合度 

方法三:静态代

package com.songzhishu.proxy.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  12:50
 * @Description: 代理对象
 * @Version: 1.0
 */
public class OrderServiceProxy implements OrderService {
    //要包含公共的功能 达到和目标对象一样的功能 要执行目标对象中目标方法

    //将目标对象作为代理对象的一个属性
    private  OrderService target;  //使用这种方式要比继承的耦合度低  注入公共接口要比实现类好

    public OrderServiceProxy(OrderService target) {
        //通过构造方法赋值
        this.target = target;
    }

    @Override
    public void generate() {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        target.generate();
        //调用目标对象目标功能
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        //调用目标对象目标功能
        target.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        //调用目标对象目标功能
        target.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }
}

        静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

2、动态代理:

        程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

在内存当中动态生成类的技术常见的包括:

  • JDK动态代理技术:只能代理接口。
  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

公共接口:

package com.songzhishu.spring6.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  11:45
 * @Description: 公共接口
 * @Version: 1.0
 */
public interface OrderService {
    //生成订单
    void generate();

    //修改订单
    void modify();

    //查看订单
    void detail();

    //获得名字
    String getName();
}

实现类:

package com.songzhishu.spring6.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  11:49
 * @Description: TODO
 * @Version: 1.0
 */
public class OrderServiceImpl implements  OrderService{
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {

        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }

    @Override
    public String getName() {
        System.out.println("getName方法执行");
        return "张胜男";
    }
}

客户端:

package com.songzhishu.spring6.client;

import com.songzhishu.spring6.service.OrderService;
import com.songzhishu.spring6.service.OrderServiceImpl;
import com.songzhishu.spring6.service.TimeInvocationHandler;
import com.songzhishu.spring6.utils.ProxyUtil;

import java.lang.reflect.Proxy;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.spring6.client
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  15:45
 * @Description: TODO
 * @Version: 1.0
 */
public class ClientTest {
    public static void main(String[] args) {
        //创建目标对象
        OrderService target =new OrderServiceImpl();

        //创建代理对象
        /*
        *   Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器);
        *   第一步  在内存中创建一个代理类的字节码文件
        *   第二步  通过代理类来实例化代理类对象
        *
        *   参数一  ClassLoader loader
        *          类加载器:内存中和硬盘上的class其实没有太大区别,都是class文件,都要加载到java的虚拟机中才能运行
        *                  注意:目标类的类加载器和代理类的加载器要使用的是同一个
        *
        *   参数二  Class<?>[] interfaces
        *           代理类和目标类要实现同一个或者同一些接口
        *
        *   参数三  InvocationHandler h  调用处理器类 实现一个接口
        *           然后可以编写增强代码
        * */

        //使用啦util工具
        OrderService proxy = (OrderService) ProxyUtil.newProxyInstance(target);

        //使用代理对象调用代理方法, 如果增强的话,目标方法需要执行
        proxy.generate();
        proxy.detail();
        proxy.modify();
        String name = proxy.getName();
        System.out.println(name);
    }
}

调用处理器:

package com.songzhishu.spring6.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.spring6.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  16:24
 * @Description: 调用处理器,用来计时增强功能
 * @Version: 1.0
 */
public class TimeInvocationHandler implements InvocationHandler {
    //目标对象
    private  Object target;
    //构造方法 目标对象
    public TimeInvocationHandler(Object target) {
        //给目标对象赋值
        this.target=target;
    }

    /*
    *   invoke方法什么时候调用 只有代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法就
    *   会被调用
    *
    *   invoke方法里面的参数
    *         参数一    代理对象
    *         参数二    目标对象的目标方法
    *         参数三    目标方法上的实参
    *
    *
    * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        Object revalue = method.invoke(target, args);
        //调用目标对象目标功能
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");

        //如果代理对象需要返回值的话,invoke方法必须将目标对象的目标方法的执行结果返回
        return revalue;  //返回方法的返回值!!!!!
    }
}

2、面向切面编程AOP:

        AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

        Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。

        一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务      核心业务是纵向的!这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。

如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

  • 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
  • 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。

        用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

AOP的优点:

  • 第一:代码复用性增强。
  • 第二:代码易维护。
  • 第三:使开发者更关注业务逻辑。

1、AOP的七大术语:

  • 连接点 Joinpoint在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。 
  • 切点 Pointcut在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点),本质上就是方法 
  • 通知 Advice通知又叫增强,就是具体你要织入的代码 ,连接点的位置,通知包含:
  1. 前置通知
  2. 最终通知
  3. 异常通知
  4. 环绕通知
  5. 后置通知
  • 切面 Aspect:切点 + 通知就是切面。
  • 织入 Weaving: 把通知应用到目标对象上的过程。
  • 代理对象 Proxy:一个目标对象被织入通知后产生的新对象。
  • 目标对象 Target:被织入通知的对象。

通过下图,大家可以很好的理解AOP的相关术语:

2、切点表达式:

切点表达式用来定义通知(Advice)往哪些方法上切入。语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

访问控制权限修饰符:可选项

  • 没写,就是4个权限都包括。
  • 写public就表示只包括公开的方法。

返回值类型:必填项

  • * 表示返回值类型任意。

全限定类名:可选项

  • 两个点“..”代表当前包以及子包下的所有类。
  • 省略时表示所有的类。

方法名:必填项

  • *表示所有方法。
  • set*表示所有的set方法。

形式参数列表:必填项

  • () 表示没有参数的方法
  • (..) 参数类型和个数随意的方法
  • (*) 只有一个参数的方法
  • (*, String) 第一个参数类型随意,第二个参数是String的。

异常:可选项

  • 省略时表示任意异常类型。

3、使用Spring的AOP:

Spring对AOP的实现包括以下3种方式:

  • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
  • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
  • 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

 1、基于注解:

spring配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

        <!--组件扫描-->
        <context:component-scan base-package="com.songzhishu.spring6.service"/>

        <!--开启自动代理 proxy-target-class="true" 表示强制实现cglib来生成动态代理 false表示如果接口使用jdk 如果是类使用cglib-->
        <aop:aspectj-autoproxy proxy-target-class="true" />
</beans>

目标类:

 切面:

好啦这就是大概的流程

 2、通知类型顺序:

通知类型包括:

  • 前置通知:@Before 目标方法执行之前的通知
  • 后置通知:@AfterReturning 目标方法执行之后的通知
  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
  • 异常通知:@AfterThrowing 发生异常之后执行的通知
  • 最终通知:@After 放在finally语句块中的通知

切面:

package com.songzhishu.spring6.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.spring6.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  20:14
 * @Description: TODO
 * @Version: 1.0
 */
@Component("logAspect")
@Aspect //这个注解表示是一个切面 ,如果没有这个注解就不是切面
public class LogAspect { //切面
    //切面等于通知+切点

    /*
    通知是以方法的形式出现 (方法可以写增强代码)
        @before(切点表达式)表示是一个前置通知,然后切点表达式就是可以表示要切入的方法
    */
    @Before("execution(* com.songzhishu.spring6.service..*(..))")
    public void before() {
        System.out.println("前置");
    }

    //后置
    @AfterReturning("execution(* com.songzhishu.spring6.service..*(..))")
    public void afterReturningAdvice() {
        System.out.println("后置");
    }

    //环绕 是最大的通知, 在前置之前 在后置之后
    @Around("execution(* com.songzhishu.spring6.service..*(..))")
    public void surround(ProceedingJoinPoint joinPoint) throws Throwable {
        //前
        System.out.println("前环绕");
        //目标方法
        joinPoint.proceed();
        //后
        System.out.println("后环绕");
    }

    //异常通知
    @AfterThrowing("execution(* com.songzhishu.spring6.service..*(..))")
    public  void afterThrowing(){
        System.out.println("异常通知");
    }


    //最终通知 finally
    @After("execution(* com.songzhishu.spring6.service..*(..))")
    public void ultimately(){
        System.out.println("最终");
    }
}

        然后这个是没有出现异常的时候的顺序!

然后我手动的扔出来一个异常后的执行顺序!

 

3、切面顺序:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1102666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【译】快速开始 Compose 跨平台项目

原文&#xff1a; Compose Multiplatform application 作者&#xff1a;JetBrains 注意 Compose Multiplatform 中的 iOS 部分目前处于 Alpha 状态。以后可能会有不兼容的更改&#xff0c;届时也许需要手动进行迁移。 你可以使用这个模板来开发同时支持桌面、安卓和 iOS 的跨平…

极品三国新手攻略之进阶篇

尊敬的主公大人您好&#xff0c;首先恭喜您在游戏中取得的不俗成绩&#xff0c;相信您已经熟练掌握了不少玩法。今天&#xff0c;我们给大家奉上一份极品三国新手攻略之进阶篇&#xff0c;希望能为您提供有力的帮助。本篇攻略将为您深入分析游戏中武将、装备、试炼塔以及神兵等…

【微服务 SpringCloud】实用篇 · Ribbon负载均衡

微服务&#xff08;4&#xff09; 文章目录 微服务&#xff08;4&#xff09;1. 负载均衡原理2. 源码跟踪1&#xff09;LoadBalancerIntercepor2&#xff09;LoadBalancerClient3&#xff09;负载均衡策略IRule4&#xff09;总结 3. 负载均衡策略3.1 负载均衡策略3.2 自定义负载…

“升级是找死,不升级是等死”,GitLab CE 的痛苦升级之路

编者按&#xff1a;本文转载自公众号运维识堂&#xff0c;已经联系作者取得转载授权。 GitLab 在发展的十余年中&#xff0c;在国内积累了大量的 CE 用户&#xff0c;但是很多 CE 用户并不会跟随 GitLab 的发版节奏&#xff08;月度发版&#xff09;进行版本升级&#xff0c;在…

基于AT89C52+ADC0809+LCD1602的模数转换实验ptoteus仿真设计

一、仿真原理图&#xff1a; 二、仿真效果图&#xff1a; 三、仿真工程&#xff1a; 基于AT89C52ADC0809LCD1602的模数转换实验ptoteus仿真设计资源-CSDN文库

flask实战(问答平台)

问答平台项目结构搭建 先创建一个配置文件config.py&#xff0c;后面有些配置写在这里 #app.py from flask import Flask import configapp Flask(__name__) #绑定配置文件 app.config.from_object(config)app.route(/) def hello_world(): # put applications code herer…

数据结构-----红黑树的删除操作

目录 前言 一、左旋和右旋 左旋&#xff08;Left Rotation&#xff09; 右旋&#xff08;Right Rotation&#xff09; 二、红黑树的查找 三、红黑树的删除 1.删除的是叶子节点 1.1删除节点颜色为红色 1.2删除节点颜色为黑色 1.2-1 要删除节点D为黑色&#xff0c;兄弟节…

git 提交代码

提交代码流程 第一步:git status 第二步&#xff1a;git add . 第三步&#xff1a;git commit -m"xxx" 第四步&#xff1a;git pull origin dev 第五步&#xff1a;git push origin dev

HEIC转jpg

下载imagemagick,安装 https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-20-Q16-HDRI-x64-dll.exe cmd D:\soft\ImageMagick-7.1.1-Q16-HDRI\magick.exe "C:\Users\Gamer\Downloads\iCloud 照片1\iCloud 照片\IMG_3889.HEIC" IMG_3889.jpg

不懂的东西

1、 2、 3、 4、 5、我看到那篇 Peace of mind 论文&#xff0c;有一个疑问&#xff0c;为什么论文里的量表用的频率指标&#xff1f;比如Some of the time&#xff0c; Not at all等&#xff0c;而PANAS用的是程度指标&#xff0c;比如moderately&#xff0c;a little等。…

一、初识 Elasticsearch:概念,安装,设置分词器

文章目录 01、初识 Elasticsearch正向索引和倒排索引索引MySQL与ES的概念映射安装ES分词器分词器的设置 01、初识 Elasticsearch 本次ES基于&#xff1a;7.12.1 版本 学习资源为&#xff1a;https://www.bilibili.com/video/BV1Gh411j7d6 什么是ES&#xff08;Elasticsearch&…

C/C++笔试易错与高频题型图解知识点(二)—— C++部分(持续更新中)

目录 1.构造函数初始化列表 1.1 构造函数初始化列表与函数体内初始化区别 1.2 必须在初始化列表初始化的成员 2 引用&引用与指针的区别 2.1 引用初始化以后不能被改变&#xff0c;指针可以改变所指的对象 2.2 引用和指针的区别 3 构造函数与析构函数系列题 3.1构造函数与析…

力扣环形链表(1)进阶环形链表(2)及环形链表的约瑟夫问题

为了加深对环形链表的理解和掌握&#xff0c;这两道题是很不错的选择。 这里所说环形链表不是一个圈圈的结构&#xff0c;而是带环链表。 链接&#xff1a;环形链表&#xff08;1&#xff09; 注意这里链表的长度 所以要注意链表是否为空 第一种方法&#xff0c;应该是比较容易…

竞赛选题 深度学习中文汉字识别

文章目录 0 前言1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xf…

影响多用户商城系统价格的因素有哪些?

多用户商城系统作为电商行业不可或缺的重要工具&#xff0c;其价格是众多商家关注的焦点。下面就影响多用户商城系统价格的因素有哪些作一些简单的介绍&#xff0c;希望对大家有所帮助(仅供参考)。 一、系统规模和功能 多用户商城系统的价格首先由其规模和功能决定。规模包括用…

如何使用RockPlus MES系统帮助SMT行业实现降本增效

SMT&#xff08;Surface Mount Technology&#xff09;是现代电子行业中主要的组装技术&#xff0c;广泛应用于电子产品的生产。SMT工艺涵盖了锡膏印刷、元器件贴装和回流焊接。经过这些关键工序&#xff0c;元器件被精确固定在电路板上&#xff0c;完成一个电子产品组装。 SM…

Java并发面试题:(五)volatile关键字

volatile 是什么 一旦一个共享变量&#xff08;类的成员变量、类的静态成员变量&#xff09;被volatile修饰之后&#xff0c;那么就具备了两层语义&#xff1a; 1&#xff09;保证了不同线程对这个变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c…

网工内推 | 南天软件,base北京,需持有CCIE认证,最高25k

01 北京南天软件有限公司 招聘岗位&#xff1a;IPT运维工程师 职责描述&#xff1a; 负责客户Cisco语音网络IPT ,CUCM的日常运维&#xff0c;扩容和项目支持&#xff0c;支持路由交换&#xff0c;无线等项目&#xff0c;实施工作以及相关实施文档。 任职要求&#xff1a; 1、…

css 特别样式记录

一、 这段代码神奇的地方在于&#xff0c; 本来容器的宽度只有1200px&#xff0c;如果不给img赋予宽度100%&#xff0c;那么图片 会超出盒子&#xff0c;如果给了img赋予了宽度100%&#xff0c;多个图片会根据自己图片大小的比例&#xff0c;去分完那1200px&#xff0c;如图二。…

【LeetCode热题100】--75.颜色分类

75.颜色分类 方法一&#xff1a;使用单指针 class Solution {public void sortColors(int[] nums) {int n nums.length;int ptr 0;for(int i 0;i<n;i){if(nums[i] 0){int tmp nums[i];nums[i] nums[ptr];nums[ptr] tmp;ptr;}}for(int i ptr;i<n;i){if(nums[i] …