注册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
// 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代理对象:

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(比如前置通知、后置通知等)将其转换成一个拦截器链,然后执行拦截器链,执行每个拦截器链的时候会执行对应通知中的方法,当拦截器链执行完毕之后,会通过反射执行真正的目标方法。

【Spring】AOP实现原理的更多相关文章

  1. 【Spring】Spring AOP实现原理

    Spring AOP实现原理 在之前的一文中介绍过Spring AOP的功能使用,但是没有深究AOP的实现原理,今天正好看到几篇好文,于是就自己整理了一下AOP实现的几种方式,同时把代理模式相关知识也 ...

  2. Spring AOP 实现原理

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...

  3. 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  4. Spring Aop底层原理详解

    Spring Aop底层原理详解(来源于csdn:https://blog.csdn.net/baomw)

  5. spring AOP底层原理实现——jdk动态代理

    spring AOP底层原理实现——jdk动态代理

  6. Spring AOP底层原理

    ------------------siwuxie095                                 Spring AOP 底层原理         AOP 即 Aspect Or ...

  7. jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  8. Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现

    前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的.本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所 ...

  9. Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建

    上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor).本文在此基 ...

  10. Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现

    我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理.@pdai Spring框架系列 ...

随机推荐

  1. Go 语言 for-range 的两个坑,你踩过吗?

    坑一:迭代时协程引用索引和值 先看看下面的例子,你知道最终输出的结果是什么吗? package main import ( "fmt" "time" ) fun ...

  2. Java 中怎样将 bytes 转换为 long 类型?

    将bytes 转换为long类型: 第一种方式: String 接收 bytes 的构造器转成 String,再 Long.parseLong: 但此种情况需要注意:字节数组中的每个字节都必须是有效的 ...

  3. 【持续更新】C++ 并不完全是 C 的超集!

    一些容易被忽略的 C 与 C++ 的不兼容特性 头文件和命名空间 C 标准库头文件名在 C++ 中通常去除扩展名,并加上 c 前缀,如: stdio.h -> cstdio stdlib.h - ...

  4. 介绍Centos7启用过程中用到的rpm软件包、及其作用

    序号 包名 作用 1 udev 系统设备管理器,用于管理设备驱动程序和设备的元数据. 2 lvm2 Logical Volume Manager 2(LVM2)是一个用于管理和分配存储设备的工具,允许 ...

  5. SpringCloud-Hystrix服务熔断与降级工作原理&源码

    先附上Hystrix源码图 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用 ...

  6. 使用 FastGPT 构建高质量 AI 知识库

    作者:余金隆.FastGPT 项目作者,Sealos 项目前端负责人,前 Shopee 前端开发工程师 FastGPT 项目地址:https://github.com/labring/FastGPT/ ...

  7. laravel artisan 常用命令

    命令 说明 php artisan key:generate 生成 App Key php artisan make:controller 生成控制器 php artisan make:model 生 ...

  8. go-zero 是如何做路由管理的?

    原文链接: go-zero 是如何做路由管理的? go-zero 是一个微服务框架,包含了 web 和 rpc 两大部分. 而对于 web 框架来说,路由管理是必不可少的一部分,那么本文就来探讨一下 ...

  9. PXE操作过程 kickstart 无人值守安装

    PXE操作过程 分配给同一局域网内新加机器的地址(配置文件) dhcp 分配地址 指明tftp 服务器的地址 tftp服务端开启 udp 配置 默认关闭 安装syslinux 取得 pxelinux. ...

  10. SimpleDateFormat 线程安全问题修复方案

    问题介绍 在日常的开发过程中,我们不可避免地会使用到 JDK8 之前的 Date 类,在格式化日期或解析日期时就需要用到 SimpleDateFormat 类,但由于该类并不是线程安全的,所以我们常发 ...