第四章 面向切面的编程

完整代码请见:https://github.com/codercuixin/SpringInAction
一 定义AOP术语
描述切面的常用术语有通知(advice),切点(pointcut)和连接(join point).
AOP中的行话
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切点指示器
Spring AspectJ 图1
Spring AspectJ图2

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");
    }
}

execution切点的解释
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实现切面,与上面注解的方式大同小异,请自行看源码。

spring-aop-xml
spring-aop-xml2
spring-aop-xml3

第四部分 直接使用更为强大的AspectJ编写切面

//TODO 这部分写的有BUG,还没有调通。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值