【Spring】AOP实现原理

注册AOP代理创建器

在平时开发过程中,如果想开启AOP,一般会使用@EnableAspectJAutoProxy注解,这样在启动时,它会向Spring容器注册一个代理创建器用于创建代理对象,AOP使用的是AnnotationAwareAspectJAutoProxyCreator,它实现了SmartInstantiationAwareBeanPostProcessor,从名字中可以看出这是一个Bean后置处理器BeanPostProcessor,BeanPostProcessor是Spring提供的一个扩展点,里面提供了两个方法,分别为postProcessBeforeInitialization(初始化之前)和postProcessAfterInitialization(初始化之后),可以在Bean初始化前后,进行一些操作(比如为Bean设置属性值)。

关于后置处理器的使用可参考:【Spring】BeanPostProcessor后置处理器

  • Advisor:对切面的封装,使用了@AspectJ注解的类会被Spring封装成Advisor。

AOP的实现主要在代理创建器的postProcessAfterInitialization方法中:

  • postProcessAfterInitialization:在bean初始化之后执行的方法,这时候bean已经实例化完毕,这里会调用wrapIfNecessary方法判断是否有必要为该Bean生成AOP代理对象,如果不需要创建AOP代理对象直接返回即可,反之会获取Advisors,然后创建AOP的代理对象,替换掉原来生成的Bean
java 复制代码
// AnnotationAwareAspectJAutoProxyCreator的父类AbstractAutoProxyCreator中实现
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    /**
     * 在bean初始化之后执行的方法
     */
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            // 构建缓存Key
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 是否有必要创建AOP代理对象
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
}

AOP代理条件判断

Spring需要知道有哪些类需要进行AOP代理、哪些需要跳过AOP代理,比如我们使用@Aspect标注的切面类,只是一个普通Bean,不需要进行AOP代理,而我们的目标类,就需要进行代理,所以这一步会进行判断。

处于以下情况之一是不需要进行AOP代理的,会跳过:

  1. 如果是Advice、Pointcut、Advisor、AopInfrastructureBean类本身及其子类则跳过创建,这些是Spring的基础类,不需要进行AOP代理;
  2. 如果有Aspect注解且不是通过Ajc编译的类,这个就是用@Aspect标注的切面类,也不需要AOP进行代理;
  3. 这个判断条件的作用同上,只不过个判断主要用于在XML中通过 aop:aspect标签形式来配置切面的情况,Spring会生成一个对应的AspectJPointcutAdvisor,切面本身对应的那个Java类是不需要进代理的,所以添加了一个判断,跳过切面本身对应的那个Java类,在使用注解和使用aop:aspect标签时实现不一样,所以这里又加了一个条件判断;

获取Advisor

对于上述情况的bean会跳过,剩下的Bean需要先获取所有的Advisors,从中找出适用于当前Bean的Advisor,如果查找到表示当前Bean需要进行AOP代理,依旧返回原来的Bean对象即可。

Spring会把使用了@AspectJ注解定义的切面包装成Advisor,判断是否有与当前bean匹配的Advisor,判断方式如下:

  1. 根据切点Pointcut的getClassFilter方法对类进行匹配,判断当前Bean的class是否匹配;

  2. 根据切点Pointcut获取MethodMatcher方法匹配器,通过MethodMatcher对当前Bean中的每一个方法进行匹配,也就是使用配置的切点表达式对方法进行匹配;

经过这一步处理,如果匹配到了该Bean的Advisor,说明当前Bean需要进行AOP代理,会返回适用于当前Bean的Advisor集合,接下来会为该Bean创建AOP代理对象。

创建AOP代理对象

创建代理对象

前置知识:JDK动态代理,可参考 【Java】JDK动态代理实现原理

Spring提供了两种方式创建代理对象,分别是JDK动态代理和Cglib,使用JDK动态代理需要被代理对象实现接口,否则使用Cglib实现。

以JDK动态代理为例,创建代理对象的过程在JdkDynamicAopProxy中,它实现了InvocationHandler,在通过JDK的动态代理创建对象的时候,需要这个InvocationHandler,通过Proxy的newProxyInstance即可创建AOP代理对象:

java 复制代码
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        // 生成代理对象,proxiedInterfaces为目标代理类,this为InvocationHandler也就是当前的JdkDynamicAopProxy
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
}

执行目标方法

在创建了AOP代理对象之后,会使用这个代理对象替换掉原来容器中的Bean,开发过程中拿到的Bean就是这个AOP代理对象了,当执行目标方法时,首先会进入到代理对象的invoke方法(JdkDynamicAopProxy中的invoke方法,InvocationHandler中定义了invoke方法,JdkDynamicAopProxy实现了InvocationHandler接口所以会实现这个方法):

