一起养成写作习惯!这是我参与「掘金日新计划 · 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的步骤
- 引入依赖
<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>
- 编写被增强的类
/**
* 被增强的类(不改源代码的类)
*/
@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扫描到
- 编写增强的类
/**
* 增强的类
*/
@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(..)
- 通知类型总共有五个,分别为:前置通知、后置通知、环绕通知、异常通知、最终通知(后文细讲)
- 在传参时若参数类型或个数不匹配,程序不报错,但通知不会执行
- 配置Spring组件扫描及开启Aspect生成代理对象
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.hqz"/>
<!-- 开启Aspect生成代理对象 -->
<aop:aspectj-autoproxy />
- 编写测试类
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"));
}
}
- 运行结果
- 项目结构
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>
参考资料: