Spring核心技术之AOP

126 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

先讲讲怎么用

例子:有这样一个功能,可以计算买了五斤单价为十元的水果,总共需要多少钱。入参为m斤水果,n元/斤。出参为总价k。函数如下:

public class Calculation {

    /**
     * 计算水果总价
     * @param j 斤数
     * @param unitPrice 单价
     * @return 总价
     */
    public BigDecimal cal(BigDecimal j, BigDecimal unitPrice) {
        return j.multiply(unitPrice);
    }
}

由于这个函数是第三方提供的,封装成了一个jar包,无法修改源代码。现在想要在计算之前打印出斤数和单价,这个时候就可以使用到Spring 提供的AOP方式了。

使用Spring AOP的步骤

  1. 引入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.18</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.18</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.18</version>
</dependency>
  1. 编写被增强的类
/**
 * 被增强的类(不改源代码的类)
 */
@Component
public class Calculation {

    /**
     * 计算水果总价
     * @param j 斤数
     * @param unitPrice 单价
     */
    public void calc(BigDecimal j, BigDecimal unitPrice) {
        BigDecimal total = j.multiply(unitPrice);
        System.out.println(total);
    }
}
  • 被增强的类需要加注解@Component,以便能被Spring IOC扫描到
  1. 编写增强的类
/**
 * 增强的类
 */
@Component
@Aspect
public class CalculationLog {
    //前置通知(带参数)
    @Before(value = "execution(* com.hqz.Calculation.calc(java.math.BigDecimal, java.math.BigDecimal)) && args(j, unitPrice)")
    public void before(BigDecimal j, BigDecimal unitPrice) {
        System.out.println("计算之前:");
        System.out.println("斤数:" + j);
        System.out.println("单价:" + unitPrice);
        System.out.println();
    }

    //后置通知(不带参数)
    @AfterReturning(value = "execution(* com.hqz.Calculation.calc(..))")
    public void afterReturning() {
        System.out.println();
        System.out.println("计算之后:");
    }
}
  • 若不考虑返回值,则execution里使用*号标记
  • 若不需传参,则形参用两个英文状态下的.标记,如calc(..)
  • 通知类型总共有五个,分别为:前置通知、后置通知、环绕通知、异常通知、最终通知(后文细讲)
  • 在传参时若参数类型或个数不匹配,程序不报错,但通知不会执行
  1. 配置Spring组件扫描及开启Aspect生成代理对象
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.hqz"/>

<!-- 开启Aspect生成代理对象 -->
<aop:aspectj-autoproxy />
  1. 编写测试类
public class CalculationTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        Calculation calculation = applicationContext.getBean("calculation", Calculation.class);
        calculation.calc(new BigDecimal("5"), new BigDecimal("10"));
    }
}
  1. 运行结果

image-20220406101929752

  1. 项目结构

image-20220406102002414

AOP术语

AOP 领域中的特性术语:

  • 通知(Advice):实际增强的逻辑部分称为通知(增强)。AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。通知有五种类型(前置、后置、环绕、异常、最终)
  • 连接点(join point): 类里面哪些方法可以被增强,这些方法称为连接点。在 Spring AOP 中,连接点总是方法的调用。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 切面(Aspect): 切面是通知和切点的结合。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

AOP精讲

AOP的切点指示器

AspectJ指示器描述
arg()限定连接点匹配参数为指定类型的执行方法。
@args()限定连接点匹配参数由指定注解标注的执行方法
execution()用于匹配连接点执行的方法
this()限定连接点匹配AOP代理的类型bean引用为指定类型的类
target()限定连接点匹配目标对象为指定类型的类
@target()限定连接点匹配特定的执行对象,这些对象对应的类要有指定类型的注解
within()限定匹配连接点指定的类型。只拦截确定的类型,也就是跟它的接口无关,定义什么类型就拦截什么类型
@within()限定匹配连接点指定注解所标注的类型(当使用Spring AOP时,方法定义在指定的注解所标注的类里)
@annotation限定匹配带有特定注解的连接点

抽取相同的切入点

  • 使用注解@Pointcut
  • 抽取相同切入点的方法名可以随便取
//相同切入点抽取
@Pointcut(value = "execution(* com.hqz.aop.User.add(..))")
public void pointcutDemo() {

}

//前置通知
@Before(value = "pointcutDemo()")
public void before() {
    System.out.println("before .....");
}

设定优先级

若有多个增强类对同一个方法进行增强,则使用@Order注解来确定优先级。数字越小,优先级越高。

@Component
@Aspect
//设定优先级
@Order(2)
public class CalculationLog {
    @Before(value = "execution(* com.hqz.Calculation.calc(..))")
    public void before(BigDecimal j, BigDecimal unitPrice) {
        System.out.println("增强类1");
    }
}

使用配置文件进行配置

<bean id="book" class="com.hqz.aopxml.Book"/>
<bean id="bookProxy" class="com.hqz.aopxml.BookProxy"/>

<!-- 配置aop增强 -->
<aop:config>
    <!-- 切入点 -->
    <aop:pointcut id="p" expression="execution(* com.hqz.aopxml.Book.buy(..))"/>

    <!-- 配置切面 -->
    <aop:aspect ref="bookProxy">
        <!-- 增强作用在具体的方法上 -->
        <aop:before method="before" pointcut-ref="p"/>
    </aop:aspect>
</aop:config>

参考资料:

Spring AOP 切点指示器 - 简书 (jianshu.com)

Spring AOP——Spring 中面向切面编程 - SharpCJ - 博客园 (cnblogs.com)