完整代码请见:https://github.com/codercuixin/SpringInAction
一 定义AOP术语
描述切面的常用术语有通知(advice),切点(pointcut)和连接(join point).
1.1 通知(实际上就是在目标方法执行之前或者之后干点其他事情)
通知用来描述切面完成的工作是什么,并且说明了在什么时候去完成。
Spring切面的五种通知类型
前置通知(Before):在目标方法被调用之前执行调用通知功能
后置通知(After):在目标方法被调用之后执行调用通知功能,此时不关心目标方法返回什么(是执行成功,还是抛出异常)
返回通知(After-returning):在目标方法被成功调用之后,执行调用通知功能。
异常通知(After-throwing):在目标方法调用抛出异常之后,调用通知。
环绕通知(Around):在目标方法执行之前与之后,执行自定义的行为,等价于Before+After
1.2 连接点
连接点是我们应用通知的地方。
1.3 切点
切点会缩小通知所应用的连接点的范围。
1.4 切面
切面 = 通知(何时要做什么)+切点 (在哪里做)
电表采集的师傅,在每月月底要出电费单之前去查表(通知,何时做什么),他会到你家电表箱前查表(连接点, 何处应用通知),他只负责你们家那一片小区(切点,限定通知应用的连接点),查完你们家那一片小区他就没事了(切面)。
1.5 引入
引入允许我们向现有的类添加新方法或属性。
1.6 织入(weaving)
织入是将切面应用到目标对象并创建新的代理对象的过程。
在目标对象生命周期有多个点可以进行织入:
编译器:切面在目标类编译时被织入。例如AspectJ的编译织入器。
类加载器:切面在目标类加载到jvm时被织入。(需要可以增强目标类字节码的类加载器来完成,比如AspectJ5的loading-time-weaving, LWT).
运行期:切面在应用程序运行的某个时刻被织入。一般情况下,在织入切面时, AOP容器会为目标对象动态地创建一个代理对象。(比如Spring AOP)
二.Spring对AOP的支持
基于代理的经典AOP (笨重复杂)
纯POJO切面(需要xml配置)
@AspectJ注解驱动的切面
注入式AspectJ切面
spring aop构建在动态代理基础之上,所以Spring对aop的拦截只限于方法拦截。如果你超过了方法拦截,比如构造器拦截,属性拦截,要使用AspectJ自己的语法来实现切面。
Spring通知使用Java编写
如果你需要更细的控制,比如构造器拦截,或者属性拦截,请使用AspectJ的语法实现
Spring在运行时通知对象
代理类封装了目标类,并拦截被通知方法的调用,再把调用方法转发给真正的目标bean
Spring只支持方法级别的连接点
二.Java注解编写切面
2.1 Spring AOP支持的AspectJ切点指示器
2.2 举个完整的栗子
2.2.1定义一个接口先
public interface Performance {
public void perform();
}
2.2.2 它的一个简单的实现,使用@Component方便自动查找为Bean
@Component
public class Dancing implements Performance {
@Override
public void perform() {
System.out.println("I just wanna dance...");
}
}
2.2.3 定义一个切面,其中@Aspect负责声明这个类是一个切面,@Before,@AfterReturning,@AfterThrowing等定义了通知类型,”execution(* concert.Performance.perform(..))”则说明了切点(在哪里应用通知)
@Aspect
public class Audience {
@Before("execution(* concert.Performance.perform(..))")
public void silenceCellPhone() {
System.out.println("Audience silence Cell Phone");
}
@Before("execution(* concert.Performance.perform(..))")
public void takingSeats() {
System.out.println("Audience taking seats");
}
@AfterReturning("execution(* concert.Performance.perform(..))")
public void applause() {
System.out.println("Audience Clap Clap Clap");
}
@AfterThrowing("execution(* concert.Performance.perform(..))")
public void demandRefund() {
System.out.println("Audience demand Refund");
}
}
2.2.4 下面是测试类,特别的这里需要使用@EnableAspectJAutoProxy来开启ASpectJ的自动代理。
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackageClasses = Performance.class)
public class ConcertConfig {
@Bean
public Audience audience()
{
return new Audience();
}
@Bean
public AudienceWithPointcut audienceWithPointcut()
{
return new AudienceWithPointcut();
}
@Bean
public AudienceWithAround audienceWithAround()
{
return new AudienceWithAround();
}
}
@ContextConfiguration(classes = ConcertConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ConcertTest {
@Autowired
private Performance performance;
@Test
public void testAspect() {
performance.perform();
}
}
2.3 使用@Pointcut定义切点
2.2 中,我们写了四遍”execution(* concert.Performance.perform(..))”来定义切点,这实在不是“懒惰”的程序员所改做的。所以我们如何只写一次呢,答案就是使用@Pointcut.
@Aspect
public class AudienceWithPointcut {
/**
* 定义切点
*/
@Pointcut("execution(* concert.Performance.perform(..))")
public void performance() {
}
/**
* 定义通知
*/
@Before("performance()")
public void silenceCellPhone() {
System.out.println("AudienceWithPointcut silence Cell Phone");
}
@Before("performance()")
public void takingSeats() {
System.out.println("AudienceWithPointcut taking seats");
}
@AfterReturning("performance()")
public void applause() {
System.out.println("AudienceWithPointcut Clap Clap Clap");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("AudienceWithPointcut demand Refund");
}
}
2.4 @Around通知的使用
为了实现一个观众观看表演会发生的行为,我们在2.3里面写了四个通知,这是不是有点多,那么如何在一个通知里搞定这些呢?答案就是@Around环绕通知
@Aspect
public class AudienceWithAround {
@Pointcut("execution(* concert.Performance.perform(..))")
public void performance(){}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinPoint){
try{
System.out.println("AudienceWithAround silence Cell Phone");
System.out.println("AudienceWithAround taking seats");
joinPoint.proceed();
System.out.println("AudienceWithAround Clap Clap Clap");
}catch (Throwable throwable) {
System.out.println("AudienceWithAround demand Refund");
}
}
}
2.5 通过注解引入新功能
@DeclareParents注解由三个部分组成
- value属性指定了哪种类型的bean要加入该bean, 在下面的例子中+表示concert.Performance的所有子类型
- defaultImpl指定了为引入功能提供实现的类
- @DeclareParents 注解所标注的静态属性指明了要引入了接口。在这里, 我们所引入的是Encoreable 接口。
@Aspect
@Component
public class EncoreableIntroducer {
@DeclareParents(value = "concert.Performance+", defaultImpl =DefaultEncoreable.class)
public static Encoreable encoreable;
}
@ContextConfiguration(classes = EncoreableConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class EncoreableTest {
@Autowired
private Performance performance;
@Test
public void perform(){
Encoreable encoreable = (Encoreable)performance;
encoreable.performEncore();
}
}
第三部分 如何xml实现切面,与上面注解的方式大同小异,请自行看源码。
第四部分 直接使用更为强大的AspectJ编写切面
//TODO 这部分写的有BUG,还没有调通。。。。