Loading

AOP实现原理

😉 本文共3257字,阅读时间约10min

代理

代理模式

用代理对象来代替对真实对象的访问,可以在不修改原目标对象的前提下,增强目标对象的功能。

静态代理

静态代理,就是自己写代理类,几乎用不到。比如接口增加方法了,目标对象和代理对象都要改。而且每个目标类都得单独写一个代理类。

从JVM层面讲,静态代理在编译器就生成了代理类的字节码文件。

public interface SmsService {
    String send(String message);
}
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

// 代理类
public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }
	
    // 访问原方法,并在方法前后进行增强
    @Override
    public String send(String message) {
        System.out.println("before method send()");
        smsService.send(message);
        System.out.println("after method send()");
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

JDK动态代理机制

代理类和被代理类一样,是同一个接口的实现类,类似兄弟关系。

InvocationHandler 接口和 Proxy

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

  1. Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象

    public static Object newProxyInstance(
    ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){}
    // 类加载器,被加载类实现的一些接口,实现了InvocationHandler接口的对象
    
  2. 还必须需要实现InvocationHandler 来自定义处理逻辑(增强逻辑)。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

    public interface InvocationHandler {
        /**
         * 当你使用代理对象调用方法的时候实际会调用到这个方法
         */
        public Object invoke(Object proxy, Method method, Object[] args);
        // 动态生成的代理类,调用的method,method的参数
    }
    
  3. (通过Proxy 类的 newProxyInstance() 创建的代理类对象)在调用方法的时候,实际会调用到(实现InvocationHandler 接口的类)的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

    // 接口和实现类
    public interface ClacInterface {
       int add(int a ,int b );
    }
    
    public class MyClac implements ClacInterface{
      @Override
      public int add(int a, int b) {
        System.out.println("a+b"+"="+(a+b));
        return a+b;
      }
    }
    
    // 代理类对象调用InvocationHandler的invoke方法
    public static void main(String[] args) { 
        ClacInterface myClac = new MyClac(); //被代理的对象
        //生成了一个代理类对象proxyBean
        ClacInterface proxyBean = (ClacInterface)Proxy.newProxyInstance(
              myClac.getClass().getClassLoader(), myClac.getClass().getInterfaces(),
              //第三参数是InvocationHandler 这里是lambda简化的匿名内部类写法
              (a, b, c) -> {
                  System.out.println("我代理了myClac对象");
                  return b.invoke(myClac, c);
               } );
      }
    

jdk动态代理原理

原理

这里不做过多说明,具体可以看B站讲Spring原理的讲解。

代理类和被代理类一样,是同一个接口的实现类,类似兄弟关系。

生成一个实现代理接口的匿名类(就是代理类),然后重写方法进行方法增强。在调用具体方法前通过调用InvocationHandler的invoke方法来处理

在这个代理类里也有那个handler。最后相当于

\(\stackrel{调用}{\rightarrow} 代理类对象的方法 \stackrel{调用}{\rightarrow} 调用代理类里的handler的invoke方法 \stackrel{调用}{\rightarrow} 反射调用被代理类的原方法\)

  • 为啥要一个InvocationHandler接口,并在生成代理类对象作为参数传入?
    • 解耦,因为得让用户/框架能去自定义代理的增强行为

特点

他的特点是生成代理类的速度很快,但是运行时调用方法操作会比较慢,因为是基于反射机制的

只能针对接口编程,即目标对象要实现接口

反射优化

前 16 次是调用了java native的MethodAccessor 的实现类,反射性能较低

第 17 次调用会生成又一个代理类,相当于为这个方法创建了一个访问的代理类,把原来的反射调用优化为正常调用

img

img

Cglib动态代理机制

代理类是被代理类的子类,父子关系

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

MethodInterceptor 接口和 Enhancer

  1. 需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。跟那个InvocationHandler的invoke()很像。Enhancer类的create()方法可以生成代理类。

    public interface MethodInterceptor extends Callback{
        // 拦截被代理类中的方法
        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
    }
    
  2. 例子:

    // 被代理类
    public class AliSmsService {
        public String send(String message) {
            System.out.println("send message:" + message);
            return message;
        }
    }
    
    // 自定义 MethodInterceptor(方法拦截器)
    public class DebugMethodInterceptor implements MethodInterceptor {
        /**
         * @param o           被代理的对象(需要增强的对象)
         * @param method      被拦截的方法(需要增强的方法)
         * @param args        方法入参
         * @param methodProxy 用于调用原始方法
         */
        @Override
        public Object intercept
            (Object o, Method method, Object[] args, MethodProxy methodProxy) {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method " + method.getName());
            Object object = methodProxy.invokeSuper(o, args);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method " + method.getName());
            return object;
        }
    }
    
    // Enhancer的create()获取代理类
    public class CglibProxyFactory {
        public static Object getProxy(Class<?> clazz) {
            // 创建动态代理增强类
            Enhancer enhancer = new Enhancer();
            enhancer.setClassLoader(clazz.getClassLoader());
            // 设置被代理类
            enhancer.setSuperclass(clazz);
            // 设置方法拦截器
            enhancer.setCallback(new DebugMethodInterceptor());
            // 创建代理类
            return enhancer.create();
        }
    }
    
    // 使用
    AliSmsService aliSmsService = (AliSmsService)CglibProxyFactory.getProxy (AliSmsService.class);
    aliSmsService.send("java");
    > before method send
    > send message:java
    > after method send
    

Cglib动态代理原理

原理

基于asm指令,直接生成代理类的字节码。且代理类是目标类的子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。

特点

代理类继承被代理类,底层是基于asm第三方框架对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

生成代理类的速度很慢,但是运行时调用方法操作会比较快,因为是没有基于反射机制的

无反射

MethodProxy在调用invoke和invokeSuper时不会走反射途经,而是通过FastClass来避免反射,cglib在调用方法时,生成两个FastClass(为FastClass的子类),FastClass中的方法能避免反射。

$\stackrel{调用}{\rightarrow} 代理类对象的方法 \stackrel{调用}{\rightarrow} 调用代理类里的interceptor的intercepet方法 $$\stackrel{调用}{\rightarrow} 调用methodproxy的invoke方法(调用fastclass里的方法) \stackrel{调用}{\rightarrow} 正常调用目标类方法$

两种动态代理比较

比较

  • jdk动态代理机制
    • 只能代理接口实现类,代理类和被代理类都实现了同一个接口,兄弟关系
    • 基于反射调用目标类方法,有反射优化
    • 生成代理类的速度很快,但是运行时调用方法操作会比较慢
  • cglib动态代理机制
    • 能代理大部分类,除非final不能继承或重写;被代理类和代理类是父子关系
    • 用fastclass,无反射问题
    • 生成代理类的速度慢,但是运行时调用方法操作会比较快(cglib是MethodProxy调用的时候就产生代理,一个proxy代理类对应两个fastClass的子类;而且基于asm对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理会慢一点)

什么时候用cglib什么时候用jdk动态代理?

1、目标对象生成了接口 默认用JDK动态代理

2、如果目标对象使用了接口,可以强制使用cglib

3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换

Cglib比JDK快?

一开始是调用次数比较少的时候,jdk好;多就cglib好。

随着jdk版本升级,jdk动态代理效率越来越好了,大量调用效率也高于cglib

Spring AOP增强技术

AJC编译器

  • AspectJ支持在编译期类加载期通过修改class文件来实现增强。不支持运行期织入。
    • 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等增强。
    • 如果在类加载期织入,则需要使用agent。
  • 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

Spring AOP 内容与原理

作用与内容

  • 作用:面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度。

  • 内容:切面(切点 + 通知)

    aspect =
    
    ​ 通知1(advice)+ 切点1(pointcut)
    
    ​ 通知2(advice)+ 切点2(pointcut)
    
    ​ 通知3(advice)+ 切点3(pointcut)
    
    asvisor = 更细粒度的切面,只包含一个通知和切点
    
    在aspect生效之前,会被拆解成多个advisor
    

原理

Spring AOP 就是基于动态代理

  • 如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象
  • 而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

即使有接口,也可以设置参数强制使用Cglib代理

SpringAOPProcess

代理注入时机

  • 自动代理后处理器 AnnotationAwareAspectJAutoProxyCreator 会帮我们创建代理
    • 通常代理创建在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行
  1. 自动代理后处理器工作步骤
    1. 解析某个类的切面 findEligibleAdvisors
      1. 解析时会将高级的 @Aspect 切面会转换为低级的 Advisor 切面,如果此处有多个Advisor,则将其添加到ProxyFactory的List<Advisor>成员变量中。
    2. 基于所有的切面按照创建一个代理

创建代理的多个切面顺序

  • Spring AOP会将当前方法的所有切面都封装进包含原方法的增强方法,使所有切面生效。
    • 不同的增强器,如@Before、@After,他们的执行顺序由他们自身控制。
    • 同一个方法的的多个增强类内相同的增强器,增强顺序按照Spring加载类后注册BeanDefinition的顺序,即Spring注册BeanDefinition的顺序。
      • 可以在Aspect类上加@Order(1)注解,数字越小越早加载

定义的通知类型有哪些?

类型 内容
Before(前置通知) 目标对象的方法调用之前触发
After (后置通知) 目标对象的方法调用之后触发
AfterReturning(返回通知) 目标对象的方法调用完成,在返回结果值之后触发
AfterThrowing(异常通知) 目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
Around (环绕通知) 编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
  • 对外是为了方便使用要区分 before、afterReturning
  • 对内统一都是环绕通知, 统一用 MethodInterceptor 表示
posted @ 2023-02-06 12:33  iterationjia  阅读(196)  评论(0编辑  收藏  举报