美文网首页springbootweb
Spring基础系列--AOP实践

Spring基础系列--AOP实践

作者: 唯一浩哥 | 来源:发表于2019-02-13 21:24 被阅读2次

    原创文章,转载请标注出处:《Spring基础系列--AOP实践》


    实践

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

    推荐使用IDEA + Spring Boot。

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

    创建完成后,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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>spring-aspect-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>spring-aspect-demo</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.4.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </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>
    

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

    @Component
    public class AspectDemo {
        public Integer test(String s){
            System.out.println("目标方法执行-"+s);
            return 123321;
        }
    }
    

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

    下面我们创建切面类:

    @Aspect
    @Component
    public class AspectTest {
    
        @Pointcut(value = "execution(* *.test(..)) && args(s)")
        public void pc(String s){
            System.out.println("切点执行");
        }
    
        @Before("pc(s)")
        public void beforeTest(JoinPoint jp,String s){
            System.out.println("前置通知-arg="+s);
        }
    
        @After("pc(s)")
        public void afterTest(String s){
            System.out.println("后置终点通知-arg="+s);
        }
    
        @AfterReturning(pointcut = "pc(s)", returning = "i")
        public void afterReturningTest(Object i,String s){
            System.out.println("后置返回通知-return="+i+"-arg="+s);
        }
    
        @AfterThrowing(pointcut = "pc(s)",throwing = "e")
        public void afterThrowingTest(Exception e,String s){
            System.out.println("后置异常通知-"+e.getMessage()+"-arg="+s);
        }
    
        @Around("pc(s)")
        public void aroundTest(ProceedingJoinPoint jp,String s){
            System.out.println("环绕前置通知-arg="+s);
            Object[] os = jp.getArgs();
            s = "caocao";
            os[0] = s;
            try {
                jp.proceed(os);
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            System.out.println("环绕后置通知-arg="+s);
        }
    }
    

    创建测试用例:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @EnableAspectJAutoProxy
    public class SpringAspectDemoApplicationTests {
        @Autowired
        private AspectDemo aspectDemo;
    
        @Test
        public void aspecctTest(){
            aspectDemo.test("huahua");
        }
    }
    

    执行结果:

    环绕前置通知-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. 我们还可以从上面的执行结果看到各个通知的执行顺序:
    环绕通知前置部分--->前置通知--->目标方法--->环绕通知后置部分--->后置终点通知--->后置返回通知--->后置异常通知
    
    1. 对于上面执行的一点补充:
    • 那就是发生异常的情况,如果在环绕通知前置部分发生异常,那么之后除了后置终点通知是必然执行的外,只有最后的异常通知会被触发,其余一概不会执行。
    • 如果第5点中执行顺序哪一步发生了异常,那么其前面的通知会正常执行,后面的除了后置终点通知一定会执行外,异常通知也回被触发。
    • 但有一个例外,那就是前置通知,在前置通知和环绕通知同时作用于一个目标方法时,前置通知的异常将不会被后置异常通知捕捉到。
    1. 终上所述,推荐不要将环绕通知和其他通知一起使用。因为环绕通知会导致一些异常的情况,使其他通知的部分功能失效。

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

    相关文章

      网友评论

        本文标题:Spring基础系列--AOP实践

        本文链接:https://www.haomeiwen.com/subject/ztjjeqtx.html