对于AOP,在invoke方法中,会先获取目标方法的所有拦截器,Spring会将适用于当前方法的Advisor转为方法拦截器,然后使用责任链模式,对拦截器进行一个个的调用,当然如果当前方法没有对应的拦截器需要执行,直接通过反射执行目标方法即可。

因为拦截器可以有多个,所以执行拦截器链方法是一个递归调用的过程(在ReflectiveMethodInvocation中实现),它使用了一个变量currentInterceptorIndex记录了当前拦截器的下标:

  1. 判断currentInterceptorIndex是否与拦截器链的大小一致,如果一致说明已经走到了最后一个拦截器,拦截器走完就可以执行目标方法了,此时会通过反射执行目方法;
  2. 如果拦截器链未走完,会对currentInterceptorIndex加1,获取下一个拦截器,继续执行;

举个例子

比如定义了一个切面,里面设置了两个通知,分别是前置通知和环绕通知,要将这个切面作用到某个目标方法,在方法执行前后进行一些操作,Spring会将切面及通知封装为拦截器,在执行目标方法时,拦截器链中就会有两个拦截器,首先执行第一个拦截器,它是一个前置通知,执行完前置通知的方法后,会向后推进进入下一个拦截器:

执行到第二个拦截器,它是一个环绕通知,首先执行环绕通知中的前置操作(环绕通知中目标方法执行之前),运行完毕之后,该方法不会结束,会继续进入拦截器链的处理逻辑,等待目标方法执行之后再继续执行后置操作(环绕通知中目标方法执行之后的操作):

此时已经是拦截器链中最后一个,所以此时可以执行目标方法,执行完目标方法,拦截器链的逻辑已经执行完毕,所以对于第二个拦截器来说,会回到环绕通知中的处理逻辑,开始执行目标方法执行之后的后置操作。

总结
(1)在开启AOP的时候,它会向容器中注册一个AOP的代理对象创建器,它是一个后置处理器,在Spring容器中每个Bean实例化之后,初始化前后会进入到后置处理器对应的方法中,AOP创建代理对象并将原来的Bean替换就是在后置处理器的postProcessAfterInitialization方法中进行的。

(2)在创建AOP代理之前会先判断是否需要为当前Bean创建代理对象,因为并不是所有的Bean都需要进行创建,只有切面中设置要拦截的那些方法所在的Bean才需要创建AOP代理对象,所以一些Spring基础类、使用@Aspect标注的切面本身等Bean都会跳过。

(3)经过上述步骤后,会或获取所有的Advisor,Spring会将创建的切面包装成Advisor,所以可以理解为获取定义的所有切面,从中找出是否有匹配当前Bean的切面,如果有表示需要为Bean创建AOP代理,之后就会根据Bean的信息,比如是否实现了接口,来决定使用JDK动态代理还是Cglib创建代理对象,创建代理对象之后会替换掉原来的Bean,将这个代理对象返回。

(4)代理对象创建完毕之后,执行目标方法时,会进入到代理对象的业务逻辑中,在这里会获取匹配当前目标方法的所有Advisor(比如前置通知、后置通知等)将其转换成一个拦截器链,然后执行拦截器链,执行每个拦截器链的时候会执行对应通知中的方法,当拦截器链执行完毕之后,会通过反射执行真正的目标方法。

相关推荐
zy happy5 小时前
黑马点评前端Nginx启动失败问题解决记录
java·运维·前端·spring boot·nginx·spring
有梦想的攻城狮8 小时前
spring中的BeanFactoryAware接口详解
java·后端·spring·beanfactory
AcmenSan9 小时前
深入解析 Guava Cache
java·spring·guava
有梦想的攻城狮10 小时前
Java的Filter与Spring的Interceptor的比较
java·开发语言·spring·interceptor·filter
結城10 小时前
前后端的双精度浮点数精度不一致问题解决方案,自定义Spring的消息转换器处理JSON转换
spring·json·状态模式
神秘的t14 小时前
Spring Web MVC————入门(3)
前端·后端·spring·mvc
小扳14 小时前
SpringAI 大模型应用开发篇-SpringAI 项目的新手入门知识
spring·spring cloud·ai·语言模型·架构·prompt·oneapi
蒂法就是我16 小时前
spring boot启动报错:2002 - Can‘t connect to server on ‘192.168.10.212‘ (10061)
java·spring·10061
未来的JAVA高级开发工程师16 小时前
spring的注入方式都有什么区别
java·spring
jian110581 天前
java spring -framework -mvc
java·spring·mvc