SpringBoot系列: SpringBoot 集成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>  

开启切面支持, 动态代理(默认就是开启的)

  1. application.yml默认开启
spring:
  aop:
    auto: true
    proxy-target-class: true
  1. 配置类 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;
	}
  • 解决方法
    1. 首先Arithmetic类中的方法, 返回类型、参数类型不要使用基本类型,而是使用包装类Integer,<font color=red(我只改了add方法,警示自己)
    2. ArithAspect类中的@Around环绕通知方法需要有返回值
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值