前言
Spring源码学习笔记,Spring-AOP实现大致过程,错误之处欢迎指正,共同学习
介绍
类应该是纯净的,不应含有与本身无关的逻辑。单一职责原则。
Spring AOP 接口设计
AOP概念总结
1.Advice 2.Pointcut 3.Advisor 4.Joinpoint 5.Aspect -------------------------------------------------------------------- 1.Advice(增强,定义在链接点做什么) a.Before Advice(执行前增强,实现MethodBeforeAdvice接口) b.Around Advice(环绕增强,可以实现以上三种功能,实现MethodInterceptor接口) c.After Running Advice(方法执行后增强,实现AfterRunningAdvice接口) d.After Throw Advice(抛出异常后增强,实现ThrowsAdvice接口) e.Introduction(引入,特殊的advice,普通advice都是作用在pointcut指定的方法上,而introduction作用在类上,可以添加类中没有的方法和属性) 2.Pointcut(连接点,定义匹配哪些方法) 3.Advisor(通知器,将 Advice 和 PointCut 结合起来,目的是为了将advice与pointcut解耦和) a.PointcutAdvisor(一个advice和一个pointcut,作用在某个连接点上) b.IntroductionAdvisor(作用于类上) 4.Joinpoint(连接点,所有可能的需要注入切面的地方。如方法前后、类初始化、属性初始化前后等等。pointcut只能选择连接点切入) a.通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等 5.Aspect(切面,切面是切点和增强的集合,一般单独作为一个类。通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能。)
Spring AOP 的底层实现有两种可选,一种是 JDK 动态代理,一种是 CGLib 动态代理。先说下结论,如果要代理的 target 有接口,则默认采用 JDK 动态代理。如果没有,则采用 CGLib 动态代理。当然也可以强制指定使用 CGLib 动态代理。方法:
- XML配置AOP: <aop:config proxy-target-class=”true”>
- 注解配置AOP: <aop:aspectj-autoproxy proxy-target-class=”true”/>
CGLib 创建代理的速度比较慢,但创建代理后运行的速度却非常快,而 JDK 动态代理正好相反。如果在运行的时候不断地用 CGLib 去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用 CGLib 去创建代理,并放入 Spring 的 ApplicationContext 中以备后用。
源码开始
Spring 使用 AOP 可以通过XML配置和注解两种方式,但是底层原理都是一样的。Spring 是否支持注解的 AOP 是由一个配置文件控制的,也就是<aop:aspectj-autoproxy />,当在配置文件中声明了这句配置的时候,Spring就会支持注解的 AOP,那么我们分析就从这句注解开始
1 | // AopNamespaceHandler.java |
AnnotationAwareAspectJAutoProxyCreator
在类的层级中,我们看到 AnnotationAwareAspectJAutoProxyCreator 实现了 BeanPostProcessor 接口,而实现 BeanPostProcessor 后,当 Spring 加载这个 Bean 时会在实例化前调用其 postProcessAfterInitialization 方法,而我们对于 AOP 逻辑的分析也由此开始。
1 | // 父类 AbstractAutoProxyCreator |
函数中我们已经看到了代理创建的雏形。当然,真正开始之前还需要经过一些判断,比如是否已经处理过或者是否是需要跳过的bean,而真正创建代理的代码是从 getAdvicesAndAdvisorsForBean 开始的。
创建代理主要包含了两个步骤:
- 获取增强方法或者增强器
- 根据获取的增强进行代理
获取增强方法的实现
1 | protected Object[] getAdvicesAndAdvisorsForBean( |
获取增强器
1 | protected List<Advisor> findCandidateAdvisors() { |
- 获取所有 beanName,这一步骤中所有在 beanFactory 中注册的Bean都会被提取出来
- 遍历所有 beanName,并找出声明 AspectJ 注解的类,进行进一步的处理
- 对标记为 AspectJ 注解的类进行增强器的提取
- 将提取结果加入缓存
寻找匹配的增强器
1 | protected List<Advisor> findAdvisorsThatCanApply( |
findAdvisorsThatCanApply 函数的主要功能是寻找所有增强器中适用于当前 class 的增强器。引介增强与普通的增强处理是不一样的,所以分开处理。而对于真正的匹配在 canApply 中实现
1 | public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { |
创建代理
在获取了所有对应bean的增强器后,便可以进行代理的创建了
1 | protected Object createProxy(Class<?> beanClass, @Nullable String beanName, |
对于代理类的创建及处理,Spring委托给了ProxyFactory去处理,此函数中主要是对ProxyFactory的初始化操作,进而对真正的创建代理做准备。
- 获取当前类中的属性
- 添加代理接口
- 封装Advisor并加入到ProxyFactory中
- 设置要代理的类
- 为子类提供了定制的函数 customizeProxyFactory,子类可以在此函数中进行对 ProxyFactory的进一步封装
- 进行获取代理操作
将拦截器封装为增强器
1 | protected Advisor[] buildAdvisors( String beanName, Object[] specificInterceptors) { |
由于Spring中涉及过多的拦截器、增强器、增强方法等方式来对逻辑进行增强,所以非常有必要统一封装成Advisor来进行代理的创建
代理的创建与获取
1 | public Object getProxy(@Nullable ClassLoader classLoader) { |
创建代理
1 | protected final synchronized AopProxy createAopProxy() { |
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换
区别:
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final
获取代理
两种代理方式的使用方法
- JDK代理使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71// 业务接口
public interface Subject {
void request();
void hello();
}
// 业务接口实现类
public class RealSubject implements Subject{
public void request() {
System.out.println("real subject execute request");
}
public void hello() {
System.out.println("hello");
}
}
// 创建自定义的 InvocationHandler,用于对接口提供的方法进行增强
public class JdkDynamicProxy implements InvocationHandler{
//目标对象
private Object target;
public JdkDynamicProxy(Object target) {
super();
this.target = target;
}
//执行目标对象的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = null;
try{
result = method.invoke(target,args);
}catch (Exception e){
System.err.println("ex:"+e.getMessage());
throw e;
}finally {
System.out.println("after");
}
return result;
}
// 获取目标对象的代理对象
public Object getProxy() {
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(),this);
}
}
public void test() {
// 实例化目标对象
Subject subject = new RealSubject();
// 实例化 InvocationHandler
JdkProxySubject jdkProxySubject = new JdkProxySubject(subject);
// 根据目标对象生成代理对象
Subject proxy = (Subject) jdkProxySubject.getProxy();
proxy.request();
}
执行结果如下
before real subject execute request after
那么,我们看看Spring中的JDK代理实现。继续之前的跟踪,到达JdkDynamicAopProxy.getProxy()
1 | public Object getProxy(@Nullable ClassLoader classLoader) { |
invoke 函数最主要的工作就是创建了一个拦截器链,并使用 ReflectiveMethodInvocation 类进行了类的封装,而在 ReflectiveMethodInvocation 类的 proceed 方法中实现了拦截器的逐一调用
1 | // 对拦截器的调用处理 |
- CGLIB使用示例
1 | public class MethodInterceptorDemo implements MethodInterceptor{ |
执行结果如下
before in cglib hello after in cglib
CglibAopProxy 有众多内部类。这些内部类是为了实现 Cglib 的各种回调而实现的。主要实现了 MethodInterceptor 接口,Callback 接口,Joinpoint 接口,Invocation 接口等待,总之是实现了Spring 的 cglib 模块的各种接口。
实现与 JDK 方式实现代理中的 invoke 方法大同小异,都是首先构造链,然后封装此链进行串联调用。
区别是JDK中直接构造 ReflectiveMethodInvocation,而在cglib中使用 CglibMethodInvocation。CglibMethodInvocation 继承自 ReflectiveMethodInvocation,但是proceed方法没有重写
总结
通过源码分析我们知道注解方式的底层是通过继承 ProxyProcessorSupport 来实现的,并且扩展的是 BenaPostProcessor 接口,通过Spring 的扩展接口,能够对特定的Bean进行增强。而 AOP 正是通过这种方式实现的。我们也可以通过扩展 Spring 的某些接口来增强我们需要的 Bean 的某些功能。