Spring Boot:使用 @Aspect 定义切面


#Spring Boot#


示例1 - 基础用法

项目结构

demo01 项目结构:

demo01
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AspectConfig.java
    │   │       ├── Demo01Application.java
    │   │       └── SayHello.java
    │   └── resources
    └── test
        ├── java
        └── resources

build.gradle 中的依赖:

compile('org.springframework.boot:spring-boot-starter')
compile('org.springframework.boot:spring-boot-starter-aop')
compile group: 'org.projectlombok', name: 'lombok', version: '1.18.0'

各个类的代码:

package demo;

import org.springframework.stereotype.Component;

@Component
public class SayHello {

    public String hi(String msg) {
        return "Hi " + msg;
    }

}
package demo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AspectConfig {

    // 定义切点(切入位置)
    @Pointcut("execution(* demo.SayHello.*(..))")
    private void pointcut(){}

    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("我是前置通知");
    }

    @AfterReturning(value="pointcut()", returning = "returnVal")  // 别忘了returning参数
    public void afterReturning(JoinPoint joinPoint, Object returnVal){
        System.out.println("我是后置通知...,收到的returnVal: " + returnVal);
    }

    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("最终通知....");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("我是环绕通知前....");
        //执行目标函数
        Object obj= (Object) joinPoint.proceed();
        System.out.println("我是环绕通知后....");
        if (obj instanceof String) {
            return "我被切面修改了";
        }
        return obj;
    }

    @AfterThrowing(value="pointcut()", throwing="throwable")   // 别忘了throwing参数
    public void afterThrowing(JoinPoint joinPoint, Throwable throwable){
        System.out.println("异常通知:"+ throwable.getMessage());
    }

}

package demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class Demo01Application implements CommandLineRunner {

    @Autowired
    private SayHello sayHello;

    @Override
    public void run(String... args) throws Exception {
        System.out.println("result: " + sayHello.hi("World"));
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo01Application.class, args);
    }

}

运行主类 Demo01Application ,输出:

我是环绕通知前....
我是前置通知
我是环绕通知后....
最终通知....
我是后置通知...,收到的returnVal: 我被切面修改了
result: 我被切面修改了

示例2:多个 @Aspect 和 @Order 配合使用

demo02 项目结构:

demo02
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AspectConfig01.java
    │   │       ├── AspectConfig02.java
    │   │       ├── Demo02Application.java
    │   │       └── SayHello.java
    │   └── resources
    └── test
        ├── java
        └── resources
package demo;

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;

@Aspect
@Order(1)
@Component
public class AspectConfig01 {

    // 定义切点(切入位置)
    @Pointcut("execution(* demo.SayHello.*(..))")
    private void pointcut(){}


    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AspectConfig01: 我是环绕通知前....");
        //执行目标函数
        Object obj= (Object) joinPoint.proceed();
        System.out.println("AspectConfig01: 我是环绕通知后....");
        if (obj instanceof String) {
            return "AspectConfig01: 我被切面修改了";
        }
        return obj;
    }

}
package demo;

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;

@Aspect
@Order(2)
@Component
public class AspectConfig02 {

    // 定义切点(切入位置)
    @Pointcut("execution(* demo.SayHello.*(..))")
    private void pointcut(){}


    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AspectConfig02: 我是环绕通知前....");
        //执行目标函数
        Object obj= (Object) joinPoint.proceed();
        System.out.println("AspectConfig02: 我是环绕通知后....");
        if (obj instanceof String) {
            return "AspectConfig02: 我被切面修改了";
        }
        return obj;
    }

}

执行主类 Demo02Application ,输出:

AspectConfig01: 我是环绕通知前....
AspectConfig02: 我是环绕通知前....
AspectConfig02: 我是环绕通知后....
AspectConfig01: 我是环绕通知后....
result: AspectConfig01: 我被切面修改了

示例3 - 对使用特定注解的方法使用切面

