之前我们学习了DI,它有助于解耦,AOP能够实现关注业务对象和横切关注点的解耦。(横切关注点cross-cutting concern和主要的应用业务逻辑是分离的)
背景
如上图所示, 网络服务是由很多关注点组成的,和直接业务相关的关注点是直切关注点,为直接业务点提供服务的就叫横切关注点。
Why
1.解耦,各个业务的横切关注点集中在一起,不分散;
2.主要业务集中,业务模块只关注核心代码。
How
Spring提供了几种AOP支持:
1.基于代理的经典AOP(直接使用ProxyFactoryBean过于笨重繁琐,不建议)
代理模式参考
2.纯POJO切面(借助aop命名空间,使用XML配置才能用)
所以比较推荐下面两种:
3.@AspectJ注解驱动的切面
4.注入式AspectJ切面
所以下面重点学习通过AspectJ实现AOP。
AOP术语
·通知(Advice)
横切关注点想要的功能,就是上面图中的安全、事务这些功能,提前定义成通知,需要的时候用一下。
AspectJ使用通知注解表明应该在什么时候被调用。AspectJ主要有以下五种通知注解:
注解 | 通知 |
---|---|
@Before | 通知方法在目标方法调用前执行 |
@After | 通知方法在目标方法调用后执行 |
@AfterReturning | 通知方法在目标方法返回后执行 |
@AfterThrowing | 通知方法在目标方法抛出异常后执行 |
@Around | 通知方法在把目标方法封装起来 |
·切点
package concert;
public interface Performance {
public void perform();
}
最主要使用AspectJ的execution指示器来编写切点表达式:
通过AspectJ语法定义了切点,在切点处启动通知,此处的切点就是当concert包下被调用执行了perform()方法,这时候会触发通知。
切点应该是定义了启动通知的触发条件。
·连接点
切面代码利用这些连接点来插入到主要业务流程中。也就是说所有perform方法都是一个连接点。
·切面
切面是通知和切点的集合。
- 使用AspectJ注解创建切面:
// 表明是Audience不光是个POJO还是一个切面。
@Aspect
public class Audience{
// perform表演之前
@Before(“execution(**concert.Performance.perform(..))”)
public void silenceCellPhone(){sys.print("slience")}
// perform表演之前
@Before()
public void takeSeats(){sys.print("seat")}
// perform表演之后
@AfterReturning()
public void applause(){sys.print("applause")}
// perform表演失败之后
@AfterThrowing()
public void demandRefund(){sys.print("refund")}
}
其他概念
启动代理
前面虽然已经定义了切点切面,但是并不会自动启动代理,还是要显式通过JavaConfig或者XML启动自动代理。
// 声明AspectJ代理
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig{
@Bean
// 声明Audience Bean
public Audience audience(){
return new Audience();
}
}
<beans xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
……>
<context:component-scan base-package="concert"/>
//启动AspectJ自动代理
<aop:aspectj-autoproxy/>
//声明Bean
<bean class="concert.Audience"/>
·Pointcut
在上例中,同样的切点我们相当于定义了四次,如何能不重复定义切点呢?
可以使用@Pointcut注解
// 表明是Audience不光是个POJO还是一个切面。
@Aspect
public class Audience{
// 定义Pointcut,提供了一个performance标识扩展了注解表达式
@Pointcut(“execution(**concert.Performance.perform(..))”)
public void performance(){}
// 这样就可以直接用Pointcut了
@Before("performance()")
public void silenceCellPhone(){}
……
}
·环绕通知
还有一个通知没有用到就是环绕通知,该怎么用?
@Aspect
public class Audience{
// 定义Pointcut
@Pointcut(“execution(**concert.Performance.perform(..))”)
public void performance(){}
// 这样就可以直接用Pointcut了
@Around("performance()")
public void watchPerformance(ProceedingPointJoint jp){
// 把之前的4个通知方法合并在一个通知里面,接受ProceedingPointJoint 作为入参
try{
sys.print("slience");
sys.print("seat");
// 必须调用jp.proceed(),否则会被阻塞,这里相当于就执行了切面。
jp.proceed();
sys.print("applause");
}catch(Throwable e){
sys.print("refund");
}
}
}