Spring Aop的使用和通知注解的执行顺序

一、Aop 常用注解

spring4的几种通知的执行顺序:

程序执行正常:

1、环绕通知前
2、@Before通知
3、程序逻辑
4、环绕通知后
5、@After通知
6、@AfterReturning通知

程序执行异常:

1、环绕通知前
2、@Before通知
3、@After通知
4、@AfterThrowing异常通知
异常日志

spring5的几种通知的执行顺序:

程序执行正常:

1、环绕通知前
2、@Before通知
3、程序逻辑
4、@AfterReturning通知
5、@After通知
6、环绕通知后

程序执行异常:

1、环绕通知前
2、@Before通知
3、@AfterThrowing异常通知
4、@After通知
异常日志

----------------------------------------------------------------------------------------------------

@Before 前置通知: 目标方法之前执行
@After 后置通知: 目标方法之后执行(始终执行)
@AfterReturning 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing 异常通知: 出现异常时候执行
@Around 环绕通知: 环绕目标方法执行

spring4和spring5的全部通知执行顺序是不同的,下面用案例说明。

二、案例演示

2.1 业务类

创建业务接口类:CalcService

/**
 * @InterfaceName CalService
 * @Description TODO
 * @Author Oneby
 * @Date 2021/1/22 11:20
 * @Version 1.0
 */
public interface CalcService {
    public int div(int x, int y);
}

创建业务接口的实现类:CalcServiceImpl

/**
 * @ClassName CalcServiceImpl
 * @Description TODO
 * @Author Oneby
 * @Date 2021/1/22 11:15
 * @Version 1.0
 */
@Service
public class CalcServiceImpl implements CalcService {
    @Override
    public int div(int x, int y) {
        int result = x / y;
        System.out.println("=========>CalcServiceImpl被调用了,我们的计算结果:" + result);
        return result;
    }
}

2.2 切面类

在方法前后各种通知,引入切面编程

@Aspect:指定一个类为切面类
@Component:纳入 Spring 容器管理

创建切面类 MyAspect

/**
 * @ClassName MyAspect
 * @Description TODO
 * @Author Oneby
 * @Date 2021/1/22 11:27
 * @Version 1.0
 */
