Spring开发基于注解AOP和通知的类型、执行顺序

前言:

Spring版本不一样,通知执行顺序也会存在差异
查看Spring版本的方法:

public static void main(String[] args) {
        System.out.println(SpringVersion.getVersion());
    }

版本结果:
在这里插入图片描述

新建一个springBoot项目,目录结构:

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.7</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>chang</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>chang</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


切点类代码:

定义切点时execution表达式语法规则:
execution表达式语法规则

package com.example.chang.aop;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;


//该注解表示声明该类为一个切面类
@Aspect
@Component
//可以使用@Order注解指定先后顺序,数字越小,优先级越高,先进后出
@Order(value = 1)
public class MyAspect {

    //定义一个切点,execution表达式可以灵活运用
    @Pointcut("execution(public * com.example.chang..*.*(..))")
    public void pointCut(){};

    //@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        System.out.println("进入@Before测试"+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+ Arrays.asList(args)+"}");
    }

    //方法执行开始之后
    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println("进入@Afte测试"+joinPoint.getSignature().getName()+"结束。。。@After");
    }


    //当方法进行返回的时候,returning属性是指定方法参数中的result来接收返回参数,这样就可以修改返回参数
    @AfterReturning(value="pointCut()",returning="result")
    public void logReturn(JoinPoint joinPoint,Object result){
        System.out.println("进入@AfterReturning测试"+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
    }


    //当方法执行异常的时候,throwding是指定方法参数中的e来接收异常参数,可以查看发生的什么异常
    @AfterThrowing(value="pointCut()",throwing="exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println("进入@AfterThrowing测试"+joinPoint.getSignature().getName()+"异常。。。异常信息:{"+exception+"}");
    }

    //环绕通知
    @Around("pointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable{

        System.out.println("进入@Around测试"+joinPoint.getSignature().getName()+"开始");
        //开始执行真正的请求
        Object  obj= joinPoint.proceed();
        System.out.println("进入@Around测试"+joinPoint.getSignature().getName()+"中间");
        System.out.println("进入@Around测试"+joinPoint.getSignature().getName()+"结束");
        return obj;


    }

}

测试类:
package com.example.chang.aop;

import org.springframework.core.SpringVersion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("request")
public class TestController01 {

    @RequestMapping("test")
    public String test(){
        System.out.println("真正的方法");
        //System.out.println(6/0);
        return "success666";
    }

//可以查看自己的spring的版本
    public static void main(String[] args) {
        System.out.println(SpringVersion.getVersion());
    }
}

启动项目

在浏览器中输入访问路径:
在这里插入图片描述

正常运行查看日志输出:

在这里插入图片描述

抛出异常查看日志输出:

在这里插入图片描述

敲黑板了!进行总结:

单个切面,通知的执行顺序:

(1)单个@Before、@After、@AfterReturning、@AfterThrowing执行顺序
 ①Spring4.0
  正常情况:@Before=目标方法=@After=====@AfterReturning
  异常情况:@Before=目标方法=@After=====@AfterThrowing
  
 ②Spring5
  正常情况:@Before=目标方法=@AfterReturning=====@After
  异常情况:@Before=目标方法=@AfterThrowing=====@After
  
(2)单个@Around的执行顺序
①Spring4.0
    正常情况:环绕前置===== 目标方法执行===== 环绕返回===== 环绕最终
    异常情况:环绕前置===== 目标方法执行===== 环绕异常===== 环绕最终
②Spring5
    正常情况:环绕前置===== 目标方法执行===== 环绕返回===== 环绕最终
    异常情况:环绕前置===== 目标方法执行===== 环绕异常===== 环绕最终
(3)五大通知执行顺序

①Spring4.0
    正常情况:环绕前置=====@Before====== 目标方法执行===== 环绕返回===== 环绕最终=====@After=====@AfterReturning
    异常情况:环绕前置=====@Before====== 目标方法执行===== 环绕异常===== 环绕最终===== @After=====@AfterThrowing
②Spring5
    正常情况:环绕前置===== @Before=目标方法执行= @AfterReturning===== @After ===== 环绕返回===== 环绕最终
    异常情况:环绕前置===== @Before=目标方法执行= @AfterThrowing===== @After ===== 环绕异常===== 环绕最终

多个切面,通知的执行顺序:

多个切面时,定义不同的@Order(value = ?)

@Aspect
@Component
//可以使用@Order注解指定先后顺序,数字越小,优先级越高,先进后出
@Order(value = 1)
public class MyAspect {}

@Aspect
@Component
//可以使用@Order注解指定先后顺序,数字越小,优先级越高,先进后出
@Order(value = 5)
public class MyAspect02 {}

①Spring4.0
    正常情况:切面1环绕前置=== 切面1@Before=== 切面2环绕前置=== 切面2@Before=== 目标方法执行=== 切面2环绕返回=== 切面2环绕最终=== 切面2@After=== 切面2@AfterReturning=== 切面1环绕返回=== 切面1环绕最终=== 切面1@After=== 切面1@AfterThrowing
    异常情况:切面1环绕前置=== 切面1@Before=== 切面2环绕前置=== 切面2@Before=== 目标方法执行=== 切面2环绕异常=== 切面2环绕最终=== 切面2@After=== 切面2@AfteThrowing === 切面1环绕异常=== 切面1环绕最终=== 切面1@After=== 切面1@AfterThrowing
②Spring5
    正常情况:切面1环绕前置=== 切面1@Before=== 切面2环绕前置=== 切面2@Before=== 目标方法执行=== 切面2@AfterReturning=== 切面2@After=== 切面2环绕返回=== 切面2环绕最终=== 切面1@AfterReturning=== 切面1@After=== 切面1环绕返回=== 切面1环绕最终
    异常情况:切面1环绕前置=== 切面1@Before=== 切面2环绕前置=== 切面2@Before=== 目标方法执行=== 切面2@AfterThrowing=== 切面2@After === 切面2环绕异常=== 切面2环绕最终=== 切面1@AfterThrowing=== 切面1@After=== 切面1环绕异常===切面1环绕最终

多个切面的示例代码:

把MyAspect类的代码复制,新建个类且@Order(value = 5),改一下输出的日志:

MyAspect02类代码:
package com.example.chang.aop;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;


//该注解表示声明该类为一个切面类
@Aspect
@Component
//可以使用@Order注解指定先后顺序,数字越小,优先级越高,先进后出
@Order(value = 5)
public class MyAspect02 {

    //定义一个切点,execution表达式可以灵活运用
    @Pointcut("execution(public * com.example.chang..*.*(..))")
    public void pointCut(){};

  //省略同MyAspect类的代码
}
运行结果

( 对应上面的 结论–》 多个切面,通知的执行顺序–》 Spring5 --》正常情况 )
在这里插入图片描述

参考:
执行顺序参考文件:https://www.cnblogs.com/orzjiangxiaoyu/p/13869747.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值