springbootweb

Spring基础系列--AOP实践

2019-02-13  本文已影响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. 对于上面执行的一点补充:
  1. 终上所述,推荐不要将环绕通知和其他通知一起使用。因为环绕通知会导致一些异常的情况,使其他通知的部分功能失效。

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

上一篇下一篇

猜你喜欢

热点阅读