Spring的AOP是我们在开发中比较常用的一种思想,比如日志的数据等,那Spring是如何通过配置来创建AOP的呢
本文主要通过注解配置来讲解
我们在使用注解配置AOP的时候通常需要@EnableAspectJAutoProxy这个注解来开启AOP
那其实这个注解里的@Import注解里有个AspectJAutoProxyRegistrar类来注册对应的BeanDefinition
这个类里注册了一个AnnotationAwareAspectJAutoProxyCreator这个BeanDefinition
我们可以看看这个类的继承关系图
这个类最终是实现了InstantiationAwareBeanPostProcessor、SmarInstantiationAwareBeanPostProceessor、 BeanPostProcessor这三个后置处理器,所以在这个Bean创建的时候会第一次调用InstantiationAwareBeanPostProcessor接口里的postProcessBeforeInstantiation()方法来解析切面,会在初始化后调用BeanPostProcessor后置处理器的postProcessAfterInitialization()方法来创建动态代理,在遇到循环依赖的时候会调用SmarInstantiationAwareBeanPostProceessor后置处理器的getEarlyBeanReference()方法来解决循环引用AOP
那么我们可以按照顺序来寻找对应的代码
首先是在创建Bean的时候,会第一次调用BeanPostProcessor后置处理器
这个方法里就是判断是否是继承了InstantiationAwareBeanPostProcessor,如果继承了的话则直接调用postProcessBeforeInstantiation()方法
而这里就会去解析我们配置的切面类
那我们再来看看它是如何去解析切面的
首先会去创建一个缓存,然后去判断是否被解析过以及做一些过滤
那根据什么去过滤的呢
这里主要就是判断这个解析的类是不是Advice、Pointcut、Advisor等
如果没有需要过滤的话,那么就会调用shouldSkip()方去解析切面了
这里首先会去获取候选的Advisors
我们继续跟代码看findCandidateAdvisors()方法,这个方法里会调用父类的方法来获取实现了Advisor接口的Bean
这里为什么要去获取Advisor接口的实现类呢,主要就是为了兼容老版本配置AOP,那么到了下一步就是去调用buildAspectJAdvisors()方法,那这个方法就是用来解析切面类
这个方法里首先会去获取所有的BeanDefinition
在获取到所有的BeanDefinition之后就会一个一个去判断是不是切面
那如果来判断的呢,就是根据你这个类上面有没有@Aspect注解来判断的
我们可以进入到isAspect()方法里看看
如果是切面类的话就会加入到缓存中
然后再去获取切面当中所有的通知
这个方法会直接获取除@Pointcut注解之外的所有的方法
这个方法在获取完之后会根据注解类型对方法进行排序
获取之后,再一个一个去循环方法并解析成一个Advisors
getAdvisor()也会对切面类里的方法进行判断,判断方法上是否含有@Pointcut、@Around、@Before等这些注解
并会按照顺序来解析
在解析的时候会优先获取引用的切点表达式
获取完之后就会去创建InstantiationModelAwarePointcutAdvisorImpl类,这个类就是Advisor的实现类
这个类在初始化的时候会把切面中的通知构造为一个一个的advice通知对象
这里再构造的时候会根据不通的注解创建不同的advice
到了这里整个的advisor集合就创建好了,创建好了之后再将切面的BeanName加入到缓存中去
然后再将所有的advisor加入到集合里去,这里的集合就是候选的Advisor
在获取到候选的Advisor的之后还会去判断Advisor是不是xml解析出来的Advisor,如果是的话就需要跳过,因为xml配置的切面是没有注解的
所有的Advisor都解析完了之后,再根据Advisor去做匹配然后创建动态代理
而创建动态代理会在Bean初始化后去创建,而初始化后会调用BeanPostProcess里的postProcessAfterInitialization()方法来创建动态代理
这里首先会从缓存中去获取对应的Bean,如果是循环依赖创建动态代理并且是现在的Bean就不再创建,并且移除
如果没有循环依赖则直接调用wrapIfNecessary()方法来创建代理实例这个方法里通过一系列的判断之外,就会根据BeanName去做匹配
这个方法会通过切面的BeanName来从缓存中获取对应的Advisor
在将切面类里的通知解析成一个Advisors之后,紧接着就会根据切点表达是来对方法进行匹配,匹配上了就会创建动态代理
在获取完之后就会判断所有的通知是否可以应用到bean上
这个方法是通过AspectJ相关的API去做判断,通过调用findAdvisorsThatCanApply()方法
这个方法里它会去循环所有的Advisor
在循环的时候会去判断是否实现了IntroductionAdvisor接口
这一步判断完之后就会去匹配对应的通知
在匹配完成之后又会往接口里加一个Advisor
加完之后就会对Advisor进行排序
匹配完成之后再给匹配上了的bean进行创建动态代理
在createProxy()方法里面首先会创建一个代理工厂
创建完成之后会去判断有没有proxyTargetClass属性,如果又的话会去设置一下这个属性
最后去创建动态代理
这个方法会直接通过createAopProxy()方法来创建动态代理对象
这个方法里会根据对应的条件来创建对应的动态代理
如果没有接口,没有proxyTargetClass属性就是直接使用jdk动态代理
然后再去调用getProxy()方法来创建jdk动态代理
创建完之后就会直接返回回去
存放到一级缓存中
再调用Bean里的某个方法的时候会直接来到JdkDynamicAopProxy类里的invoke()方法
通过一系列的判断执行之后,会将Advisor转换为interceptor,因为只有实现了interceptor才会有invoke()方法,最后通过这个invoke()方法来进行责任链调用
转换完成之后再去调用proceed()方法
这个方法里面就是通过责任链的方式去一次调用通知
Spring的AOP就是对动态代理模式的一种运用,我们具体再来看看这个动态代理模式
动态代理一种分为两种,一种是JDK的动态代理,还有一种是cglib动态代理
我们先来看看第一种
第一种主要是JDK提供的一种动态代理方法,这个动态代理方式需要一个接口,也就是被代理的那个类需要实现一个接口才可以
我们这里有个User的接口
package com.dlmo.dtdl;
public interface UserInterface {
// 玩游戏
public void playGame();
}
这个接口里面有个playGeme()方法
我们还需要一个实现类实现这个接口
package com.dlmo.dtdl;
public class UserImpl implements UserInterface{
public void playGame() {
System.out.println("玩游戏!!!");
}
}
写完了被代理类之后,我们还需要写一个增强的方法
JDK给我们提供了一个InvocationHandler类,我们只需要实现这个类,重写里面的invoke()方法就行了
package com.dlmo.dtdl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InvokeHandler implements InvocationHandler {
//代表目标对象
private Object target = null;
public InvokeHandler(Object target) {
//给目标对象赋值
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强!增强!");
return method.invoke(target, args);
}
}
在执行目标方法之前需要做什么逻辑, 我们只需要在invoke()方法里写就行了
package com.dlmo.dtdl;
import java.lang.reflect.Proxy;
public class DtdlDemo {
public static void main(String[] args) {
UserInterface user = new UserImpl();
InvokeHandler invokeHandler = new InvokeHandler(user);
UserInterface userInterface = (UserInterface)Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(), invokeHandler);
userInterface.playGame();
}
}
此时我们只需要调用代理对象的方法就可以进行增强了
我们再来看看第二种
cglib动态代理是不需要用到接口的,但是需要额外的进入一个cglib的jar包
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>2.2.2version>
dependency>
我们重新创建一个被代理类
package com.dlmo.dtdl;
public class CglibUser {
public void playGame(){
System.out.println("打游戏!!!");
}
}
写完被代理类之后还需要一个增强类
cglib给我们提供了一个MethodInterceptor 接口,我们只需要实现这个接口,实现这个intercept()方法就行了
package com.dlmo.dtdl;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibInterceptor implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("增强!增强!");
return arg3.invokeSuper(arg0, arg2);
}
}
都写完了之后,我们就可以通过cglib的来创建动态代理了
package com.dlmo.dtdl;
public class CglibDtdlDemo {
public static void main(String[] args) {
CglibUser cglibInterceptor = (CglibUser)new CglibInterceptor().getProxy(CglibUser.class);
cglibInterceptor.playGame();
}
}