Spring源码解读——第一节
1.AOP, AspectJ, Spring AOP的区别
1.1 AOP
- AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。
- AOP 的实现并不是因为 Java提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现一个代理,实际运行的实例其实是生成的代理类的实例。
1.2 Spring AOP
- 它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB实现。大家一定要明白背后的意思,包括什么时候会不用 JDK 提供的动态代理,而用 CGLIB 实现。
- Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖。
- Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依赖于 IOC 容器来管理。 如果你是 web
开发者,有些时候,你可能需要的是一个 Filter 或一个 Interceptor,而不一定是 AOP。 Spring AOP 只能作用于Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法。 - Spring 提供了 AspectJ 的支持,后面我们会单独介绍怎么使用,一般来说我们用纯的 Spring AOP 就够了。
- 很多人会对比 Spring AOP 和 AspectJ 的性能,Spring AOP是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ那么好。
1.3 AspectJ:
- AspectJ 出身也是名门,来自于 Eclipse 基金会,link:https://www.eclipse.org/aspectj
属于静态织入,它是通过修改代码来实现的,它的织入时机可以是:
- Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B
引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。 - Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar
包了,这种情况我们需要增强处理的话,就要用到编译后织入。 - Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的
agent:-javaagent:xxx/xxx/aspectjweaver.jar。 - AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。Spring AOP
致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。 - 因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。
2.SpringAOP的配置方法
2.1 概念解释:
首先要说明的是,这里介绍的 Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。
后面介绍的如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
下面我们来介绍 Spring AOP 的使用方法,先从最简单的配置方式开始说起,这样读者想看源码也会比较容易。
目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容,所以大家可以放心使用。
- Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的。
- Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做
@AspectJ,但是这个和 AspectJ 其实没啥关系。
2.2 基于接口的配置
1.定义一个接口和实现类:
2.接下来,我们定义两个 advice(通知),分别用于拦截方法执行前和方法返回后:
上面的两个 Advice 分别用于方法调用前输出参数和方法调用后输出结果。
3.现在可以开始配置了,我们配置一个名为 spring_1_2.xml 的文件:
4.接下来,我们跑起来看看:
查看输出结果:
准备执行方法: createUser, 参数列表:[Tom, Cruise, 55]
方法返回:User{firstName=‘Tom’, lastName=‘Cruise’, age=55, address=‘null’}
准备执行方法: queryUser, 参数列表:[]
方法返回:User{firstName=‘Tom’, lastName=‘Cruise’, age=55, address=‘null’}
缺点分析:
此中方法有个致命的问题,如果我们需要拦截 OrderService 中的方法,那么我们还需要定义一个 OrderService 的代理。如果还要拦截 PostService,得定义一个 PostService 的代理…
而且上面会对类的所有方法进行拦截,我们需要粒度小一点的,对其中的方法进行拦截:
在上面的配置中,配置拦截器的时候,interceptorNames 除了指定为 Advice,是还可以指定为 Interceptor 和 Advisor 的。
这里我们来理解 Advisor 的概念,它也比较简单,它内部需要指定一个 Advice,Advisor 决定该拦截哪些方法,拦截后需要完成的工作还是内部的 Advice 来做。
它有好几个实现类,这里我们使用实现类 NameMatchMethodPointcutAdvisor 来演示,从名字上就可以看出来,它需要我们给它提供方法名字,这样符合该配置的方法才会做拦截。
我们可以看到,userServiceProxy 这个 bean 配置了一个 advisor,advisor 内部有一个 advice。advisor 负责匹配方法,内部的 advice 负责实现方法包装。
注意,这里的 mappedNames 配置是可以指定多个的,用逗号分隔,可以是不同类中的方法。相比直接指定 advice,advisor 实现了更细粒度的控制,因为在这里配置 advice 的话,所有方法都会被拦截。
上面我们对每一个bean都会生成一个代理,我们需要一个自动生成代理的类:
下面介绍 autoproxy 的解决方案。
autoproxy:从名字我们也可以看出来,它是实现自动代理,也就是说当 Spring 发现一个 bean 需要被切面织入的时候,Spring 会自动生成这个 bean 的一个代理来拦截方法的执行,确保定义的切面能被执行。
这里强调自动ÿ