推荐工具 objectlog
对于重要的一些数据,我们需要记录一条记录的所有版本变化过程,做到持续追踪,为后续问题追踪提供思路。objectlog工具是一个记录单个对象属性变化的日志工具,工具采用spring切面和mybatis拦截器相关技术编写了api依赖包,以非侵入方式实现对标记的对象属性进行记录,仅需要导入依赖即可,几乎不需要对原系统代码改动,下面展示简单的效果(根据对象field渲染即可):
该系统具有以下特点:
- 简单易用:系统将核心逻辑抽离,采用非侵入方式,只需要导入依赖后标注相关注解即可。
- 业务共享:系统可以同时供多个业务系统使用,彼此之间互不影响。
- 自动解析:能自动解析对象的属性变化,自动生成变化记录。
- 便于扩展:支持更多对象属性类型的扩展,支持自定义解析处理逻辑等。
- 工具性能:工具采用线程模式,脱离业务主线程,避免了解析过程对业务性能的影响。
开源地址:https://gitee.com/opensofte/objectlog,有兴趣的朋友可以看看点个star.
使用背景
我们现在有一个业务场景,每次获取数据的时候需要判断有没有权限数据,我们首先以oop角度来说明:
public class BusinessA {public void do(){//1、判断权限//1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(curUserId);//1.3 判断当前用户是否有权限if (!permList.contains(curPerm)) {//返回错误信息}//2、执行业务逻辑}
}
现在假如业务B也需要进行权限验证,那么聪明的你一定想到了拷贝一份就好啦!
public class BusinessB {public void do() {//1、判断权限//1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(curUserId);//1.3 判断当前用户是否有权限if (!permList.contains(curPerm)) {//返回错误信息}//2、执行业务逻辑}
}
此时聪明的你又发现,两边代码一样,可以提出一个公共方法来处理:
public class BusinessA {public void do(){if (!Util.hasPerm()) {//返回错误信息}//2、执行业务逻辑}
}
public class BusinessB {public void do(){if (!Util.hasPerm()) {//返回错误信息}//2、执行业务逻辑}
}
public class Util {//1、判断权限public static boolean hasPerm(){ //1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(Context.currentUserId());//Context中封装了用户信息,提供给上下文使用//1.3 判断当前用户是否有权限return permList.contains(curPerm);}
}
到这里,我们就完成了这次业务的逻辑。过了一段时间你的leader告诉你,有的接口如果标注了@IsNotPerm这个注解,就不需要进行权限验证了。聪明的你马上就想到了在Util方法中加一个参数,改改调用的地方就好了。但此时看着100+的调用链路,你陷入了沉思,为什么当初不说还有这些改动。此时你有些绝望的打开百度,经过一番搜索你发现可以用aop来解决这个问题。
到这里你知道为什么用spring的AOP而不是创建一个工具类来解决呢?
于是你马上写了一个切面信息,然后把权限判断逻辑放入其中,
@Aspect
@Component
@Slf4j
public class LogAspect {private final Logger logger = LoggerFactory.getLogger(LogClient.class);@Pointcut("@annotation(org.sweetie.objectlog.core.annotation.PermValidate)")public void objectLogPointCut() {}@Around(value = "objectLogPointCut()")public <T extends BaseEntity> void saveObject(ProceedingJoinPoint joinPoint) throws Throwable {//获取切入点信息MethodSignature sign = (MethodSignature) joinPoint.getSignature();Method method = sign.getMethod();// 判断有没有IsNotPerm标记IsNotPerm annotation = method.getAnnotation(IsNotPerm.class);boolean allowDoBusiness = annotation != null;// 如果切入点需要校验权限if (!allowDoBusiness) {//1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(Context.currentUserId());//Context中封装了用户信息,提供给上下文使用//1.3 判断当前用户是否有权限allowDoBusiness = permList.contains(curPerm);}if(allowDoBusiness) {//执行业务逻辑joinPoint.proceed(joinPoint.getArgs());} else {//抛出异常给controller捕捉}}
}
此时你将业务中的方法进行了改造
public class BusinessA {@PermValidatepublic void do(){//2、执行业务逻辑}
}
public class BusinessB {@PermValidatepublic void do(){//2、执行业务逻辑}
}
此时你发现,业务中调用权限校验的地方都消失了,业务逻辑仿佛一下子轻盈了起来
可以看到aop对业务代码进行了解耦,也方便扩展
。
Aop思想介绍
Aop的定义
AOP (Aspect Orient Programming),直译过来就是面向切面编程,AOP是一种编程思想,是面向对象编程(OOP)的一种补充。通过上面的例子我们可以发现面向切面编程,能够在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
一文了解spring事务特性
常见的Aop术语
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指那些被拦截到的点,在Spring中,指可以被动态代理拦截目标类的为方法。 |
Pointcut(切入点) | 指要对哪些Joinpoint进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到Joinpoint之后要做的事情人即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点和通知的结合。 |
常见的Aop织入时机
时期 | 说明 |
---|---|
编译期 | 切面在目标类编译时被织入,这种方式需要特殊的编译器,Aspectj的织入编译器就是以这种方式织入切面的 |
类加载期 | 切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载我器(ClassLoader),它可以在目标类引入应用之前增强目标类的字节码。 |
运行期 | 切面在应用运行的某个时期被织入一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,SpringAOP默认采用的就是这种织入方式 。 |
常见的Aop的应用场景
- 日志记录
- 事务管理
- 权限验证
- 性能监测
AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。
SpringAop
Spring AOP就是一款AOP的一种实现,Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。
- JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
- CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。
下面介绍springAop中常见的通知类型。
Spring常见通知类型
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来,可以实现上述几种通知 |
下面是通知执行的顺序:
Spring切入点表达式
表达式类型 | 描述 |
---|---|
execution | 匹配方法切入点 |
within | 匹配指定类型 |
this | 匹配代理对象实例的类型 |
target | 匹配目标对象实例的类型 |
args | 匹配方法参数 |
bean | 匹配bean的id或名称 |
@within | 匹配类型是否含有注解 |
@target | 匹配目标对象实例的类型是否含有注解 |
@annotation | 匹配方法是否含有注解 |
@args | 匹配方法参数类型是否含有注解 |
execution
: 匹配方法切入点。根据表达式描述匹配方法,是最通用的表达式类型,可以匹配方法、类、包。
注意也可以匹配抛出异常类型,省略时匹配任意类型
// 匹配public方法
execution(public * *(..))// 匹配名称以set开头的方法
execution(* set*(..))// 匹配AccountService接口或类的方法
execution(* com.xyz.service.AccountService.*(..))// 匹配service包及其子包的类或接口
execution(* com.xyz.service..*(..))
within
: 匹配指定类型。匹配指定类的任意方法,不能匹配接口。
// 匹配service包的类
within(com.xyz.service.*)// 匹配service包及其子包的类
within(com.xyz.service..*)// 匹配AccountServiceImpl类
within(com.xyz.service.AccountServiceImpl)
this
: 匹配代理对象实例的类型,匹配在运行时对象的类型。
// 匹配代理对象类型为service包下的类
this(com.xyz.service.*)// 匹配代理对象类型为service包及其子包下的类
this(com.xyz.service..*)// 匹配代理对象类型为AccountServiceImpl的类
this(com.xyz.service.AccountServiceImpl)
注意:基于 JDK 动态代理实现的 AOP,this 不能匹配接口的实现类,因为代理类和实现类并不是同一种类型,参阅《Spring中的AOP和动态代理》
target
: 匹配目标对象实例的类型,匹配 AOP 被代理对象的类型。
// 匹配目标对象类型为service包下的类
target(com.xyz.service.*)// 匹配目标对象类型为service包及其子包下的类
target(com.xyz.service..*)// 匹配目标对象类型为AccountServiceImpl的类
target(com.xyz.service.AccountServiceImpl)
args
: 匹配方法参数类型和数量,参数类型可以为指定类型及其子类。
// 匹配参数只有一个且为Serializable类型(或实现Serializable接口的类)
args(java.io.Serializable)// 匹配参数个数至少有一个且为第一个为Example类型(或实现Example接口的类)
args(cn.codeartist.spring.aop.pointcut.Example,..)
bean
: 通过 bean 的 id 或名称匹配,支持*
通配符。
// 匹配名称以Service结尾的bean
bean(*Service)// 匹配名称为demoServiceImpl的bean
bean(demoServiceImpl)
@within
: 匹配指定类型是否含有注解。当定义类时使用了注解,该类的方法会被匹配,但在接口上使用注解不匹配。
// 匹配使用了Demo注解的类
@within(cn.codeartist.spring.aop.pointcut.Demo)
@target
: 匹配目标对象实例的类型是否含有注解。当运行时对象实例的类型使用了注解,该类的方法会被匹配,在接口上使用注解不匹配。
// 匹配对象实例使用了Demo注解的类
@target(cn.codeartist.spring.aop.pointcut.Demo)
@annotation
: 匹配方法是否含有注解。当方法上使用了注解,该方法会被匹配,在接口方法上使用注解不匹配。
// 匹配使用了Demo注解的方法
@annotation(cn.codeartist.spring.aop.pointcut.Demo)
@args
: 匹配方法参数类型是否含有注解。当方法的参数类型上使用了注解,该方法会被匹配。
// 匹配参数只有一个且参数类使用了Demo注解
@args(cn.codeartist.spring.aop.pointcut.Demo)// 匹配参数个数至少有一个且为第一个参数类使用了Demo注解
@args(cn.codeartist.spring.aop.pointcut.Demo,..)
切点表达式的参数匹配:
切点表达式中的参数类型,可以和通知方法的参数通过名称绑定,表达式中不需要写类或注解的全路径,而且能直接获取到切面拦截的参数或注解信息。
- @Before(“pointcut() && args(name,…)”)
public void doBefore(String name) {
… // 切点表达式增加参数匹配,可以获取到name的信息
}- @Before(“@annotation(demo)”)
public void doBefore(Demo demo) {
// 这里可以直接获取到Demo注解的信息
}
切点表达式的参数匹配同样适用于 @within, @target, @args
- 使用
&&、|| 和 ! 来组合多个切点表达式
,表示多个表达式“与”、“或”和“非”的逻辑关系。这可以用来组合多种类型的表达式,来提升匹配效率。匹配doExecution()切点表达式并且参数第一个为Account类型的方法:
@Before(“doExecution() && args(account,…)”)
public void validateAccount(Account account) {
// 自定义逻辑
}
springAop实现
注解和xml开启aop支持方式如下:
//开启配置文件支持
<!--配置Aspectj的自动代理-->
<aop:aspectj-autoproxy/>//开启注解支持
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}
基于接口实现
配置通知时需实现org.springframework.aop包下的一些接口:
前置通知:MethodBeforeAdvice
后置通知:AfterReturningAdvice
环绕通知:MethodInterceptor
异常通知:ThrowsAdvice
前置通知拦截器MethodBeforeAdviceInterceptor:
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {private final MethodBeforeAdvice advice;public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {//前置处理 这个就是利用反射执行我们定义的前置方法this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());// 调用链条return mi.proceed();}
}
后置通知拦截器AfterReturningAdviceInterceptor:
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {private final AfterReturningAdvice advice;public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {//先执行链条Object retVal = mi.proceed();// 后利用反射执行我们定义的后置通知方法this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());return retVal;}
}
异常通知拦截器ThrowsAdviceInterceptor :
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {// 省略............@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {try {// 这个就是链条return mi.proceed();} catch (Throwable var4) {// 链条报错了 就异常处理(还需要判断是不是需要处理的异常)// 异常通知可以指定需要处理的异常Method handlerMethod = this.getExceptionHandler(var4);if (handlerMethod != null) {this.invokeHandlerMethod(mi, var4, handlerMethod);}throw var4;}}// 省略...............
}
最终通知AspectJAfterAdvice :
public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {public AspectJAfterAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJBeforeAdviceMethod, pointcut, aif);}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {Object var2;try {// 先执行链条var2 = mi.proceed();} finally {//最终执行this.invokeAdviceMethod(this.getJoinPointMatch(), (Object)null, (Throwable)null);}return var2;}}
环绕通知AspectJAroundAdvice :
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {public AspectJAroundAdvice(Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJAroundAdviceMethod, pointcut, aif);}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {if (!(mi instanceof ProxyMethodInvocation)) {throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);} else {ProxyMethodInvocation pmi = (ProxyMethodInvocation)mi;ProceedingJoinPoint pjp = this.lazyGetProceedingJoinPoint(pmi);JoinPointMatch jpm = this.getJoinPointMatch(pmi);// 这个就是去执行我们 自己写的环绕通知方法 // 所以环绕通知方法一定会有个参数嘛 joinPoint.proceed()就是执行链条return this.invokeAdviceMethod(pjp, jpm, (Object)null, (Throwable)null);}}protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {return new MethodInvocationProceedingJoinPoint(rmi);}
}
以上就是关于通知链条里面所有最后会执行的方法,可以看到共同点就是invoke方法的传参MethodInvocation ,这不就是我们之前说的连接点嘛,当然还有很多内置的其他拦截器,但这都跟我们AOP拦截器没关系
基于XML配置实现
public class LogAspectj {//前置通知public void beforeAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj前置通知】 ==========");}//后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知 public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){System.out.println("========== 【Aspectj后置通知】 ==========");}public void afterAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj后置通知】 ==========");}//环绕通知public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【环绕通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【环绕通知中的后置通知】##########");return returnVale;}/*** 异常通知:方法出现异常时,执行该通知*/public void throwAdvice(JoinPoint joinPoint, Exception ex){System.out.println("出现异常:" + ex.getMessage());}}
<!--开启aop支持,注意需要引入aop标签-->
<aop:aspectj-autoproxy/><!--业务组件bean, 需要使用到切面的bean-->
<bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/><!--日志Aspect切面-->
<bean id="logAspectjBean" class="com.apesource.log.LogAspectj"/><!--使用Aspectj实现切面,使用Spring AOP进行配置-->
<aop:config><!--配置切面--><!--注入切面bean--><aop:aspect ref="logAspectjBean"><!--定义Pointcut:通过expression表达式,来查找 特定的方法(pointcut)--><aop:pointcut id="pointcut"expression="execution(* com.apesource.service.impl.*.create*(..))"/><!--配置"前置通知"--><!--在pointcut切入点(serviceMethodPointcut)查找到 的方法执行"前",来执行当前logAspectBean的doBefore--><aop:before method="beforeAdvice" pointcut-ref="pointcut"/><!--配置“后置通知”--><!--returning属性:配置当前方法中用来接收返回值的参数名--><aop:after-returning returning="returnVal" method="afterReturningAdvice" pointcut-ref="pointcut"/> <aop:after method="afterAdvice" pointcut-ref="pointcut"/><!--配置"环绕通知"--><aop:around method="aroundAdvice" pointcut-ref="pointcut"/><!--配置“异常通知”--><!--throwing属性:配置当前方法中用来接收当前异常的参数名--><aop:after-throwing throwing="ex" method="throwAdvice" pointcut-ref="pointcut"/></aop:aspect></aop:config>
基于注解的实现
//声明当前类为Aspect切面,并交给Spring容器管理
@Component
@Aspect
public class LogAnnotationAspectj {private final static String EXPRESSION = "execution(* com.apesource.service.impl.*.create*(..))";//前置通知 @Before(EXPRESSION)public void beforeAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj前置通知】 ==========");}//后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知@AfterReturning(value = EXPRESSION,returning = "returnVal")public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){System.out.println("========== 【Aspectj后置通知】 ==========");}//后置通知@After(EXPRESSION)public void afterAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj后置通知】 ==========");}//环绕通知@Around(EXPRESSION)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【环绕通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【环绕通知中的后置通知】##########");return returnVale;}// 异常通知:方法出现异常时,执行该通知@AfterThrowing(value = EXPRESSION,throwing = "ex")public void throwAdvice(JoinPoint joinPoint, Exception ex){System.out.println("********** 【Aspectj异常通知】执行开始 **********");System.out.println("出现异常:" + ex.getMessage());System.out.println("********** 【Aspectj异常通知】执行结束 **********");}}
SpringAop失效场景
避免 Spring 的 AOP 的自调用问题在 Spring 的 AOP 代理下,只能目标方法由外部调用。我们可以使用ltw来解决这个问题https://blog.csdn.net/c39660570/article/details/106791365
切面类未被注册为bean。
SpringAop生效原理
aop切入点
从AOP配置加载点一看便知,开启AOP的配置注解是 @EnableAspectJAutoProxy
在其内部导入了一个类AspectJAutoProxyRegistrar
<aop:aspectj-autoproxy/>
注意这个和上面一样
AspectJAutoProxyRegistrar这个类实现了ImportBeanDefinitionRegistrar接口,这个接口之前说过了,可以注册BeanDefination,所以我们要看看注册的这个是什么?干了什么?
沿着那个方法一路往下,发现注册了AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator这个类可谓是最重要的类了,从下方的类图上看,它实现了很多接口,还有我们非常熟悉的后置处理器,在这里面主要实现了4个方法:
- setBeanFactory:实例化后,初始化前调用
- getEarlyBeanReference:和三级缓存有关,存在循环依赖里面会调用
- postProcessBeforeInstantiation:实例化前执行
- postProcessAfterInitialization:初始化后执行
别看有4个方法,其实下面三个方法内部都会调用一样的方法,只是需要注意在Bean生成流程中的介入点
- AbstractAutoProxyCreator
实例前执行postProcessBeforeInstantiation()
实例前执行,主要是判断代理目标对象是否已经存在了,存在了就走getAdvicesAndAdvisorsForBean方法,然后调用createProxy()方法创建代理对象
Object cacheKey = this.getCacheKey(beanClass, beanName);if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {if (this.advisedBeans.containsKey(cacheKey)) {return null;}if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return null;}}// 判断代理目标对象是否已经存在了 存在了就进入代理流程TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);if (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);// 创建动态代理对象Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;} else {return null;}
初始化后执行postProcessAfterInitialization
初始化后执行,会调用wrapIfNecessary()方法
//该bean初始化完毕之后,回调该方法判断该bean是否需要被代理
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = this.getCacheKey(bean.getClass(), beanName);//如果该bean未执行过AOP,则进行封装;如果执行过,则不再进行封装if (this.earlyProxyReferences.remove(cacheKey) != bean) {return this.wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
wrapIfNecessary()方法也会调用getAdvicesAndAdvisorsForBean
方法来获取对应的通知处理,如果没获取到通知处理方法说明不需要代理,获取到了就要创建代理对象了createProxy()
注意: 这里的通知处理就是切面里面的通知方法,getAdvicesAndAdvisorsForBean就是获取所有的切面类里面的切点及通知方法与Bean来匹配,匹配上了说明这个Bean要被代理,同时会封装匹配的切点对应的所有通知方法返回
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {// 获取该bean的所有的通知处理Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);// 获取的通知处理不为空 说明要代理if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 创建代理Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;} else {// 为空就不需要创建代理了 直接返回Beanthis.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}} else {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}
}
循环依赖会调用getEarlyBeanReference
三级缓存,存在循环依赖则会调用,这里put进去代表已经生成代理了,所以后续初始化后调用的时候会get判断一次,这个也会调用wrapIfNecessary() 方法
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = this.getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return this.wrapIfNecessary(bean, beanName, cacheKey);
}
总结
: 所以会在Bean实例化前、循环依赖、初始化后介入处理,当然只会处理一次,最终都会调用getAdvicesAndAdvisorsForBean方法来对Bean进行切点匹配,匹配上了就调用createProxy方法生成代理对象然后返回
获取所有切面处理
AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean()
会先获取所有的切面其下的通知方法,然后根据切点表达式去和这个Bean对象匹配,将匹配成功的通知方法返回,这就说明该Bean需要被代理,匹配成功的通知方法排序后就是需要执行的方法调用链
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 获取所有切面其下的切面通知方法List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);// 为空返回空数组 不为空转成数组返回return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
}// 获取所有切面及其下的切面通知方法
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 获取所有切面及其下的切面通知方法List<Advisor> candidateAdvisors = this.findCandidateAdvisors();// 从中根据切点筛选出符合Bean的通知方法List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);this.extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}
AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors
有个父类的方法是获取一些实现了Advisor接口的Bean,我们重点关注被@Aspect注解标识的Bean的处理
protected List<Advisor> findCandidateAdvisors() {// 获取所有实现了Advisor接口的Bean 有些内置的比如事务List<Advisor> advisors = super.findCandidateAdvisors();if (this.aspectJAdvisorsBuilder != null) {// 获取被注解@Aspect标识的Bean 以及其下的切点和通知方法advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}
处理所有切面其下通知方法: BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors
会遍历所有的Bean找到其中被注解 @Aspect 标识的,然后去处理其下的切点和通知方法
public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = this.aspectBeanNames;if (aspectNames == null) {synchronized(this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new ArrayList();List<String> aspectNames = new ArrayList();String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);String[] var18 = beanNames;int var19 = beanNames.length;// 遍历所有的Beanfor(int var7 = 0; var7 < var19; ++var7) {String beanName = var18[var7];if (this.isEligibleBean(beanName)) {Class<?> beanType = this.beanFactory.getType(beanName, false);// 判断是否被@Aspect注解标识 标示的就需要去处理其下的切点和通知方法if (beanType != null && this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);// 去获取其下的切点和通知方法List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);} else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);} // 省略..............}}}this.aspectBeanNames = aspectNames;return advisors;}}}// 省略..............
}
获取切面下所有的通知方法
ReflectiveAspectJAdvisorFactory.getAdvisors
遍历切面下的所有方法,去找方法上是否有相应的注解,如果有则需要封装处理
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();this.validate(aspectClass);MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new ArrayList();// 获取切面下的所有方法Iterator var6 = this.getAdvisorMethods(aspectClass).iterator();// 遍历所有方法while(var6.hasNext()) {Method method = (Method)var6.next();// 判断该方法是否被相关注解标识 标识的方法处理后封装返回Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);if (advisor != null) {advisors.add(advisor);}}// 省略......return advisors;}
获取具体通知方法:ReflectiveAspectJAdvisorFactory.getAdvisor
遍历我需要的注解,在方法上找注解是否存在,存在的就需要封装处理
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());// 获取方法上的注解 实际就是遍历需要的注解 一个个找AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());// 没有对应的注解就返回null 有对应的注解就需要处理封装后返回return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {// 看下面方法AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;} else {// 找到了就设置一下切点上的表达式AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);ajexp.setExpression(aspectJAnnotation.getPointcutExpression());if (this.beanFactory != null) {ajexp.setBeanFactory(this.beanFactory);}return ajexp;}
}
// ASPECTJ_ANNOTATION_CLASSES = new Class[]{Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
protected static AbstractAspectJAdvisorFactory.AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {// 遍历需要的注解,一个一个找Class[] var1 = ASPECTJ_ANNOTATION_CLASSES;int var2 = var1.length;for(int var3 = 0; var3 < var2; ++var3) {Class<?> clazz = var1[var3];AbstractAspectJAdvisorFactory.AspectJAnnotation<?> foundAnnotation = findAnnotation(method, clazz);if (foundAnnotation != null) {return foundAnnotation;}}return null;
}
通知方法的封装
InstantiationModelAwarePointcutAdvisorImpl
这个在构造里面就会对通知方法进行处理封装
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {this.declaredPointcut = declaredPointcut;this.declaringClass = aspectJAdviceMethod.getDeclaringClass();this.methodName = aspectJAdviceMethod.getName();this.parameterTypes = aspectJAdviceMethod.getParameterTypes();this.aspectJAdviceMethod = aspectJAdviceMethod;this.aspectJAdvisorFactory = aspectJAdvisorFactory;this.aspectInstanceFactory = aspectInstanceFactory;this.declarationOrder = declarationOrder;this.aspectName = aspectName;if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);this.pointcut = new InstantiationModelAwarePointcutAdvisorImpl.PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);this.lazy = true;} else {this.pointcut = this.declaredPointcut;this.lazy = false;// 封装通知方法this.instantiatedAdvice = this.instantiateAdvice(this.declaredPointcut);}}
ReflectiveAspectJAdvisorFactory.getAdvice 所有的通知方法都会被封装成对应处理类
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();this.validate(candidateAspectClass);AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;} else if (!this.isAspect(candidateAspectClass)) {throw new AopConfigException("Advice must be declared inside an aspect type: Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]");} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Found AspectJ method: " + candidateAdviceMethod);}Object springAdvice;// 根据方法上的注解类型 封装对应的通知方法处理类switch(aspectJAnnotation.getAnnotationType()) {case AtPointcut:if (this.logger.isDebugEnabled()) {this.logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");}return null;case AtAround:springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtBefore:springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfter:springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfterReturning:springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterReturning afterReturningAnnotation = (AfterReturning)aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterReturningAnnotation.returning())) {((AbstractAspectJAdvice)springAdvice).setReturningName(afterReturningAnnotation.returning());}break;case AtAfterThrowing:springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterThrowing afterThrowingAnnotation = (AfterThrowing)aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {((AbstractAspectJAdvice)springAdvice).setThrowingName(afterThrowingAnnotation.throwing());}break;default:throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);}((AbstractAspectJAdvice)springAdvice).setAspectName(aspectName);((AbstractAspectJAdvice)springAdvice).setDeclarationOrder(declarationOrder);String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);if (argNames != null) {((AbstractAspectJAdvice)springAdvice).setArgumentNamesFromStringArray(argNames);}((AbstractAspectJAdvice)springAdvice).calculateArgumentBindings();return (Advice)springAdvice;}
}
通知方法与Bean匹配
:AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);List var4;try {// 通知方法集合与Bean匹配var4 = AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);} finally {ProxyCreationContext.setCurrentProxiedBeanName((String)null);}return var4;
}
总结: 所以这一步会找到所有的切面,遍历其下的所有切点和通知方法,然后根据切点中的表达式去与Bean对象匹配,获取所有匹配成功的通知方法,将这些通知方法排序后就是最后的方法执行链,同时也说明该Bean需要被代理,所以需要创建代理对象
创建代理对象
AbstractAutoProxyCreator.createProxy
这里实际就是在创建代理对象前填充一下必要信息,然后创建代理对象,默认是采用JDK动态代理,如果被代理的目标对象不是接口,则会采用Cglib动态代理
- CglibAopProxy:Cglib动态代理逻辑类
- JdkDynamicAopProxy:Jdk动态代理逻辑类(我们以这个为例)
protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);// 省略一大段...........// 匹配成功的某些通知方法会被包装成拦截器 上面说过了Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);this.customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (this.advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}ClassLoader classLoader = this.getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader)classLoader).getOriginalClassLoader();}// 上面设置搞定后 就要获取代理对象 JDK还是Cglibreturn proxyFactory.getProxy(classLoader);}
JdkDynamicAopProxy.getProxy
这一步很简单就是直接创建代理对象,处理类是this,说明该类本身就是处理类
public Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isTraceEnabled()) {logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());}return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
代理执行方法
我们以JDK动态代理为例,最终代理对象在执行方法的时候就会调用该方法:
JdkDynamicAopProxy.invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Object target = null;Class var8;try {// 省略...........if (method.getDeclaringClass() != DecoratingProxy.class) {Object retVal;// 省略...........target = targetSource.getTarget();Class<?> targetClass = target != null ? target.getClass() : null;// 根据具体要执行的方法 再去之前匹配成功的通知方法集合中找对应的增强方法// 前面匹配的通知方法集合并不一定是针对类下的所有方法 所以还需要匹配一次List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// 为空说明该方法并不需要增强 所以直接调用原本方法即可if (chain.isEmpty()) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);} else {// 不为空说明需要增强 所以会包装一个连接点 // 然后执行 调用链条 MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);retVal = invocation.proceed();}Class<?> returnType = method.getReturnType();if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {retVal = proxy;} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);}Object var12 = retVal;return var12;}var8 = AopProxyUtils.ultimateTargetClass(this.advised);} finally {// 省略...........}return var8;}
原理总结
- AOP代理对象的生成是在Bean实例化前、循环依赖、初始化后这三个位置判断生成的(以初始化后为主,其他两个阶段属于特殊阶段)
- 通过获取所有的切面下的通知方法以切点表达式来与Bean匹配,来判断该Bean是否需要被代理,同时准备好了与该Bean相关的所有增强方法
- AOP默认采用JDK动态代理的方式,如果被代理目标对象不是接口,则会采用Cglib的代理方法
- AOP的底层原理虽然是动态代理,但是我觉得最重要的还是执行的方法调用链非常巧妙
- 在逻辑实现上:每种通知在调用链上执行的方式及其执行顺序决定了其扮演的角色
最后附上个执行结构图
参考博文
Spring AOP切点表达式(Pointcut)详解
SpringAop介绍
SpringAop介绍与原理