可以对某些注解「装饰」的类或者方法,织入切面。这个示例展示对注解「装饰」的方法织入切面。

demo03 项目结构:

demo03
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AspectConfig.java
    │   │       ├── Demo03Application.java
    │   │       ├── MyAnnotation.java
    │   │       └── SayHello.java
    │   └── resources
    └── test
        ├── java
        └── resources

注解 MyAnnotation :

package demo;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MyAnnotation {
    boolean value() default false;
}

类 SayHello :

package demo;

import org.springframework.stereotype.Component;

@Component
public class SayHello {

    @MyAnnotation  // 用自定义的注解修饰hi方法
    public String hi(String msg) {
        return "Hi , this is SayHello. " + msg;
    }

}

类 AspectConfig:

package demo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Order(1)
@Component
public class AspectConfig {

    // 定义切点(切入位置)
    @Pointcut("@annotation(demo.MyAnnotation)")
    private void pointcut(){}


    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
        System.out.println("AspectConfig: 注解中的value值:" + myAnnotation.value());

        System.out.println("AspectConfig: 我是环绕通知前....");
        //执行目标函数
        Object obj= (Object) joinPoint.proceed();
        System.out.println("AspectConfig: 我是环绕通知后....");
        if (obj instanceof String) {
            return "AspectConfig: 我被切面修改了";
        }
        return obj;
    }

}

主类:

package demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class Demo03Application implements CommandLineRunner {

    @Autowired
    private SayHello sayHello;

    @Override
    public void run(String... args) throws Exception {
        System.out.println("result: " + sayHello.hi("World"));
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo03Application.class, args);
    }

}

执行主类,输出:

AspectConfig: 注解中的value值:false
AspectConfig: 我是环绕通知前....
AspectConfig: 我是环绕通知后....
result: AspectConfig: 我被切面修改了

示例4 - 对使用特定注解的类的所有方法使用切面

这个示例展示对注解「装饰」的类中的所有方法织入切面。

项目结构:

demo04
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AspectConfig02.java
    │   │       ├── Demo04Application.java
    │   │       ├── MyAnnotation02.java
    │   │       └── SayHello.java
    │   └── resources
    └── test
        ├── java
        └── resources      

注解 MyAnnotation02 :

package demo;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyAnnotation02 {
    boolean value() default false;
}

注意,注解的@Target是ElementType.TYPE,只能用来修饰class。

用注解 MyAnnotation02 修饰 SayHello 类:

package demo;

import org.springframework.stereotype.Component;

@Component
@MyAnnotation02(value = true)
public class SayHello {

    public String hi(String msg) {
        return "Hi , this is SayHello. " + msg;
    }

}

类 AspectConfig02:

package demo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@Aspect
@Order(2)
@Component
public class AspectConfig02 {

    // 定义切点(切入位置)
    @Pointcut("within(@demo.MyAnnotation02 *)")
    private void pointcut(){}


    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {


        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取类的所有注解
        Annotation[] annotations = signature.getDeclaringType().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType() == MyAnnotation02.class) {
                System.out.println("AspectConfig02: 注解value:" + ((MyAnnotation02) annotation).value());
            }
        }

        System.out.println("AspectConfig02: 我是环绕通知前....");
        //执行目标函数
        Object obj= (Object) joinPoint.proceed();
        System.out.println("AspectConfig02: 我是环绕通知后....");
        if (obj instanceof String) {
            return "AspectConfig02: 我被切面修改了";
        }
        return obj;
    }

}

主类 Demo04Application:

package demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class Demo04Application implements CommandLineRunner {

    @Autowired
    private SayHello sayHello;

    @Override
    public void run(String... args) throws Exception {
        System.out.println("result: " + sayHello.hi("World"));
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo04Application.class, args);
    }

}

运行主类,输出:

AspectConfig02: 注解value:true
AspectConfig02: 我是环绕通知前....
AspectConfig02: 我是环绕通知后....
result: AspectConfig02: 我被切面修改了

( 本文完 )