【Spring实战——面向切面的Spring】1.5 使用注解创建切面

 各位小猿,程序员小猿开发笔记,希望大家共同进步。
​ 使用注解来创建切面是AspectJ 5所引入的关键特性。AspectJ 5之前, 编写AspectJ切面需要学习一种Java语言的扩展,但是AspectJ面向注解 的模型可以非常简便地通过少量注解把任意类转变为切面。

​ 我们已经定义了Performance接口,它是切面中切点的目标对象。 现在,让我们使用AspecJ注解来定义切面。

1.5.1 定义切面

流程图:

​ 如果一场演出没有观众的话,那不能称之为演出。对不对?从演出的 角度来看,观众是非常重要的,但是对演出本身的功能来讲,它并不 是核心,这是一个单独的关注点。因此,将观众定义为一个切面,并将其应用到演出上就是较为明智的做法。

程序清单4. 1展现了Audience类,它定义了我们所需的一个切面。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
public class Audience {
    @Before("execution(**concert.Performance.perform(..))")
    public void silenceCellPhones() {
        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("CLAP CLAP CLAP!!!");
    }
    }

程序清单4.1 Audience类:观看演出的切面

1. @AspectJ注解的作用是什么?

​ Audience类使用@AspectJ注解进行了标注。

该注解表明Audience不仅仅是一个POJO,还是一个切面。
Audience类中的方法都使用注解来定义切面的具体行为。

​ 可以看到,这些方法都使用了通知注解来表明它们应该在什么时候调用。AspectJ提供了五个注解来定义通知,如表4.2所示。

4.2 Spring使用AspectJ注解来声明通知方法

​ Audience使用到了前面五个注解中的三个。

​ takeSeats ()和 silence CellPhones ()方法都用到了@Before注解,表明它们应 该在演出开始之前调用。

​ applause ()方法使用了@AfterReturning注解,它会在演出成功返回后调用。

​ demandRefund()方法上添加了@AfterThrowing注解,这表 明它会在抛出异常以后执行。

暂时无法在飞书文档外展示此内容

2.我们这时候会发现,这些注解都有一个切点表达式作为值?

​ 所有的这些注解都给定了一个切点表达式作为它的值,同时,这四个方法的切点表达式都是相同的。反之,它们可以设置成不同的切点表达式,但是在这里,这个切点表达式就能满足所有通知方法的需求。

3.切点表达式用了四次,存在一定的问题?

​ 触发时间:在Performance的perform()方法执行时触发。

​ 缺点:在任务执行前后,触发了四次,虽然能达到预期功能,但是给人怪怪的感觉。是不是可以定义一次呢,在每次需要的时候引用它,是一个很好的方案。

​ 这时候,我们可以使用@PointCut注解,在@Aspect内定义可重用的切点。

package com.spring.cut;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
public class Audience {
    /**
     * 定义命名的切点
     */
    @Pointcut("execution(**concert.Performance.perform(..))")
    public void performace() {
    }

    /**
     * 表演之前
     */
    @Before("performace() ")
    public void silenceCellPhones() {
        System.out.println("Silencing cell phones");
    }

    @Before("performace() ")
    public void takeSeats() {
        System.out.println("Taking seats");
    }

    /**
     * 表演之后
     */
    @AfterReturning("performace() ")
    public void applause() {
        System.out.println("CLAP CLAP CLAP!!!");
    }

    /**
     * 表演失败之后
     */
    @AfterThrowing("performace() ")
    public void demandRefund() {
        System.out.println("CLAP CLAP CLAP!!!");
    }
    }

​ 程序清单4.2 通过@Pointcut注解声明频繁使用的切点表达式

4. 使用@Pointcut注解代替切面表达式,应该怎么做?

​ 1.在Audience类中,我们使用了@Pointcut注解来定义performance()方法。

​ 2.@Pointcut注解接受一个切点表达式作为参数,就像我们之前在通知注解中所使用的那样。

5.这样做的意义在哪里?

​ 通过在performance()方法上添加@Pointcut注解,我们扩展了切点表达式语言的能力,这样我们可以在任何需要使用performance()的地方使用它,而不必再使用更长的切点表达式。我们将所有通知注解中的长表达式都替换成了performance()。

​ 实际上,performance()方法的具体内容并不重要,在这里它是空的。该方法只是一个标识,供@Pointcut注解使用。

6.Audience的意义何在?

