摘要
AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了将不同的关注点分离出来的效果。本文深入剖析Spring的AOP的原理。
一、spring AOP相关的概念
1.1 PointCut
即在哪个地方进行切入,它可以指定某一个点,也可以指定多个点。比如类A的methord函数,当然一般的AOP与语言(AOL)会采用多用方式来定义PointCut,比如说利用正则表达式,可以同时指定多个类的多个函数。
我们来看一下PointCut的类图,以PointCut接口为核心进行扩展。PointCut 依赖了ClassFilter和MethodMatcher,ClassFilter用来指定特定的类,MethodMatcher 指定特定的函数,正是由于PointCut仅有的两个依赖,它只能实现函数级别的AOP。对于属性、for语句等是无法实现该切点的。MethodMatcher 有两个实现类StaticMethodMatcher和DynamicMethodMatcher,它们两个实现的唯一区别是isRuntime(参考下面的源码)。StaticMethodMatcher不在运行时检测,DynamicMethodMatcher要在运行时实时检测参数,这也会导致DynamicMethodMatcher的性能相对较差。
类似继承于StaticMethodMatcher和DynamicMethodMatcher也有两个分支StaticMethodMatcherPointcut和DynamicMethodMatcherPointcut,StaticMethodMatcherPointcut是我们最常用,其具体实现有两个NameMatchMethodPointcut和JdkRegexpMethodPointcut,一个通过name进行匹配,一个通过正则表达式匹配。有必要对另外一个分支说一下ExpressionPointcut,它的出现是了对AspectJ的支持,所以其具体实现也有AspectJExpressionPointcut。最左边的三个给我们提供了三个更强功能的PointCut,AnnotationMatchingPointcut:可以指定某种类型的注解,ComposiblePointcut:进行与或操作,ControlFlowPointcut:这个有些特殊,它是一种控制流,例如类A 调用B.method(),它可以指定当被A调用时才进行拦截。
1.2 Advice
在切入点干什么,指定在PointCut地方做什么事情(增强),打日志、执行缓存、处理异常等等。
AfterAdvice是指函数调用结束之后增强,它又包括两种情况:异常退出和正常退出;BeforeAdvice指函数调用之前增强;Inteceptor有点特殊,它是由AOP联盟定义的标准,也是为了方便Spring AOP 扩展,以便对其它AOL支持。Interceptor有很多扩展,比如Around Advice的功能实现(具体实现是Advisor的内容了,接下来再看)
1.3 Advisor/Aspect
PointCut + Advice 形成了切面Aspect,这个概念本身即代表切面的所有元素。但到这一地步并不是完整的,因为还不知道如何将切面植入到代码中,解决此问题的技术就是PROXY。同样Advisor按照Advice去分也可以分成两条线路,一个是来源于Spring AOP 的类型,一种是来自AOP联盟的Interceptoor, IntroductionAdvisor就是对MethodInterceptor的继承和实现。
所以接下类我们还是分成两类来研究其具体实现:Spring AOP的PointcutAdvisor
AbstractPointcutAdvisor 实现了Ordered,为多个Advice指定顺序,顺序为Int类型,越小优先级越高,AbstractGenericPointcutAdvisor 指定了Advice,除了Introduction之外的类型
下面具体的Advisor实现则对应于PointCut 的类型,具体指定哪个pointCut。
1.4 Proxy
Proxy 即代理,其不能算做AOP的家庭成员,更相当于一个管理部门,它管理 了AOP的如何融入OOP。之所以将其放在这里,是因为Aspect虽然是面向切面核心思想的重要组成部分,但其思想的践行者却是Proxy,也是实现AOP的难点与核心据在。
ProxyConfig设置了几个参数:
二、spring AOP 的实现方式
AOP分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为:JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)。尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。
2.1 JDK动态代理
主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。JDK动态代理要求被代理实现一个接口,只有接口中的方法才能够被代理。其方法是将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在 被代理对象调用它的方法时,在调用的前后插入一些代码。而 Proxy.newProxyInstance() 能够利用中间对象来生产代理对象。插入的代码就是切面代码。所以使用JDK动态代理可以实现AOP。
JDK动态代理:必须是面向接口的,只有实现了具体接口的类才能生成代理对象
2.2 CGLIB动态代理
CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。CGLIB动态代理和jdk代理一样,使用反射完成代理,不同的是他可以直接代理类(jdk动态代理不行,他必须目标业务类必须实现接口),CGLIB动态代理底层使用字节码技术,CGLIB动态代理不能对 final类进行继承。(CGLIB动态代理需要导入jar包)。
最底层的是字节码 Bytecode ,字节码是 java 为了保证依次运行,可以跨平台使用的一种虚拟指令格式,在字节码文件之上的是ASM,只是一种直接操作字节码的框架,应用 ASM 需要对 Java 字节码、 class 结构比较熟悉位于 ASM 上面的是 Cglib、groovy、beanshell,后来那个种并不是 Java 体系中的内容是脚本语言,他们通过 ASM 框架生成字节码变相执行 Java 代码,在JVM中程序执行不一定非要写 java 代码,只要能生成 java 字节码, JVM
并不关系字节码的来源位于 cglib、groovy、beanshell之上的就是 hibernate 和 Spring AOP,最上面的是applications。
2.3 Cglib 和 jdk 动态代理的区别
- Jdk动态代理:利用拦截器(必须实现 InvocationHandler )加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
- Cglib动态代理:利用
ASM
框架,对代理对象类生成的 class 文件加载进来,通过修改其字节码生成子类来处理
什么时候用 cglib 什么时候用 JDK 动态代理?
- 目标对象生成了接口 默认用 JDK 动态代理
- 如果目标对象使用了接口,可以强制使用 cglib
- 如果目标对象没有实现接口,必须采用 cglib 库, Spring 会自动在 JDK 动态代理和 cglib 之间转换
JDK 动态代理和 cglib 字节码生成的区别?
- JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。
- Cglib 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要设置为 final ,对于 final 类或方法,是无法继承的
Cglib 比 JDK 快?
- cglib 底层是 ASM 字节码生成框架,但是字节码技术生成代理类,在 JDK1.6 之前比使用 java 反射的效率要高
- 在 JDK1.6 之后逐步对 JDK 动态代理进行了优化,在调用次数比较少时效率高于 cglib 代理效率
- 只有在大量调用的时候 cglib 的效率高,但是在 JDK8 的时候JDK的效率已高于 cglib
- Cglib 不能对声明 final 的方法进行代理,因为 cglib 是动态生成代理对象,final 关键字修饰的类不可变只能被引用不能被修改
Spring 如何选择是用 JDK 还是 cglib?
- 当 bean 实现接口时,会用 JDK 代理模式
- 当 bean 没有实现接口,用 cglib实现
- 可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class="true"/>)
2.4 Lombok原理
Lombok 属于 Java 的一个热门代码生成工具类,使用它可以自动生成 Setter、Getter、toString、equals 和 hashCode 等等方法。
编译源文件,然后反编译class文件,反编译结果如下图。说明@Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。可以看出 Lombok 是在编译期就为我们生成了对应的字节码。Lombok 是基于 Java 1.6 实现的 JSR 269: Pluggable Annotation Processing API 来实现的,也就是通过编译期自定义注解处理器来实现的,它的执行步骤如下:
三、spring AOP的配置使用
AOP是一个标准规范,而为了实现这个标准规范,有几种方式:
- 基于代理的AOP
- @AspectJ注解驱动的切面
- 纯POJO切面
- 注入式AspectJ切面
这四种方式都是实现aop的方法,这里讲一下通过AspectJ提供的注解实现AOP,但在spring官网中,有AspectJ 的概念,主要是因为在spring2.x的时候,spring aop的语法过于复杂,spring想进行改进,而改进的时候就借助了AspectJ 的语法、编程风格来完场aop的配置功能,这里使用AspectJ 注解方式来实现。
增加切面类
使用@Aspect注解声明一个切面,并使用@Before、@After等注解表明连接点
测试运行类
直接运行测试类,可以看到对方法进行了增强
更多实战code请参考: SpringPrinciple: SpringPrinciple - Gitee.com
三、spring中AOP实现
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging, tracing, profiling and monitoring 记录跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务