SpringBoot系列: SpringBoot 集成AOP进行面向切面编程案例
介绍
- Spring AOP 概念理解可以跳转到https://blog.csdn.net/qukaiwei/article/details/50367761,这篇文章的讲解通俗易懂,Cglib动态代理也顺便讲了。
- 这篇文章主要以四则运算作为基础演示AOP切面的过程;方便以后查看。
Aspect切面注解理解
-
@Aspect 把当前类标识为一个切面,用在类上
-
@Pointcut 织入Advice(通知)的触发条件(切入点)。
-
@Before 前置通知,相当于BeforeAdvice,目标方法执行前执行
-
@After 后置通知,不管是抛出异常或者正常退出都会执行
-
@AfterReturning 后置返回通知,相当于AfterReturningAdvice,方法正常退出时执行,在@Before之后,@After之前执行
-
@AfterThrowing 异常抛出通知,相当于ThrowsAdvice,目标方法抛出异常后执行,在@Before之后,@After之前执行
-
@Around 环绕增强t通知,相当于MethodInterceptor,连接点类/方法使用ProceedingJoinPoint#proceed()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WZpiRWfr-1620895858556)(C:\Users\yinchen\Desktop\2.png)]
SpringBoot集成AOP切面
pom.xml引入aop依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
开启切面支持, 动态代理(默认就是开启的)
- application.yml默认开启
spring:
aop:
auto: true
proxy-target-class: true
- 配置类 AspectConfig
@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
}
基础运算代码: Arithmetic.java(简单的四则运算)
@Component
public class Arithmetic {
//add
public Integer add(Integer d,Integer e) {
System.out.println("add method END!");
System.out.println();
return d+e;
}
//subtraction
public int sub(int a,int b) {
System.out.println("sub method END!");
System.out.println();
return a-b;
}
//multiplicative
public int mul(int a,int b) {
System.out.println("mul method END!");
System.out.println();
return a*b;
}
//division
public int div(int a,int b) {
System.out.println("div method END!");
System.out.println();
return a/b;
}
}
切面拦截代码:ArithAspect.java(基于AspectJ的注解方式的切面——拦截Arithmetic)
@Component
@Aspect
public class ArithAspect {
/**
* 织入切入点
* @Pointcut 切入点注解,用来声明切点表达式
* execution 表示在方法执行时触发
* * 表示返回任意类型
* com.cheer.morning.math.Arithmetic 方法所属的类
* *(..) 任意方法及参数
*/
@Pointcut("execution( * com.cheer.morning.math.Arithmetic.*(..))")
public void pointcutDeclaration(){
}
/**
* 前置通知,在方法执行前执行, 第一个执行
* @param jop
*/
@Before("pointcutDeclaration()")
public void beforeMethod(JoinPoint jop){
//方法名
String methodName = jop.getSignature().getName();
//获取参数
Object[] args = jop.getArgs();
System.out.println("BeforeAdvice, The Method:" + methodName + ", Parameter:" + Arrays.asList(args) );
System.out.println();
}
/**
* 后置通知,在方法执行后执行
* @param jop
*/
@After("pointcutDeclaration()")
public void afterMethod(JoinPoint jop){
//方法名
String methodName = jop.getSignature().getName();
//获取参数
Object[] args = jop.getArgs();
System.out.println("AfterAdvice, The Method:" + methodName + ", Parameter:" + Arrays.asList(args) );
System.out.println();
}
/**
* 返回通知,方法正常执行完毕后执行,在@After之前,@Before之后
* @param jop
* @param result
*/
@AfterReturning(value = "pointcutDeclaration()", returning = "result")
public void afterReturningMethod(JoinPoint jop, Object result){
//方法名
String methodName = jop.getSignature().getName();
//获取参数
Object[] args = jop.getArgs();
System.out.println("AfterReturning, The Method:" + methodName + ", Parameter:" + Arrays.asList(args)+ ", Result:" + result);
System.out.println();
}
/**
* 异常通知,在方法抛出异常之后执行,在@After之前,@Before之后
* @param jop
* @param e
*/
@AfterThrowing(value = "pointcutDeclaration()", throwing = "e")
public void afterThrowingMethod(JoinPoint jop, Exception e){
//方法名
String methodName = jop.getSignature().getName();
//获取参数
Object[] args = jop.getArgs();
System.out.println("AfterThrowing, The Method:" + methodName + ", Parameter:" + Arrays.asList(args)+ ", Exception:" + e );
System.out.println();
}
/**
* 环绕通知,最强大的通知,可以在这里代替以上四个通知。如果配置了上面的四个方法,可以注释或者删掉
* @param jop
* @return
*/
@Around("pointcutDeclaration()")
public Object around(ProceedingJoinPoint jop){
//方法名
String methodName = jop.getSignature().getName();
//获取参数
Object[] args = jop.getArgs();
//提前声明结果
Object result = null;
try {
//前置通知
System.out.println("Before, The Method:" + methodName + ", Parameter:" + Arrays.asList(args) );
System.out.println();
//执行目标方法
result = jop.proceed();
//后置通知
System.out.println("After, The Method:" + methodName + ", Parameter:" + Arrays.asList(args) + ", Result:" + result);
System.out.println();
} catch (Throwable e) {
//异常通知
System.out.println("AfterThrowing, The Method:" + methodName + ", Parameter:" + Arrays.asList(args) );
System.out.println();
e.printStackTrace();
}
return result;
}
}
测试类
@SpringBootTest
class SpringBootAopApplicationTests {
@Test
void contextLoads() {
}
@Autowired
Arithmetic arithmetic;
@Test
public void aopTest(){
int result = arithmetic.add(1,3);
System.out.println("Add结果:" + result);
System.out.println("======================");
result = arithmetic.div(8,0);
System.out.println("Div结果:" + result);
}
/**
* 单独测试@Around环绕通知
*/
@Test
public void aopAroundTest(){
Integer result = arithmetic.add(1,3);
System.out.println("Add结果:" + result);
System.out.println("======================");
result = arithmetic.div(8,0);
System.out.println("Div结果:" + result);
}
}
测试结果
aopTest测试结果
* BeforeAdvice, The Method:add, Parameter:[1, 3]
*
* add method END!
*
* AfterReturning, The Method:add, Parameter:[1, 3], Result:4
*
* AfterAdvice, The Method:add, Parameter:[1, 3]
*
* Add结果:4
* ======================
* BeforeAdvice, The Method:div, Parameter:[8, 0]
*
* div method END!
*
* AfterThrowing, The Method:div, Parameter:[8, 0], Exception:java.lang.ArithmeticException: / by zero
*
* AfterAdvice, The Method:div, Parameter:[8, 0]
aopAround测试结果
* Before, The Method:add, Parameter:[1, 3]
*
* add method END!
*
* After, The Method:add, Parameter:[1, 3], Result:4
*
* Add结果:4
* ======================
* Before, The Method:div, Parameter:[8, 0]
*
* div method END!
*
* AfterThrowing, The Method:div, Parameter:[8, 0]
开发中的坑
测试报错 org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public int com.cheer.morning.math.Arithmetic.add(int,int)
-
报错原因
在环绕通知之后, 目标对象的方法是返回基本类型(int), 切面拦截之后返回了null.
-
报错源码
AOP的Cglib动态代理,org.springframework.aop.framework.CglibAopProxy#processReturnType(java.lang.Object, java.lang.Object, java.lang.reflect.Method, java.lang.Object)
/**
* 处理返回值。如果需要作为代理,则封装this的返回值,并验证null是否作为原语返回
*/
@Nullable
private static Object processReturnType(
Object proxy, @Nullable Object target, Method method, @Nullable Object returnValue) {
// Massage return value if necessary
if (returnValue != null && returnValue == target &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this". Note that we can't help
// if the target sets a reference to itself in another returned object.
returnValue = proxy;
}
Class<?> returnType = method.getReturnType();
if (returnValue == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return returnValue;
}
- 解决方法
- 首先Arithmetic类中的方法, 返回类型、参数类型不要使用基本类型,而是使用包装类Integer,<font color=red(我只改了add方法,警示自己)
- ArithAspect类中的@Around环绕通知方法需要有返回值