代理模式与AOP

 代理模式

1、概念

使用代理类间接调用目标方法,将目标方法核心逻辑的代码从目标方法中剥离出来——实现解耦。

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

2、静态代理

创建静态代理类: 


public class CalculatorStaticProxy implements Calculator {
    
    // 将被代理的目标对象声明为成员变量
    private Calculator target;
    
    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }
    
    @Override
    public int add(int i, int j) {
    
        // 附加功能由代理类中的代理方法来实现
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
    
        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
    
        System.out.println("[日志] add 方法结束了,结果是:" + addResult);
    
        return addResult;
    }
}


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

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

3、动态代理

3.1、概念

个人理解:动态代理主要是利用反射机制来获取class对象,通过这个class对象动态得到目标类的相关的方法,然后在该方法前后添加一些功能性的代码,而静态代理是将获取核心方法的代码写死了,不利于扩展到其他的对象以及后续的维护。

参考:https://blog.csdn.net/qq_16570607/article/details/118360338

动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

3.2、创建动态代理对象的两个方法

代理类在程序运行期间,创建的代理对象称之为动态代理对象。

创建代理对象的两个方法:
//JDK动态代理

Proxy.newProxyInstance(三个参数);

//CGLib动态代理

Enhancer.create(两个参数);

3.3、两种常用的动态代理方式

  1. 基于接口的动态代理

    • 提供者:JDK
    • 使用JDK官方的Proxy类创建代理对象
    • 注意:代理的目标对象必须实现接口
  2. 基于类的动态代理

    • 提供者:第三方 CGLib
    • 使用CGLib的Enhancer类创建代理对象
    • 注意:如果报 asmxxxx 异常,需要导入 asm.jar包

以下是两种代理方式的实例代码:

生产代理对象的工厂类

public class ProxyFactory{
    /**
     * 生成对象的代理对象,对被代理对象进行所有方法日志增强
     * 参数:原始对象
     * 返回值:被代理的对象
     * JDK 动态代理
     *  基于接口的动态代理
     *  被代理类必须实现接口
     *  JDK提供的
     */
    public static Object getProxy(final Object obj){
        /**
         * 创建对象的代理对象
         * 参数一:生成代理类的类加载器
         * 参数二:被代理对象实现的接口
         * 参数三:实现了 InvocationHandler 接口的对象,作为代理对象的调用处理器,对代理对象的方法进行拦截和处理。
         */
        Object proxyInstance = Proxy.newProxyInstance(obj.getClass().getClassLoader()
                , obj.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
               /**
                 * proxy:代理对象
                 * method:代理对象需要实现的方法,即其中需要重写的方法
                 * args:method所对应方法的参数
                 */
                //方法执行前
                long startTime = System.currentTimeMillis();

                Object result = method.invoke(obj, args);//执行方法的调用

                //方法执行后
                long endTime = System.currentTimeMillis();
                SimpleDateFormat sdf = new SimpleDateFormat();
                System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n"
                        , method.getName()), sdf.format(endTime), endTime - startTime);
                return result;
            }
        });
        return proxyInstance;
    }
    /**
     * 使用CGLib创建动态代理对象
     * 第三方提供的的创建代理对象的方式CGLib
     * 被代理对象不能用final修饰
     * 使用的是Enhancer类创建代理对象
     */
    public static Object getObjectByCGLib(final Object obj){
        /**
         * 使用CGLib的Enhancer创建代理对象
         * 参数一:对象的字节码文件
         * 参数二:方法的拦截器
         */
        Object proxyObj = Enhancer.create(obj.getClass(), new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
         /**    
                
         *  o           被代理的对象(需要增强的对象)
         *  method      被拦截的方法(需要增强的方法)
         *  args        方法入参
         *  methodProxy 用于调用原始方法
        */

                //方法执行前
                long startTime = System.currentTimeMillis();

                Object invokeObject = method.invoke(obj, objects);//执行方法的调用

                //方法执行后
                long endTime = System.currentTimeMillis();
                SimpleDateFormat sdf = new SimpleDateFormat();
                System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n"
                        , method.getName()), sdf.format(endTime), endTime - startTime);
                return invokeObject;
            }
        });
        return proxyObj;
    }
}

3.4、测试

@Test
public void testDynamicProxy(){
    ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl()); // 传入目标对象作为参数创建一个代理类工厂的实例对象
    Calculator proxy = (Calculator) factory.getProxy(); // 调用代理类的方法传入相关的日志
    proxy.div(1,0); // 调用接口的相关重写方法
    //proxy.div(1,1);
}

3.5、CGLib

底层原理:

  1. 通过查看 Enhancer 类源码,最终也是生成动态代理类的字节码,动态代理类继承要被代理的类,然后实现其方法。

  2. 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。

3.6、JDK Proxy 和 CGLib 的区别

  • 一个是JDK自带的,一个是第三方提供的工具类
  • JDK Proxy 是通过拦截器加反射的方式实现的
  • JDK Proxy 只能代理实现接口的类;
  • CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。

AOP

1、AOP思想

基于动态代理思想,对原来目标对象创建代理对象,在不修改原对象代码情况下,通过代理对象调用增强功能的代码,从而对原有业务方法进行增强。

2、AOP 使用场景

  • 记录日志(调用方法后记录日志)
  • 监控性能(统计方法运行时间)
  • 权限控制(调用方法前校验是否有权限)
  • 事务管理(调用方法前开启事务,调用方法后提交关闭事务 )
  • 缓存优化(第一次调用查询数据库,将查询结果放入内存对象, 第二次调用,直接从内存对象返回,不需要查询数据库 )

3、AOP 实现原理

Spring AOP 的有两种实现方式:JDK proxy 和 CGLib 动态代理

  • 当 Bean 实现接口时,Spring 使用 JDK proxy实现。当 Bean 没有实现接口时,Spring 使用 CGlib 代理实现。
  • 通过配置可以强制使用 CGlib 代理(在 spring 配置中加入 aop:aspectj-autoproxy proxy-target-class=“true”)。

4、相关术语

tips:

通过切入点定位到特定的连接点。通俗说,就是要实际去增强的方法 。

切面即代理类对象

各种通知:

  • 前置通知:使用@Before注解标识,在被代理的目标方法执行

  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝

  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命

  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论

  • 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

5、AOP实现步骤

基于注解的AOP的实现:

  1. 将目标对象和切面(切入点+通知)交给IOC容器管理(注解+扫描)
  2. 在XML中开启AspectJ(是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的)的自动代理,为目标对象自动生成代理
  3. 将切面类通过注解@Aspect标识
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值