⭐表示重要。
第一章:基于注解的 AOP 用到的技术(⭐)
- 动态代理(InvocationHandler):JDK 原生的实现方法,需要被代理的目标类必须实现接口。因为该技术要求
代理对象和目标对象实现同样的接口
。 - Cglib:通过
继承被代理的目标类
实现代理,所以不需要目标类实现接口。 - AspectJ:本质上是静态代理,
将代理逻辑织入被代理的目标类编译得到的字节码文件
,所以最终效果是动态的。weaver 就是织入器,Spring 只是借用了AspectJ中的注解。
第二章:环境搭建
- IDEA 2021+。
- JDK 11+。
Maven 3.8。
pom.xml
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.12</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
第三章:入门案例(⭐)
3.1 准备接口和实现类
- Calculator.java
package com.github.fairy.era.aop;
/**
* 计算器
*
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:50
*/
public interface Calculator {
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
}
- CalculatorImpl.java
package com.github.fairy.era.aop.impl;
import com.github.fairy.era.aop.Calculator;
import org.springframework.stereotype.Component;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:51
*/
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int sub(int a, int b) {
return a - b;
}
@Override
public int mul(int a, int b) {
return a * b;
}
@Override
public int div(int a, int b) {
return a / b;
}
}
3.2 创建 Spring 的配置文件,配置自动扫描的包和开启 AOP 的注解支持
- applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.github.fairy.era"></context:component-scan>
<!-- 开启注解的AOP支持 -->
<aop:aspectj-autoproxy/>
</beans>
3.3 创建切面类
- LogAspect.java
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
/**
* @Before注解: 声明当前方法是前置通知方法,value属性用来指定切入点表达式
*/
@Before("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void printLogBefore() {
System.out.println("[AOP前置通知] 方法开始了");
}
/**
* @AfterReturn注解: 声明当前方法是返回通知方法,value属性用来指定切入点表达式
*/
@AfterReturning("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void printLogAfterReturn() {
System.out.println("[AOP返回通知] 方法开始了");
}
/**
* @AfterThrowing: 声明当前方法是异常通知方法,value属性用来指定切入点表达式
*/
@AfterThrowing("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void printLogAfterThrowing() {
System.out.println("[AOP异常通知] 方法开始了");
}
/**
* @After:声明当前方法是最终通知方法,value属性用来指定切入点表达式
*/
@After("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void printLogAfter() {
System.out.println("[AOP最终通知] 方法开始了");
}
}
3.4 测试
package com.github.fairy.era.bean;
import com.github.fairy.era.aop.Calculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringTest {
@Autowired
private Calculator calculator;
@Test
public void test() {
int add = calculator.add(1, 2);
System.out.println("add = " + add);
}
}
3.5 通知执行顺序(了解)
- Spring 版本 5.3.x 以前:
- 前置通知。
- 目标操作。
- 后置通知。
- 返回通知或异常通知。
- Spring 版本 5.3.x 以后:
- 前置通知。
- 目标操作。
- 返回通知或异常通知。
- 后置通知。
第四章:通知的细节(⭐)
4.1 JoinPoint 接口
org.aspectj.lang.JoinPoint
。- ①
JoinPoint
接口通过getSignature()
方法获取目标方法的签名(方法声明时的完整信息)。 - ② 通过目标方法前面对象获取方法名。
③ 通过
JoinPoint
对象获取外界调用目标方法时传入的实参列表组成的数组。示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
/**
* @Before注解: 声明当前方法是前置通知方法,value属性用来指定切入点表达式
* 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
*/
@Before("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("[AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
}
4.2 方法的返回值
- 在
@AfterReturning
注解的returning
属性获取目标方法的返回值。
- 示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
/**
* @AfterReturn注解: 声明当前方法是返回通知方法,value属性用来指定切入点表达式
*/
@AfterReturning(value = "execution(public int com.github.fairy.era.aop.Calculator.add(int,int))", returning = "targetMethodReturnValue")
public void printLogAfterReturn(Object targetMethodReturnValue) {
System.out.println("[AOP返回通知] 方法的返回值是:" + targetMethodReturnValue);
}
}
4.3 目标方法抛出的异常
- 在
@AfterThrowing
注解的throwing
属性获取目标方法抛出的异常对象。
- 示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
/**
* @AfterThrowing: 声明当前方法是异常通知方法,value属性用来指定切入点表达式
*/
@AfterThrowing(value = "execution(public int com.github.fairy.era.aop.Calculator.add(int,int))", throwing = "targetMethodException")
public void printLogAfterThrowing(JoinPoint joinPoint, Throwable targetMethodException) {
System.out.println("[AOP异常通知] 方法:" + joinPoint.getSignature().getName() + "抛出了异常,异常类型是:" + targetMethodException.getClass().getName());
}
}
第五章:重用切入点表达式(⭐)
5.1 概述
- 在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。
- 易于维护,一处修改,处处生效。
5.2 声明切入点表达式方法
通过
@Pointcut
注解将一个切入点声明成简单的方法。示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
/**
* 切入点表达式重用
*/
@Pointcut("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void pointcut() {
}
}
5.2 同一类内部引用
如果
@Pointcut
注解标注的切入点表达式方法和通知在一个方法中,那么通知可以通过方法名引入该切入点。示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
/**
* 切入点表达式重用
*/
@Pointcut("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void pointcut() {
}
/**
* @Before注解: 声明当前方法是前置通知方法,value属性用来指定切入点表达式
* 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
*/
@Before("pointcut()")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("[AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
/**
* @AfterReturn注解: 声明当前方法是返回通知方法,value属性用来指定切入点表达式
*/
@AfterReturning(value = "pointcut()", returning = "targetMethodReturnValue")
public void printLogAfterReturn(Object targetMethodReturnValue) {
System.out.println("[AOP返回通知] 方法的返回值是:" + targetMethodReturnValue);
}
/**
* @AfterThrowing: 声明当前方法是异常通知方法,value属性用来指定切入点表达式
*/
@AfterThrowing(value = "pointcut()", throwing = "targetMethodException")
public void printLogAfterThrowing(JoinPoint joinPoint, Throwable targetMethodException) {
System.out.println("[AOP异常通知] 方法:" + joinPoint.getSignature().getName() + "抛出了异常,异常类型是:" + targetMethodException.getClass().getName());
}
/**
* @After:声明当前方法是最终通知方法,value属性用来指定切入点表达式
*/
@After("pointcut()")
public void printLogAfter() {
System.out.println("[AOP最终通知] 方法开始了");
}
}
5.3 在不同类中引用
- 切入点方法的访问控制符同时也控制着这个切入点的可见性。
- 如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中,在这种情况下,它们必须被声明为
public
。 - 在引入这个切入点时,必须将类名也包括在内,如果类没有与这个切面放在同一个包中,还必须包含包名。
- 示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 作为存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于统一管理
*
* @author 许大仙
* @version 1.0
* @since 2021-11-08 10:57
*/
@Component
public class GlobalPointCut {
/**
* 切入点表达式重用
*/
@Pointcut("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void pointcut() {
}
}
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
/**
* @Before注解: 声明当前方法是前置通知方法,value属性用来指定切入点表达式
* 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
*/
@Before("com.github.fairy.era.aop.aspect.GlobalPointCut.pointcut()")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("[AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
/**
* @AfterReturn注解: 声明当前方法是返回通知方法,value属性用来指定切入点表达式
*/
@AfterReturning(value = "com.github.fairy.era.aop.aspect.GlobalPointCut.pointcut()", returning = "targetMethodReturnValue")
public void printLogAfterReturn(Object targetMethodReturnValue) {
System.out.println("[AOP返回通知] 方法的返回值是:" + targetMethodReturnValue);
}
/**
* @AfterThrowing: 声明当前方法是异常通知方法,value属性用来指定切入点表达式
*/
@AfterThrowing(value = "com.github.fairy.era.aop.aspect.GlobalPointCut.pointcut()", throwing = "targetMethodException")
public void printLogAfterThrowing(JoinPoint joinPoint, Throwable targetMethodException) {
System.out.println("[AOP异常通知] 方法:" + joinPoint.getSignature().getName() + "抛出了异常,异常类型是:" + targetMethodException.getClass().getName());
}
/**
* @After:声明当前方法是最终通知方法,value属性用来指定切入点表达式
*/
@After("com.github.fairy.era.aop.aspect.GlobalPointCut.pointcut()")
public void printLogAfter() {
System.out.println("[AOP最终通知] 方法开始了");
}
}
第六章:设置切面的优先级(⭐)
6.1 概述
- 在同一连接点上可能应用不止一个切面,除非明确指定,否则它们的优先级是不确定的。
- 切面的优先级可以通过实现
Ordered
接口或通过@Order注解
指定。 - 实现 Ordered 接口,getOrder() 方法的返回值越小,优先级越高。
- 使用 @Order 注解,通过注解的 value 属性指定优先级,值越小优先级越高。
切面的优先级控制切面的内外嵌套顺序,优先级高的切面在外面,优先级低的切面在里面
。
6.2 定义多个切面类,并指定优先级
- LogAspect.java
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Order(value = 1) // 设置切面的优先级
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
@Pointcut("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void pointcut() {
}
/**
* @Before注解: 声明当前方法是前置通知方法,value属性用来指定切入点表达式
* 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
*/
@Before("pointcut()")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("LogAspect [AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
/**
* @AfterReturn注解: 声明当前方法是返回通知方法,value属性用来指定切入点表达式
*/
@AfterReturning(value = "pointcut()", returning = "targetMethodReturnValue")
public void printLogAfterReturn(Object targetMethodReturnValue) {
System.out.println("LogAspect [AOP返回通知] 方法的返回值是:" + targetMethodReturnValue);
}
/**
* @AfterThrowing: 声明当前方法是异常通知方法,value属性用来指定切入点表达式
*/
@AfterThrowing(value = "pointcut()", throwing = "targetMethodException")
public void printLogAfterThrowing(JoinPoint joinPoint, Throwable targetMethodException) {
System.out.println("LogAspect [AOP异常通知] 方法:" + joinPoint.getSignature().getName() + "抛出了异常,异常类型是:" + targetMethodException.getClass().getName());
}
/**
* @After:声明当前方法是最终通知方法,value属性用来指定切入点表达式
*/
@After("pointcut()")
public void printLogAfter() {
System.out.println("LogAspect [AOP最终通知] 方法开始了");
}
}
- LogAspect2.java
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Order(value = 2) // 设置切面的优先级
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect2 {
@Pointcut("execution(public int com.github.fairy.era.aop.Calculator.add(int,int))")
public void pointcut() {
}
/**
* @Before注解: 声明当前方法是前置通知方法,value属性用来指定切入点表达式
* 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
*/
@Before("pointcut()")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("LogAspect2 [AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
/**
* @AfterReturn注解: 声明当前方法是返回通知方法,value属性用来指定切入点表达式
*/
@AfterReturning(value = "pointcut()", returning = "targetMethodReturnValue")
public void printLogAfterReturn(Object targetMethodReturnValue) {
System.out.println("LogAspect2 [AOP返回通知] 方法的返回值是:" + targetMethodReturnValue);
}
/**
* @AfterThrowing: 声明当前方法是异常通知方法,value属性用来指定切入点表达式
*/
@AfterThrowing(value = "pointcut()", throwing = "targetMethodException")
public void printLogAfterThrowing(JoinPoint joinPoint, Throwable targetMethodException) {
System.out.println("LogAspect2 [AOP异常通知] 方法:" + joinPoint.getSignature().getName() + "抛出了异常,异常类型是:" + targetMethodException.getClass().getName());
}
/**
* @After:声明当前方法是最终通知方法,value属性用来指定切入点表达式
*/
@After("pointcut()")
public void printLogAfter() {
System.out.println("LogAspect2 [AOP最终通知] 方法开始了");
}
}
6.3 测试
- 测试:
package com.github.fairy.era.bean;
import com.github.fairy.era.aop.Calculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringTest {
@Autowired
private Calculator calculator;
@Test
public void test() {
int add = calculator.add(1, 2);
System.out.println("add = " + add);
}
}
- 结果:
LogAspect [AOP前置通知] 方法名:add,返回修饰符:public abstract,声明类型的完全限定名称:com.github.fairy.era.aop.Calculator,方法参数:[1, 2]
LogAspect2 [AOP前置通知] 方法名:add,返回修饰符:public abstract,声明类型的完全限定名称:com.github.fairy.era.aop.Calculator,方法参数:[1, 2]
LogAspect2 [AOP返回通知] 方法的返回值是:3
LogAspect2 [AOP最终通知] 方法开始了
LogAspect [AOP返回通知] 方法的返回值是:3
LogAspect [AOP最终通知] 方法开始了
add = 3
第七章:切入点表达式(⭐)
7.1 概述
- 通过切入点表达式可以定位一个或多个具体的连接点。
7.2 语法格式
- 语法:
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
简化:
- 用
*
代替权限修饰符
和返回值类型
部分表示权限修饰符
和返回值类型
不限。 - 在包名的部分,一个
*
只能代表包层次结构中的一层,表示这一层是任意的。如:*.Hello
匹配com.Hello
,但是不匹配com.github.Hello
。 - 在类名的部分,使用
*..
表示包名任意,包的层次深度任意。 - 在类名部分,可以使用使用
*
号代替,表示类名任意。 - 在类名部分,可以使用
*
号代替类名的一部分,如:*Service
。 - 在方法名部分,可以使用
*
号代替,表示方法名任意。 - 在方法名部分,可以使用
*
号代替方法名的一部分,如:*Emp
。 - 在方法参数列表部分,使用
(..)
表示参数列表任意。 - 在方法参数列表部分,使用
(int,..)
表示参数列表以int
类型的参数开头。 - 在方法参数部分,基本数据类型和对应的包装类型是不一样的,如:切入点表达式中使用
int
和实际方法中Integer
是不匹配的。 在方法返回值类型部分,如果想要明确的指定一个返回值类型,那么必须同时明确写明权限修饰符。
// 正确 execution(public int *..*Service.*(.., int))
// 错误 execution(* int *..*Service.*(.., int))
在方法返回值类型部分,
public *
表示权限修饰符明确,返回值任意。
- 用
切入点表达式还可以使用逻辑运算符(了解):
execution() || execution()
:表示满足两个 execution() 中的任何一个即可。execution() && execution()
:表示两个 execution() 表达式必须都满足。!execution()
:表示不满足表达式的其他方法。
示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
@Pointcut("execution(* com.github.fairy.era.aop.Calculator.add(..))")
public void pointcut() {
}
@Before("pointcut()")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("LogAspect [AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
}
- 示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
@Pointcut("execution(* *..Calculator.add(..))")
public void pointcut() {
}
@Before("pointcut()")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("LogAspect [AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
}
- 示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
@Pointcut("execution(* *..Calculator.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void printLogBefore(JoinPoint joinPoint) {
// 通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
// 通过方法的签名对象获取目标方法的详细信息
String name = signature.getName();
String modifier = Modifier.toString(signature.getModifiers());
String declaringTypeName = signature.getDeclaringTypeName();
// 通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
System.out.println("LogAspect [AOP前置通知] 方法名:" + name + ",返回修饰符:" + modifier + ",声明类型的完全限定名称:" + declaringTypeName + ",方法参数:" + Arrays.asList(args));
}
}
7.3 总结
- 虽然上面介绍过的切入点表达式语法细节很多,有很多变化,但是实际上具体在项目中应用时有比较固定的写法。
- 典型场景:
- 在基于 XML 的声明式事务配置中需要指定切入点表达式,这个切入点表达式通常都会织入到所有 Service 类(接口)的所有方法。
- 那么切入点表达式将如右所示:
execution(* *..*Service.*(..))
。
第八章:环绕通知(⭐)
环绕通知对应整个
try...catch...finally
结构,包括前面四种通知的所有功能。示例:
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
@Pointcut("execution(* *..Calculator.*(..))")
public void pointcut() {
}
/**
* 使用@Around注解标明环绕通知方法
*
* @param joinPoint 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参,Spring会将这个类型的对象传给我们
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
// 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组
Object[] args = joinPoint.getArgs();
// 通过ProceedingJoinPoint对象获取目标方法的签名对象,通过签名对象获取目标方法的方法名
String methodName = joinPoint.getSignature().getName();
// 声明变量用来存储目标方法的返回值
Object targetReturnValue = null;
try {
// 模拟事务
System.out.println("[AOP 环绕通知] 开启事务,方法名:" + methodName + ",参数列表:" + Arrays.asList(args));
// 通过ProceedingJoinPoint对象调用目标方法,
targetReturnValue = joinPoint.proceed(args);
// 在目标方法成功返回后:提交事务(模拟)
System.out.println("[AOP 环绕通知] 提交事务,方法名:" + methodName + ",方法返回值:" + targetReturnValue);
} catch (Throwable e) {
// 在目标方法抛异常后:回滚事务(模拟)
System.out.println("[AOP 环绕通知] 回滚事务,方法名:" + methodName + ",异常:" + e.getClass().getName());
} finally {
// 在目标方法最终结束后:释放数据库连接
System.out.println("[AOP 环绕通知] 释放数据库连接,方法名:" + methodName);
}
return targetReturnValue;
}
}
package com.github.fairy.era.bean;
import com.github.fairy.era.aop.Calculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringTest {
@Autowired
private Calculator calculator;
@Test
public void test() {
int add = calculator.add(1, 2);
System.out.println("add = " + add);
}
}
第九章:没有接口的情况
9.1 概述
- 在目标类没有实现任何接口的情况下,Spring 会自动使用 cglib 技术实现代理。
9.2 创建目标类
确保这个类在自动扫描的包下,同时确保切面的切入点表达式能够覆盖到类中的方法。
EmpService.java
package com.github.fairy.era.aop.service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 14:25
*/
public class EmpService {
public void getEmpList() {
System.out.println("方法内部 com.github.fairy.era.aop.service.EmpService.getEmpList");
}
}
9.2 切面类
- LogAspect.java
package com.github.fairy.era.aop.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-08 08:54
*/
@Aspect // 标注这个类是一个切面类
@Component // 加入到Spring的IOC容器中
public class LogAspect {
@Pointcut("execution(* *..EmpService.*(..))")
public void pointcut() {
}
/**
* 使用@Around注解标明环绕通知方法
*
* @param joinPoint 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参,Spring会将这个类型的对象传给我们
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
// 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组
Object[] args = joinPoint.getArgs();
// 通过ProceedingJoinPoint对象获取目标方法的签名对象,通过签名对象获取目标方法的方法名
String methodName = joinPoint.getSignature().getName();
// 声明变量用来存储目标方法的返回值
Object targetReturnValue = null;
try {
// 模拟事务
System.out.println("[AOP 环绕通知] 开启事务,方法名:" + methodName + ",参数列表:" + Arrays.asList(args));
// 通过ProceedingJoinPoint对象调用目标方法,
targetReturnValue = joinPoint.proceed(args);
// 在目标方法成功返回后:提交事务(模拟)
System.out.println("[AOP 环绕通知] 提交事务,方法名:" + methodName + ",方法返回值:" + targetReturnValue);
} catch (Throwable e) {
// 在目标方法抛异常后:回滚事务(模拟)
System.out.println("[AOP 环绕通知] 回滚事务,方法名:" + methodName + ",异常:" + e.getClass().getName());
} finally {
// 在目标方法最终结束后:释放数据库连接
System.out.println("[AOP 环绕通知] 释放数据库连接,方法名:" + methodName);
}
return targetReturnValue;
}
}
9.3 测试
package com.github.fairy.era.bean;
import com.github.fairy.era.aop.service.EmpService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringTest {
@Autowired
private EmpService empService;
@Test
public void test() {
empService.getEmpList();
}
}