Spring - 4. AOP
AOP 面向切面编程
面向切面式编程, 会横切程序中的流程, 在不破坏原有代码结构的情况下, 将相同的业务逻辑(比如 Transaction, Security, Logging)应用到"切面的位置".
AOP 定义
-
ADVICE, 定义 what and when 插入, Spring 提供以下时机:
- Before, ADVICE 方法之前调用
- After, ADVICE 方法之后调用, 不管成功或者异常
- After-returning, 只在方法成功时调用
- After-throwing, 只在方法抛出异常时调用
- Around, 前后都会调用
-
JOIN POINTS, 定义插入点
-
POINTCUTS, 定义哪里插入, 匹配一个或者多个 JOIN POINT
-
ASPECTS, 是 ADVICE 和 POINTCUT 的结合
-
INTRODUCTIONS, 允许往已经存在的类中添加方法和属性
-
WEAVING, 通过创建一个代理创新来将切面应用到目标对象. WEAVING 一般有三个时机:
- 编译时: 切面在目标单位编译时应用,需要特殊的编译器支持, 如 AspectJ 的 weaving compiler.
- 类加载时: 当目标对象字节码被加载到 JVM 时应用, AspectJ 5的 load-time weaving(LTW).
- 运行时: 应用程序运行时动态创建代理, Spring AOP采用的方式
Spring AOP 支持
Spring 对 AOP 的支持特征体现在:
- 基于代理对象的经典 Spring AOP
- Pure-POJO 切面
- @AspectJ 注解驱动的切面
- 支持使用 AspectJ 切面(Spring 自身实现的 AOP 不支持构造函数和 Setter 切面)
前三种都是基于 Spring 所实现的 AOP. Spring AOP 只支持方法级别.
通过 pointcuts 选择 join points
定义 pointcuts
假如有一个接口:
package concert;
public interface Performance {
public void perform();
}
那么如何才能在 perorm() 方法定义一个切入点呢?
executioin(* concert.Performance.perform(..))
其中:
-
execution
表明这是在目标 方法(Spring AOP 是围绕方法的) 运行时触发 -
* concert.Performance.perform(..)
标明方法*
代表返回值为任意返回值concert.Performance
, 代表方法所在的类perform
是被触发方法的名称(..)
代表参数,..
代表任意参数
可以用 within()
属性限制 pointcut 的范围:
executioin(* concert.Performance.perform(..) && within(concert.*)
within
代表 concert 包及其子包下的任何方法执行.
从 pointcuts 中选择 bean
execution(* concert.Performance.perform()) and bean('woodstock')
只有 ID 为woodstock 的 bean 才需要插入切入点.
也可以用 !bean('woodstock')
来指定除了 ID 为 'woodstock'的 Bean 以外, 都需要插入切入点
创建切面
直接写明切入点
@Before("execution(** concert.Performance.perform(..))")
public void takeSeats() {
System.out.println("Taking seats");
}
引用切入点
先定义 pointcut
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
再在切面中引用 pointcut
@Before("performance()")
public void takeSeats() {
System.out.println("Taking seats");
}
启用切面
启用切面需要在 @Configuration
标注的类名上使用 @EnableAspectJAutoProxy
.
或者是通过 XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="concert" />
<aop:aspectj-autoproxy />
<bean class="concert.Audience" />
</beans>
@Around
的用法
@Around
相当于做一个封装.
@Aspect
public class Audience {
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
参数 @args()
@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)")
public void trackPlayed(int trackNumber) {}
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
playTrack(int)
代表接受一个int
类型的参数.args(trackNumber)
切点中playTrack(int)
的参数别名(未必与代码中参数名一样)叫trackNumber
. 而名字trackNumber
也会作为 Advice 的参数.