• Spring基础系列--AOP实践


    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9615720.html 

      本文目的是简单讲解下Spring AOP的使用。

      推荐使用IDEA + Spring Boot。

      新建Spring Boot 项目,选择Aspect功能。

      创建完成后,POM文件如下:

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     4     <modelVersion>4.0.0</modelVersion>
     5 
     6     <groupId>com.example</groupId>
     7     <artifactId>spring-aspect-demo</artifactId>
     8     <version>0.0.1-SNAPSHOT</version>
     9     <packaging>jar</packaging>
    10 
    11     <name>spring-aspect-demo</name>
    12     <description>Demo project for Spring Boot</description>
    13 
    14     <parent>
    15         <groupId>org.springframework.boot</groupId>
    16         <artifactId>spring-boot-starter-parent</artifactId>
    17         <version>2.0.4.RELEASE</version>
    18         <relativePath/> <!-- lookup parent from repository -->
    19     </parent>
    20 
    21     <properties>
    22         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    23         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    24         <java.version>1.8</java.version>
    25     </properties>
    26 
    27     <dependencies>
    28         <dependency>
    29             <groupId>org.springframework.boot</groupId>
    30             <artifactId>spring-boot-starter-aop</artifactId>
    31         </dependency>
    32 
    33         <dependency>
    34             <groupId>org.springframework.boot</groupId>
    35             <artifactId>spring-boot-starter-test</artifactId>
    36             <scope>test</scope>
    37         </dependency>
    38     </dependencies>
    39 
    40     <build>
    41         <plugins>
    42             <plugin>
    43                 <groupId>org.springframework.boot</groupId>
    44                 <artifactId>spring-boot-maven-plugin</artifactId>
    45             </plugin>
    46         </plugins>
    47     </build>
    48 
    49 
    50 </project>

      然后我们创建目标类和方法:

     1 package com.example.springaspectdemo;
     2 
     3 import org.springframework.stereotype.Component;
     4 
     5 @Component
     6 public class AspectDemo {
     7     public Integer test(String s){
     8         System.out.println("目标方法执行-"+s);
     9         return 123321;
    10     }
    11 }

      上面的代码中主要的就是@Component注解,在于将目标类扫描到Spring容器中。

      下面我们创建切面类:

     1 package com.example.springaspectdemo;
     2 
     3 import org.aspectj.lang.JoinPoint;
     4 import org.aspectj.lang.ProceedingJoinPoint;
     5 import org.aspectj.lang.annotation.*;
     6 import org.springframework.stereotype.Component;
     7 
     8 @Aspect
     9 @Component
    10 public class AspectTest {
    11 
    12     @Pointcut(value = "execution(* *.test(..)) && args(s)")
    13     public void pc(String s){
    14         System.out.println("切点执行");
    15     }
    16 
    22     @Before("pc(s)")
    23     public void beforeTest(JoinPoint jp,String s){
    24         System.out.println("前置通知-arg="+s);
    25     }
    26 
    27     @After("pc(s)")
    28     public void afterTest(String s){
    29         System.out.println("后置终点通知-arg="+s);
    30     }
    31 
    32     @AfterReturning(pointcut = "pc(s)", returning = "i")
    33     public void afterReturningTest(Object i,String s){
    34         System.out.println("后置返回通知-return="+i+"-arg="+s);
    35     }
    36 
    37     @AfterThrowing(pointcut = "pc(s)",throwing = "e")
    38     public void afterThrowingTest(Exception e,String s){
    39         System.out.println("后置异常通知-"+e.getMessage()+"-arg="+s);
    40     }
    41 
    42     @Around("pc(s)")
    43     public void aroundTest(ProceedingJoinPoint jp,String s){
    44         System.out.println("环绕前置通知-arg="+s);
    45         Object[] os = jp.getArgs();
    46         s = "caocao";
    47         os[0] = s;
    48         try {
    49             jp.proceed(os);
    50         } catch (Throwable throwable) {
    51             throwable.printStackTrace();
    52         }
    53         System.out.println("环绕后置通知-arg="+s);
    54     }
    55 }

      创建测试用例:

     1 package com.example.springaspectdemo;
     2 
     3 import org.junit.Test;
     4 import org.junit.runner.RunWith;
     5 import org.springframework.beans.factory.annotation.Autowired;
     6 import org.springframework.boot.test.context.SpringBootTest;
     7 import org.springframework.context.annotation.EnableAspectJAutoProxy;
     8 import org.springframework.test.context.junit4.SpringRunner;
     9 
    10 @RunWith(SpringRunner.class)
    11 @SpringBootTest
    12 @EnableAspectJAutoProxy
    13 public class SpringAspectDemoApplicationTests {
    14     @Autowired
    15     private AspectDemo aspectDemo;
    16 
    17     @Test
    18     public void aspecctTest(){
    19         aspectDemo.test("huahua");
    20     }
    21 }

      执行结果:

    环绕前置通知-arg=huahua
    前置通知-arg=caocao
    目标方法执行-caocao
    环绕后置通知-arg=caocao
    后置终点通知-arg=caocao
    后置返回通知-return=null-arg=caocao

      重点解析:

      1、后置返回通知是在目标代码执行完毕,返回结果之后执行,可以对返回的结果进行处理,但是要注意,其不能和环绕通知一起作用于同一个目标方法,否则会导致无法获取到返回值,正如上面例子中执行结果最后一行的null,表示的就是返回值,如果将环绕通知的部分注释掉,则可以返回正确的结果。

      2、后置返回通知的返回值类型必须是引用类型或者包装类型,不能是原始类型,否则会报错,类型不匹配。

      3、我们可以对目标方法的参数进行修改,但只能在环绕通知中进行,在环绕通知中的第一个参数必然是ProceedJoinPoint,它是JoinPoint的子类,通过其getArgs方法可以获取到调用目标方法的参数列表,可以对其进行修改,然后再执行带参数的proceed方法,将新的参数列表传递到目标方法。而且我们可以从上面的执行结果看到,环绕通知的前置部分是先于其他所有通知而执行的,那么它修改参数之后将会作用于后面所有的通知。正如例子中,我们在环绕通知前置部分将参数"huahua"改成了"caocao",在之后的所有通知和目标方法中获取到的参数全部变成了"caocao"。

      4、异常通知不只是捕捉目标方法中的异常,还有作用于同一方法上的其他通知中发生的异常。所以并不是一旦出现异常就不会执行afterReturning通知方法。如果是目标方法执行正常,却在afterReturning中发生异常的话,那么就会同时执行afterReturning通知方法和afterThrowing通知方法。

      5、我们还可以从上面的执行结果看到各个通知的执行顺序:

        环绕通知前置部分--->前置通知--->目标方法--->环绕通知后置部分--->后置终点通知--->后置返回通知--->后置异常通知

      6、对于上面执行的一点补充:

        那就是发生异常的情况,如果在环绕通知前置部分发生异常,那么之后除了后置终点通知是必然执行的外,只有最后的异常通知会被触发,其余一概不会执行。

        如果第5点中执行顺序哪一步发生了异常,那么其前面的通知会正常执行,后面的除了后置终点通知一定会执行外,异常通知也回被触发。

        但有一个例外,那就是前置通知,在前置通知和环绕通知同时作用于一个目标方法时,前置通知的异常将不会被后置异常通知捕捉到。

      7、终上所述,推荐不要将环绕通知和其他通知一起使用。因为环绕通知会导致一些异常的情况,使其他通知的部分功能失效。

      可以使用上面的代码进行修改测试!

  • 相关阅读:
    RAID 0 软件实现(Windows 系统)
    keepalived 实现LVS负载均衡高可用集群(一)
    ISCSI服务端-客户端基础配置
    LVS负载分担(DR模式)基础搭建(一)
    LVS负载分担(NAT模式)基础搭建
    Chronyd同步时间(Server/Client)配置
    代码签名
    paillier加密算法原理详解
    pip安装了包但pycharm里找不到(pip如何安装到conda下)
    苹果系统iOS、macOS应用管理机制
  • 原文地址:https://www.cnblogs.com/V1haoge/p/9615720.html
Copyright © 2020-2023  润新知