Spring AOP
文章目录
- Spring AOP
-
- 1.概念
- 2. Spring AOP的特性
- 3. 定义切面与切点
- 4.切点表达式
-
- 1.Spring支持的PCD
-
- 1.`execution`: 用于匹配联结点(即方法的执行),SpringAOP最主要的用法。
- 2.`within`: 匹配目标类。限制联结点必须出现在某些特定的类中,这些类中所有的方法均会成为联结点。
- 3.`this`: 通过代理类的类型来匹配。如果某个Bean的代理类属于该类型,则拦截该Bean的方法。
- 4.`target`: 通过目标类的类型来匹配。如果某个Bean(即被代理的类)属于该类型,则拦截该Bean的方法。
- 5.`args`: 通过联结点的形参来匹配。
- 6.`@annotation`: 通过联结点是否被相应的注解所修饰来进行匹配。
- 7.`@within`: 指定一个作用于类型的注解,通过目标对象的类型是否属于被该注解修饰的(子)类型来进行匹配。
- 8.`@target`: 指定一个作用于类型的注解,通过目标对象是否被该注解所修饰来进行匹配。
- 9.`@args`: 指定一个作用于类型的注解,通过传入联结点的实参的运行时类型是否被该注解所修饰来进行匹配。
- 10. Spring还支持一个额外的PCD:
- 5.增强
- 6.增强方法的执行顺序
- 7.引入
1.概念
AOP(Aspect Oriented Programming) ,直译过来即为面向切面编程。AOP 是一种编程思想,是面向对象编程OOP的一种补充,提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中,我们以类(class)作为我们的基本单元,而 AOP 中的基本单元是切面(Aspect)。好比下图,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。
1.面向切面编程
在面向切面编程的思想里面,可以把功能分为两种:
- 核心业务:登陆、注册、增、删、改、查等,都叫核心业务
- 周边功能:日志、事务管理等为周边业务
在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的。然后把切面功能和核心业务功能 “编织”在一起,便谓之AOP。
2.AOP的目的
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。即,AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
在技术上,AOP可以在程序运行期间,在不修改源码的情况下对方法进行功能增强,如此一来,不难理解 AOP 其实就是代理模式的典型应用。
3.AOP实现的分类
按照 AOP 修改源代码的时机,可以将其分为两类:
- 静态 AOP 实现:AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器)。例如 AspectJ。
- 动态 AOP 实现: AOP 框架在运行阶段动态生成代理对象(在内存中以 JDK 或 CGlib 动态代理动态地生成 AOP 代理类)。如 SpringAOP。
SpringAOP:
- JDK 代理:基于接口的动态代理技术。
- cglib 代理 :基于父类的动态代理技术。
常用AOP实现比较:
类别 | 机制 | 原理 | 优点 | 缺点 |
---|---|---|---|---|
静态AOP | 静态织入 | 在编译期,切面直接以字节码的形式编译到目标字节码文件中 | 对系统无性能影响 | 灵活性不足 |
动态AOP | JDK动态代理 | 在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中 | 相对于静态AOP更加灵活 | 1) 切入的关注点需要实现接口; 2) 对系统有一点性能损耗 |
动态字节码生成 | CGLIB | 在运行期,目标类加载后,动态生成目标类的子类,将切面逻辑加入到子类中 | 没有接口也可以织入 | 扩展类的实例方法用final修饰时,则无法进行织入 |
自定义类加载器 | 在运行期,目标类加载前,将切面逻辑加到目标字节码里 | 可以对绝大部分类进行织入 | 代码中如果使用了其他类加载器,则这些类将不会织入 | |
字节码转换 | 在运行期,所有类加载器加载字节码前进行拦截 | 可以对所有类进行织入 |
4.AOP 术语
Unfortunately, AOP terminology is not particularly intuitive.
谓词(predicate):在计算机语言的环境下,谓词是指条件表达式的求值返回真或假的过程。
术语 | 中文 | 含义 |
---|---|---|
Aspect | 切面 | 横跨多个类的某个功能(如事务管理)。从概念上,它是切点和增强的结合。 |
Join point | 联结点 | 程序执行过程中的一个时机(点)。例如方法的调用、异常的抛出。 在 Spring AOP 中,联结点总是方法的调用。 |
Advice | 增强 | 在某个联结点上,某个切面执行的具体动作(这里对advice译为增强,为意译)。 很多AOP框架(包括Spring)都把增强建模为一个拦截器,并且相应地围绕联结点维护了一个拦截器链。 |
Pointcut | 切点 | 一个用来匹配联结点的谓词。 Advice is associated with a pointcut expression and runs at any join point matched by the pointcut. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching. |
Introduction | 引入 | 引入是为一个类声明额外的方法或字段。 Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object. |
Target object | 目标对象 | 要被切面增强的(原始)对象,也常称作advised object(增强对象,这种称呼并不好,有歧义)。 由于Spring是基于运行时代理的机制实现AOP的,因此目标对象总是一个proxied object(被代理的对象)。 |
AOP Proxy | AOP代理 | AOP为实现切面功能而创建的对象,故名之代理对象。 在Spring中,代理对象总是一个JDK代理或CGLIB代理对象。 |
Weaving | 织入 | 描述了把增强处理添加到目标对象、并创建一个被增强的对象(代理)这一过程,不对应一份实体。 1) 织入可以发生在编译时、加载时、运行时 2) SpringAOP的织入总是发生在运行时。 |
增强的类型:
- 前置增强
- 返回后增强
- 异常抛出增强
- 后置增强
- 环绕增强
2. Spring AOP的特性
1.能力与目标
Spring AOP does not need to control the class loader hierarchy and is thus suitable for use in a servlet container or application server.
Spring AOP当前只支持对Spring Bean的方法作为联结点,不支持对字段的拦截(尽管对字段拦截的支持不需要破坏Spring AOP的核心API)。如果希望拦截对字段的访问与更新,建议直接使用AspectJ。
SpringAOP使用时需要结合SpringIoC容器,因此SpringAOP无法对非常细粒度的对象进行增强,典型的例子就是domain objects,对于这些场景,选择AspectJ吧。
2.AOP机制
AOP技术在Spring中实现的内容:Spring框架监控切点方法的执行,一旦监控到切入点方法被运行,即使用动态代理机制,动态创建目标对象的代理对象,根据增强类别在代理对象的相应位置将Advice对应的功能织入,从而完成增强后的整个代码逻辑的执行。
Spring 的 AOP 实现底层就是对 JDK 代理、cglib 代理的方式进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
- JDK 代理:基于接口的动态代理技术。
- cglib 代理 :基于父类的动态代理技术。
默认情况下,Spring 会根据目标类是否实现了接口来决定采用哪种动态代理的方式。如果一个对象没有实现任何接口,则会使用CGLIB代理,否则使用JDK代理。
-
当使用JDK代理的时候,所有该目标对象实现的接口都会被代理;
-
如果需要,也可以强制使用CGLIB代理,方法是设置
proxy-target-class
为true
:<aop:aspectj-autoproxy proxy-target-class="true"/>copyerrorcopied
Spring使用CGLIB代理时需注意如下事项:
-
final
方法无法被增强,因为它们无法被在运行时生成的子类所覆盖; -
正常情况下,CGLIB代理是通过Objenesis创建的,但当JVM不允许绕过构造函数时,SpringAOP会对构造器进行双重调用来达成目的,此时Spring会记录相应的debug日志信息。
Objenesis是一个轻量的Java库,作用是绕过构造器创建实例。
1.理解SpringAOP的代理
Spring AOP是基于代理的,牢记这一点很重要,这是本质特征!
假定有一个纯天然的POJO类:
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
对于POJO类的实例pojo,调用pojo的方法时毫无疑问会直接调用该对象的相应方法:
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}
然而,如果pojo引用的是代理类的代理对象时,调用方式会发生改变:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
However, once the call has finally reached the target object (the SimplePojo
reference in this case), any method calls that it may make on itself.
- Such as
this.bar()
orthis.foo()
, are going to be invoked against thethis
reference, and not the proxy. - It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to run.
综上,最好的方式永远是代码中不要出现有自调用的情况。其次,如果真的迫不得已,可以通过在代码中使用Spring提供的一些API来解决,如下:
这种方案首先使得代码与Spring发生了强耦合,其次使得这个类本身知道了自己即将被代理,后者与AOP的理念背道而驰了。
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);