面向切面的Spring
DI有助于应用对象之间的解耦,而AOP可以实现横切关注点与他们所影响的对象之间的解耦。
横切关注点:在软件开发中,散布于应用中多处的功能。通常来讲,这些横切关注点从概念上与应用的业务逻辑相分离,但是往往会直接嵌入到应用的业务逻辑中。
把这些横切关注点与业务逻辑相分离正是面向切面编程所要解决的问题
4.1 什么是面向切面编程
重用通用功能最常用的面向对象的技术:
+ 继承(inheritance):会导致一个脆弱的对象体系
+ 委托(delegation):需要对委托对象进行复杂的调用
横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)
优点:
+ 每个关注点都集中于一个地方,而不是分散到多处代码中
+ 服务模块更简洁
4.1.1 术语
通知
切面的工作被称为通知,通知定义了切面是什么及何时使用
5种类型的通知:
+ 前置通知
+ 后置通知
+ 返回通知
+ 异常通知
+ 环绕通知
连接点
在应用执行过程中能够插入切面的一个点
切点
一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围
通知定义的是“什么”和“何时”,切点定义的是“何处”
切点的定义会匹配通知所要织入的一个或多个连接点
切面
切面是通知和切点的结合
引入
引入允许我们向现有的类添加新方法或属性
织入
织入是把切面应用到目标对象并创建新的代理对象的过程。
切面在指定的连接点被织入到目标对象中
织入的时机:
+ 编译期()
+ 类加载期(AspectJ5的加载时织入)
+ 运行期(Spring AOP使用此种方式)
4.1.2 Spring对AOP的支持
Spring提供了4中对AOP的支持:
+ 基于代理的经典AOP(过时,不再介绍)
+ 纯POJO切面(借助Spring的aop命名空间,需要显式xml配置)
+ @AspectJ注解驱动的切面()
+ 注入式AspectJ切面(AOP需求超过了简单的方法调用,如构造器或属性拦截)
Spring对AOP的支持局限于方法拦截
4.2 通过切点来选择连接点
在Spring AOP中,使用AspectJ的切点表达式语言来定义切点
4.2.1 编写切点
execution(* concert.Performance.perform(..))
这个表达式能够设置当perform()方法调用时触发通知的调用
execution 在方法执行时触发
* 返回任意类型
concert.Performance 方法所属的类
perform 方法名
.. 使用任意参数
使用within()指示器来限制匹配
execution(* concert.Performance.perform(..)) && within( concert.*)
切点仅匹配 concert包
4.2.2 切点中选择bean
使用bean()指示器
execution(* concert.Performance.perform()) && bean( 'woodstock')
4.3 使用注解创建切面
package concert;
import *;
@Aspect
public class Audience{
@Before("execution(** concert.Performance.perform(..))")
public void silenceCellPhone(){
System.out.println("Silencing cell phones");
}
@Before("execution(** concert.Performance.perform(..))")
public void takeSeats(){
System.out.println("Taking seats");
}
@AfterReturning("execution(** concert.Performance.perform(..))")
public void applause(){
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("execution(** concert.Performance.perform(..))")
public void demandRefund(){
System.out.println("Demanding a refund");
}
}
注解 | 通知 |
---|---|
@After | 通知方法会在目标方法返回或抛出异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会在目标方法封装起来 |
@Before | 通知方法会在目标方法调用之前执行 |
@Pointcut注解能够在一个@AspectJ切面内定义可重用的切点
package concert;
import *;
@Aspect
public class Audience{
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance(){}
@Before("performance()")
public void silenceCellPhone(){
System.out.println("Silencing cell phones");
}
@Before("performance()")
public void takeSeats(){
System.out.println("Taking seats");
}
@AfterReturning("performance()")
public void applause(){
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("performance()")
public void demandRefund(){
System.out.println("Demanding a refund");
}
}
至此,Audience只是Spring容器的一个bean。即使使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不会创建将其转换为切面的代理。
JavaConfig配置方法:
package concert;
import *;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig{
@Bean
public Audience audience(){
return new Audience();
}
}
xml配置:
<aop:aspectj-autoproxy />
<bean class="concert.Audience" />
4.3.2 创建环绕通知
环绕通知是最强大的通知类型。它能够让你所编写的逻辑将被通知的目标方法完全包装起来
package concert;
import *;
@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 (Exception e) {
System.out.println("Demanding a refund");
}
}
}
4.3.3 处理通知中的参数
限定符args(name)
package soundsystem;
import *;
@Aspect
public class TrackCounter{
private Map<Integer,Integer> trackCounts = new HashMap<Integer,Integer>();
@Pointcut("execution(* soundsystem.CompactDisc.playTrack(int))&& args(trackNumber)")
public void trackPlayed(int trackNumber){}
@Before("trackPlayed(int trackNumber)")
public void countTrack(int trackNumber){
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber,currentCount+1);
}
public int getPlayCount(int trackNumber){
return trackCounts.containsKey(trackNumber)
? trackCounts.get(trackNumber) : 0 ;
}
}
4.4 在XML中声明切面
sop命名空间:
AOP配置元素 | 用途 |
---|---|
定义AOP通知器 | |
定义AOP后置通知 | |
定义AOP返回通知 | |
定义AOP异常通知 | |
定义AOP环绕通知 | |
定义一个切面 | |
启用@AspectJ注解驱动的切面 | |
定义AOP前置通知 | |
顶层的AOP配置元素 | |
以透明的方式为被通知的对象引入额外的接口 | |
定义一个切点 |
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(** concert.Performance.perform(..))" />
<aop:before
point-ref="performance"
method="silenceCellPhones" />
<aop:before
point-ref="performance"
method="takeSeats" />
<aop:after-returning
point-ref="performance"
method="applause" />
<aop:after-throwing
point-ref="performance"
method="demandRefund" />
</aop:aspect>
</aop:config>
环绕通知:
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(** concert.Performance.perform(..))" />
<aop:around
point-ref="performance"
method="watchPerformance" />
</aop:aspect>
</aop:config>