在Spring Boot的开发中,经常会有与其他业务系统对接或封装公共API接口等需求,为了使开发过程更加便捷性,并降低系统之间的耦合性,通常会使用自定义注解来封装一些业务,在后续开发时直接使用自定义注解即可,无需再关注注解内部业务流程的实现。本文将介绍如何在Spring Boot框架中实现自定义注解。

1 添加依赖

<!-- 引入aop -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2 创建注解类

项目结构如下:

spring boot自定义注解的编写 springboot怎么自定义注解_java


创建annotation包(自定义名称),在该包下创建注解类,右键annotation包,选择添加Java Class。此类的类型为@interface,在Kind处选择Annotation。

注解类常用注解的作用如下:

注解

参数类型

作用

@Target

ElementType

指定写入注解的合法位置。

@Retention

RetentionPolicy

指定注解的生存周期。

@Documented

表明由 javadoc记录,注释成为公共API的一部分。

@Inherited

允许子类继承父类中的注解。

常见的注解使用目标位置为:类、字段、方法等,ElementType枚举类型的定义如下:

public enum ElementType {
    /** 类, 接口 (包括注释类型), 或 枚举 声明 */
    TYPE,
 
    /** 字段声明(包括枚举常量) */
    FIELD,
 
    /** 方法声明(Method declaration) */
    METHOD,
 
    /** 正式的参数声明 */
    PARAMETER,
 
    /** 构造函数声明 */
    CONSTRUCTOR,
 
    /** 局部变量声明 */
    LOCAL_VARIABLE,
 
    /** 注释类型声明 */
    ANNOTATION_TYPE,
 
    /** 包声明 */
    PACKAGE,
 
    /**
     * 类型参数声明
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
 
    /**
     * 使用的类型
     *
     * @since 1.8
     */
    TYPE_USE
}

RetentionPolicy枚举类型的定义如下:

public enum RetentionPolicy {
    /**
     * 注释只在源代码级别保留,编译时被忽略
     */
    SOURCE,
    /**
     * 注释将被编译器在类文件中记录
     * 但在运行时不需要JVM保留。这是默认的
     * 行为.
     */
    CLASS,
    /**
     *注释将被编译器记录在类文件中
     *在运行时保留VM,因此可以反读。
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

注解类示例代码如下:

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCache {

    String name() default "";
    String action() default "";
    String key() default "";
    boolean filter() default false;
}

实现注解类之后,就可以开始在类或字段上使用自定义的注解了。示例如下:

public class User implements Serializable {

    private Long id;
    @MyCache(name = "user1")
    private String account;
    private String password;
    private Date createTime;
    private Date modifyTime;
    private Integer deleted;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getModifyTime() {
        return modifyTime;
    }

    public void setModifyTime(Date modifyTime) {
        this.modifyTime = modifyTime;
    }

    public Integer getDeleted() {
        return deleted;
    }

    public void setDeleted(Integer deleted) {
        this.deleted = deleted;
    }

    @Override
    public String toString() {
        return "id:" + id
                + ",account:" + account
                + ",password:" + password
                + ",createTime:" + createTime
                + ",modifyTime:" + modifyTime
                + ",deleted:" + deleted;
    }
}

字段加上注解后,可使用反射方法获取注解,实现相关业务逻辑。示例如下:

private void showName() {
        // 获取类的字段定义
        Field[] fields = User.class.getDeclaredFields();
        for (Field field : fields) {
            // 判断字段定义是否存在指定注解
            if (field.isAnnotationPresent(MyCache.class)) {
                // 允许获取字段信息
                field.setAccessible(true);
                // 获取注解属性的值
                String name = field.getAnnotation(MyCache.class).name();
                System.out.println("name: " + name);
            }
        }
    }

3 创建切面类
实现方法的注解时,除注解类外还需要创建切面类,用于实现被注解方法执行前后需要执行的业务逻辑。创建aspect包(自定义名称),在该包下创建切面类,右键aspect包,选择添加Aspect,Kind选择@Aspect。或直接创建一个Java Class,然后在类上方添加@Aspect注解。

切面类示例代码如下:

@Aspect
@Component
public class MyCacheAspect {
 
    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.cetc.cloud.annotation.MyCache)")
    public void annotationPointcut() {
    }
 
    /**
     * 方法开始执行之前
     */
    @Before("annotationPointcut()")
    public void doBeforeRunning (JoinPoint joinPoint) {
    
    }
 
 	/**
     * 方法开始执行之后
     */
    @After("annotationPointcut()")
    public void doAfterRunning (JoinPoint joinPoint) {
    
    }
 
 	/**
     * 当方法执行完返回结果时
     * returning属性指定方法参数的result拦截返回结果
     * 可获取或修改返回结果
     */
    @AfterReturning(value = "annotationPointcut()", returning = "result")
    public void doAfterReturning (JoinPoint joinPoint, Object result) {
    
    }
 
 	/**
     * 当方法执行异常时
     * throwing属性指定方法参数的e获取异常参数
     * 可查看异常信息
     */
    @AfterThrowing(value = "annotationPointcut()", throwing = "e")
    public void doAfterThrowing (JoinPoint joinPoint, Exception e) {
    
    }
 
 	/**
     * 切点环绕通知
     * 可跳过执行原方法
     */
    @Around("annotationPointcut()")
    public Object aroundPointcut (ProceedingJoinPoint joinPoint) throws Throwable {
        //原方法执行之前
        ...
        //执行原方法,可跳过
        Object obj = joinPoint.proceed();
        //原方法执行之后
        ...
        //返回方法执行结果
        return obj;
    }
}

实现注解类和切面类之后,就可以开始使用自定义的方法注解了。示例如下:

@Override
    @MyCache(name = "userCache", action = "query", key = "#user.account", filter = false)
    public User getUserByAccount(User user) {
        ...
    }

4 方法注解的执行顺序
切面类中几种注解的运行顺序如下:
@Around
@Before
原方法
@Around
@After
@AfterThrowing
@AfterReturning

当一个方法上有多个注解时,默认按照从上到下的顺序设置注解的Order,即注解执行顺序,Order小的注解先被触发。

注解即Spring AOP,Spring AOP基于面向切面编程思想,即所有注解和方法是一个同心圆,要执行的方法为圆心。最外层的Order最小,从最外层按照AOP1、AOP2的顺序依次执行Around方法、Before方法,然后执行原方法,最后按照AOP2、AOP1的顺序依次执行Around方法、After方法、AfterThrow方法、AfterReturn方法。也就是说对于多个AOP来说,先执行Before的,一定后执行After。见下图:

spring boot自定义注解的编写 springboot怎么自定义注解_后端_02


也可以自己指定AOP的执行顺序Order,在切面上使用@Order注解,如:

@Aspect
@Component
@Order(1)
public class Aspect1 {
 
}
@Aspect
@Component
@Order(2)
public class Aspect2 {
 
}