@Aspect
@Component
public class MyAspect {
    @Before("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
    public void beforeNotify() {
        System.out.println("******** @Before我是前置通知MyAspect");
    }

    @After("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
    public void afterNotify() {
        System.out.println("******** @After我是后置通知");
    }

    @AfterReturning("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
    public void afterReturningNotify() {
        System.out.println("********@AfterReturning我是返回后通知");
    }

    @AfterThrowing("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
    public void afterThrowingNotify() {
        System.out.println("********@AfterThrowing我是异常通知");
    }

    @Around("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object retValue = null;
        System.out.println("我是环绕通知之前AAA");
        retValue = proceedingJoinPoint.proceed();
        System.out.println("我是环绕通知之后BBB");
        return retValue;
    }
}

2.3 主启动类

在主包名下创建启动类

为何要在主包名下创建启动类?其他子包均在主包下面,这样我们就不用使用 @ComponentScan 扫扫描包啦~

在这里插入图片描述
Springboot 启动类带上 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}),至于为啥,等到我复习 SpringBoot 的时候再说吧~

/**
 * @ClassName AopStudyApplication
 * @Description TODO
 * @Author Oneby
 * @Date 2021/1/22 11:53
 * @Version 1.0
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class AopStudyApplication {
    public static void main(String[] args) {
        SpringApplication.run(AopStudyApplication.class, args);
    }
}

2.4 Spring4 下的测试

在 POM 文件中导入 SpringBoot 1.5.9.RELEASE 版本

SpringBoot 1.5.9.RELEASE 版本的对应的 Spring 版本为 4.3.13 Release

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!-- <version>2.3.3.RELEASE</version> -->
        <version>1.5.9.RELEASE</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.heygo</groupId>
    <artifactId>interview1024</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- <version>1.5.9.RELEASE</version>
               ch/qos/logback/core/joran/spi/JoranException解决方案-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- springboot-jdbc 技术 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- springboot-aop 技术 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-captcha</artifactId>
            <version>4.6.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

在主启动类所在包下创建子包与测试类

包名必须和启动类包名相同。

在这里插入图片描述
注意:SpringBoot 1.5.9 版本在测试类上需要加上 @RunWith(SpringRunner.class) 注解,单元测试需要导入的包名为 import org.junit.Test;

@SpringBootTest
@RunWith(SpringRunner.class)  //1.5.9
public class AopTest {
    @Autowired
    private CalcService calcService;

    @Test
    public void testAop4() {
        System.out.println("spring版本:" + SpringVersion.getVersion() + "\t" + "SpringBoot版本:" + SpringBootVersion.getVersion());
        System.out.println();
        calcService.div(10, 2);
        // calcService.div(10, 0);
    }
}

正常执行的结果

环绕通知将前置通知与目标方法包裹住,执行完 @After 才执行 @AfterReturning

在这里插入图片描述

异常执行的结果

由于抛出了异常,因此环绕通知后半部分没有执行,执行完 @After 才执行 @AfterThrowing

在这里插入图片描述
注:Spring4 默认用的是 JDK 的动态代理

2.5 Spring 5 下的测试

在 POM 文件中导入 SpringBoot 2.3.3.RELEASE 版本

SpringBoot 2.3.3.RELEASE 版本的对应的 Spring 版本为 5.2.8 Release

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <!-- <version>1.5.9.RELEASE</version> -->
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.heygo</groupId>
    <artifactId>interview1024</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- springboot-jdbc 技术 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- springboot-aop 技术 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-captcha</artifactId>
            <version>4.6.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

测试类下修改代码

注意:SpringBoot 2.3.3 版本下,不需要在测试类上面添加 @RunWith(SpringRunner.class) 直接,单元测试需要导入的包名为 import org.junit.jupiter.api.Test;,不再使用 import org.junit.Test;

@SpringBootTest
public class AopTest {
    @Autowired
    private CalcService calcService;

    @Test
    public void testAop4() {
        System.out.println("spring版本:" + SpringVersion.getVersion() + "\t" + "SpringBoot版本:" + SpringBootVersion.getVersion());
        System.out.println();
        calcService.div(10, 0);
    }

    @Test
    public void testAop5() {
        System.out.println("spring版本:" + SpringVersion.getVersion() + "\t" + "SpringBoot版本:" + SpringBootVersion.getVersion());
        System.out.println();
        calcService.div(10, 5);
    }
}

正常执行的结果

感觉 Spring5 的环绕通知才是真正意义上的华绕通知,它将其他通知和方法都包裹起来了,而且 @AfterReturning 和 @After 之前,合乎逻辑!

在这里插入图片描述

异常执行的结果

由于方法抛出了异常,因此环绕通知后半部分没有执行,并且 @AfterThrowing 和 @After 之前

在这里插入图片描述

三、Aop 执行顺序总结

在这里插入图片描述

四、@Pointcut切入点表达式补充

来源:https://blog.csdn.net/XWForever/article/details/103163021

 链接

4.1 表达式类型

标准的Aspectj Aop的pointcut的表达式类型是很丰富的,但是Spring Aop只支持其中的9种,外加Spring Aop自己扩充的一种一共是10种类型的表达式,分别如下。

  • execution:一般用于指定方法的执行,用的最多。
  • within:指定某些类型的全部方法执行,也可用来指定一个包。
  • this:Spring Aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
  • target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
  • args:当执行的方法的参数是指定类型时生效。
  • @target:当代理的目标对象上拥有指定的注解时生效。
  • @args:当执行的方法参数类型上拥有指定的注解时生效。
  • @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
  • @annotation:当执行的方法上拥有指定的注解时生效。
  • bean:当调用的方法是指定的bean的方法时生效。

4.2 使用示例

execution

execution是使用的最多的一种Pointcut表达式,表示某个方法的执行,其标准语法如下。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? 
	name-pattern(param-pattern) throws-pattern?)

modifiers-pattern表示方法的访问类型,public等;ret-type-pattern表示方法的返回值类型,如String表示返回类型是String,“”表示所有的返回类型;declaring-type-pattern表示方法的声明类,如“com.elim…”表示com.elim包及其子包下面的所有类型;name-pattern表示方法的名称,如“add*”表示所有以add开头的方法名;param-pattern表示方法参数的类型,name-pattern(param-pattern)其实是一起的表示的方法集对应的参数类型,如“add()”表示不带参数的add方法,“add()”表示带一个任意类型的参数的add方法,“add(,String)”则表示带两个参数,且第二个参数是String类型的add方法;throws-pattern表示异常类型;其中以问号结束的部分都是可以省略的。

1、“execution(* add())”匹配所有的不带参数的add()方法。
2、“execution(public * com.elim….add(…))”匹配所有com.elim包及其子包下所有类的以add开头的所有public方法。
3、“execution(* *(…) throws Exception)”匹配所有抛出Exception的方法。

within

within是用来指定类型的,指定类型中的所有方法将被拦截。

1、“within(com.elim.spring.aop.service.UserServiceImpl)”匹配UserServiceImpl类对应对象的所有方法外部调用,而且这个对象只能是UserServiceImpl类型,不能是其子类型。
2、“within(com.elim…*)”匹配com.elim包及其子包下面所有的类的所有方法的外部调用。

this

Spring Aop是基于代理的,this就表示代理对象。this类型的Pointcut表达式的语法是this(type),当生成的代理对象可以转换为type指定的类型时则表示匹配。基于JDK接口的代理和基于CGLIB的代理生成的代理对象是不一样的。

1、“this(com.elim.spring.aop.service.IUserService)”匹配生成的代理对象是IUserService类型的所有方法的外部调用。

target

Spring Aop是基于代理的,target则表示被代理的目标对象。当被代理的目标对象可以被转换为指定的类型时则表示匹配。

1、“target(com.elim.spring.aop.service.IUserService)”则匹配所有被代理的目标对象能够转换为IUserService类型的所有方法的外部调用。

args

args用来匹配方法参数的。

1、“args()”匹配任何不带参数的方法。
2、“args(java.lang.String)”匹配任何只带一个参数,而且这个参数的类型是String的方法。
3、“args(…)”带任意参数的方法。
4、“args(java.lang.String,…)”匹配带任意个参数,但是第一个参数的类型是String的方法。
5、“args(…,java.lang.String)”匹配带任意个参数,但是最后一个参数的类型是String的方法。

@target

@target匹配当被代理的目标对象对应的类型及其父类型上拥有指定的注解时。

1、“@target(com.elim.spring.support.MyAnnotation)”匹配被代理的目标对象对应的类型上拥有MyAnnotation注解时。

@args

@args匹配被调用的方法上含有参数,且对应的参数类型上拥有指定的注解的情况。

1、“@args(com.elim.spring.support.MyAnnotation)”匹配方法参数类型上拥有MyAnnotation注解的方法调用。如我们有一个方法add(MyParam param)接收一个MyParam类型的参数,而MyParam这个类是拥有注解MyAnnotation的,则它可以被Pointcut表达式“@args(com.elim.spring.support.MyAnnotation)”匹配上。

@within

@within用于匹配被代理的目标对象对应的类型或其父类型拥有指定的注解的情况,但只有在调用拥有指定注解的类上的方法时才匹配。

1、“@within(com.elim.spring.support.MyAnnotation)”匹配被调用的方法声明的类上拥有MyAnnotation注解的情况。比如有一个ClassA上使用了注解MyAnnotation标注,并且定义了一个方法a(),那么在调用ClassA.a()方法时将匹配该Pointcut;如果有一个ClassB上没有MyAnnotation注解,但是它继承自ClassA,同时它上面定义了一个方法b(),那么在调用ClassB().b()方法时不会匹配该Pointcut,但是在调用ClassB().a()时将匹配该方法调用,因为a()是定义在父类型ClassA上的,且ClassA上使用了MyAnnotation注解。但是如果子类ClassB覆写了父类ClassA的a()方法,则调用ClassB.a()方法时也不匹配该Pointcut。

@annotation

@annotation用于匹配方法上拥有指定注解的情况。

1、“@annotation(com.elim.spring.support.MyAnnotation)”匹配所有的方法上拥有MyAnnotation注解的方法外部调用。

bean
bean用于匹配当调用的是指定的Spring的某个bean的方法时。

1、“bean(abc)”匹配Spring Bean容器中id或name为abc的bean的方法调用。
2、“bean(user*)”匹配所有id或name为以user开头的bean的方法调用。

比较常用的就是execution和annotation

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 可以使用Spring AOP来通过自定义注解来实现切入点。首先,定义一个自定义注解,并在需要切入的方法上使用注解,然后定义一个切面,并在切面中使用@Before 或 @Around 注解来拦截被标记的方法,最后,在Spring的配置文件中声明所需的切面,此时,所有被标记的方法都会被拦截。 ### 回答2: 使用Spring AOP切入自定义注解有以下几个步骤: 1. 在项目中引入Spring AOP的依赖,一般为spring-aopspring-aspects。 2. 在配置文件中开启Spring AOP的自动代理功能。可以通过在XML配置文件中添加<aop:aspectj-autoproxy />或在Java配置文件中添加@EnableAspectJAutoProxy注解实现。 3. 创建一个切面类,用于定义切入的逻辑。这个类需要使用@Component或者其他Spring注解进行标识,以便Spring能够扫描到。 4. 在切面类的方法中,使用@Before、@After等注解定义切入点和具体的切入操作。例如,使用@Before注解定义在某个注解标记的方法执行之前切入的逻辑。 5. 在注解中定义自定义的切点。可以使用@Retention和@Target等元注解来配置注解的生命周期和使用范围。 6. 在目标类或方法上添加自定义的注解。例如,在一个Service类的某个方法上添加自定义注解。 7. 运行项目,Spring会根据配置自动代理目标类,当目标类或方法被调用时,切面类中定义的切入逻辑就会自动被执行。 8. 可以通过配置切入的顺序、通知的类型等来进一步细化切入的逻辑。 通过以上步骤,我们可以使用Spring AOP方便地切入自定义注解,实现对目标类或方法的增强、日志记录、权限控制等功能。 ### 回答3: 使用Spring AOP切入自定义注解需要以下几个步骤: 1. 定义一个自定义的注解。可以使用Java提供的`@interface`关键字创建一个注解,例如: ```java @Target(ElementType.METHOD) // 定义注解的作用范围为方法 @Retention(RetentionPolicy.RUNTIME) // 注解在运行时可见 public @interface CustomAnnotation { // 自定义注解的属性 } ``` 2. 创建一个切面类来处理注解。可以使用Spring提供的`@Aspect`注解来标记切面类,并在方法上使用`@Before`、`@After`等注解来定义切入点和增强逻辑。例如: ```java @Aspect @Component public class CustomAspect { @Before("@annotation(customAnnotation)") // 拦截带有CustomAnnotation注解的方法 public void beforeMethod(CustomAnnotation customAnnotation) { // 在方法执行前执行的逻辑 } } ``` 3. 配置Spring AOP。在Spring配置文件中添加AOP的配置,例如使用`<aop:aspectj-autoproxy>`标签开启自动代理,并指定切面类的包名,让Spring能够自动扫描并应用切面逻辑。 4. 在目标方法上使用自定义注解。在需要切入的方法上标记使用自定义注解,例如: ```java @CustomAnnotation public void doSomething() { // 方法的实际逻辑 } ``` 这样,在调用`doSomething()`方法时,Spring AOP会拦截到带有`@CustomAnnotation`注解的方法,并执行切面逻辑。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值