​ Audience只是一个通过注解表明将用作切面的Java类。

​ 像其他的Java类一样,它可以装配为Spring中的bean:

@Bean
public Audience. audience(){
 return new Audience();
}
7.Audience自动代理会怎么样?

​ 如果你就此止步的话,Audience只会是Spring容器中的一个bean。 即便使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不会创建将其转换为切面的代理。

8.如何启动自动代理功能?

​ 如果你使用JavaConfig的话,可以在配置类的类级别上通过使用EnableAspectJ-AutoProxy注解启用自动代理功能。

​ 程序清单 4.3展现了如何在JavaConfig中启用自动代理。

@Configuration
@EnableAspectJAutoproxy//启用AspectJ自动代理
@ComponentScan
public class ConcertConfig{
   @Bean//声明Audience bean
   public Audience audience(){
      return new Audience();
   }
}

程序清单4.3JavaConfig中启用AspectJ注解的自动代理

​ 假如你在Spring中要使用XML来装配bean的话,那么需要使用Spring aop命名空间中的aop:aspectj-autoproxy元素。下面的XML 配置展现了如何完成该功能。

程序清单4.4XML中,通过Springaop命名空间启用AspectJ自 动代理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/c" 
       xmlns:aop="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

   <context:component-scan base-package="concert"/>

    <aop:aspectj-autoproxy/>

    <!--声明Audience bean-->
    <bean class="com.spring.cut.Audience"/>

</beans>     

​ 不管你是使用JavaConfig还是XML,AspectJ自动代理都会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面 的切点所匹配的bean。在这种情况下,将会为Concertbean创建一个 代理,Audience类中的通知方法将会在perform()调用前后执行。

9.Spring的Aspect自动代理特性?

​ Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导,切面依然是基于代理的。

​ 在本质上,它依然是 Spring基于代理的切面。换句话说虽然使用的 是@AspectJ注解,仍然限于代理方法的调用。

10.如何充分利用Aspect的能力?

​ 如果想利用 AspectJ的所有能力,我们必须在运行时使用AspectJ并且不依赖Spring 来创建基于代理的切面。

​ 到现在为止,我们的切面在定义时,使用了不同的通知方法来实现前 置通知和后置通知。但是表4.2还提到了另外的一种通知:环绕通知 (around advice ) 。环绕通知与其他类型的通知有所不同,因此值得 花点时间来介绍如何进行编写。

1.5.2 创建环绕通知

​ 环绕通知是最为强大的通知类型。它能够让你所编写的逻辑将被通知Aui的目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。

​ 为了阐述环绕通知,我们重写Audience切面。这次,我们使用一个 环绕通知来代替之前多个不同的前置通知和后置通知。

程序清单4.5 使用环绕通知重新实现Audience切面

package com.spring.cut;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Audience {
    /**
     * 定义命名的切点
     */
    @Pointcut("execution(**concert.Performance.perform(..))")
    public void performace(){}

    /**
     * 环绕通知方法
     */
    @Around("performace()")
    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");
        }
    }
}

1.@ around注解在上述方法的意义何在?代表了什么? 

​ 在这里,@Around注解表明watchPerformance ()方法会作为performance ()切点的环绕通知。

2.在这程序中,执行流程是怎么样的?

暂时无法在飞书文档外展示此内容

​ 在这个通知中,观众在演出之 前会将手机调至静音并就坐,演出结束后会鼓掌喝彩。像前面一样, 如果演出失败的话,观众会要求退款。

3.通知的执行效果与前置通知和后置通知效果怎么样?

​ 这个通知所达到的效果与之前的前置通知和后置通知是一样的。但是,现在它们位于同一个方法中,不像之前那样分散在四个不同的通知方法里面。

4.关于这个新的通知方法,参数是怎么样的?

​ 这个通知方法,它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。

5.通知方法中控制权转移?

​ 通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法。

6.为什么需要调用proceed()方法?

​ 需要注意的是,别忘记调用proceed()方法。如果不调这个方法的 话,那么你的通知实际上会阻塞对被通知方法的调用。有可能这就是你想要的效果,但更多的情况是你希望在某个点上执行被通知的方 法。

7.如果不调用proceed方法会怎么样?

​ 有意思的是,你可以不调用proceed()方法,从而阻塞对被通知方法的访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝试。
 欢迎程序员关注程序员小猿小胡工作之家,大家一起交流进步。

本文由博客一文多发平台 OpenWrite 发布!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值