在工作中,我们有时候需要将一些公共的功能封装,比如操作日志的存储,防重复提交等等。这些功能有些接口会用到,为了便于其他接口和方法的使用,做成自定义注解,侵入性更低一点。别人用的话直接注解就好。下面就来讲讲自定义注解这些事情。

一.@Target、@Retention、@Documented简介

java自定义注解的注解位于包:java.lang.annotation下。包含三个元注解@Target、@Retention、@Documented,即注解的注解。

@Target

@Target:注解的作用目标。和枚举ElementType共同起作用

根据源码知道,可以配置多个作用目标。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}
复制代码

ElementType的类型如下:

* @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 4.1 The Kinds of Types and Values
 */
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
}
复制代码

@Retention

指示带注解类型的注解要多长时间被保留。如果没有保留注释存在 注释类型声明,保留策略默认为 {@code RetentionPolicy.CLASS}和RetentionPolicy共同起作用。

RetentionPolicy类型如下:

package java.lang.annotation;

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
     */
    SOURCE,

    /**
     * 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
     */
    CLASS,

    /**
     * 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
     */
    RUNTIME
}

复制代码

这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。

那怎么来选择合适的注解生命周期呢?

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife,就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override@SuppressWarnings,则可选用 SOURCE 注解

**@**Documented

**@**Documented 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中,是一个标记注解,没有成员。

二.编写自定义注解

1.创建自定annotation包

当然,名字自定义

2.创建自定义注解

我这里以Log为例。这里是通过新建自定义注解创建的。也可以手动创建一个class类进行修改@interface呢。

public @interface test {
}
复制代码

3.加入元注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}
复制代码

4.编写AOP

@Aspect
@Component
public class SysLogAspect {

    /**
     * logger
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(SysLogAspect.class);


    @Pointcut("@annotation(com.aldeo.common.annotation.Log)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 目标方法
        Object result = point.proceed();
        long time = System.currentTimeMillis() - beginTime;
      
      Log syslog = method.getAnnotation(Log.class);
        if (syslog != null) {
            // 注解上的描述
            LOGGER.info(syslog.value());
        }
      
        // 保存日志
        try {
            saveLog(point, time);
        } catch (Exception e) {
            LOGGER.error("==================================> saveSysLog.around.exception: " + e.getMessage(), e);
        }
        return result;
    }
复制代码

三.使用

在需要注解的方法上加上注解

@Log("测试自定义注解")
public String restPassword(){
    return "成功";
}
复制代码

运行结果

2020-11-14 16:09:00.245 |-INFO  http-nio-6089-exec-9 c.v.t.*.40 -  gmzakixAkym3 : 测试自定义注解
复制代码

四.自定义注解使用场景和原理

自定义注解的原理,就是自己定义完注解。将注解加到需要注解的方法上。然后在拦截器拦截到注解的,然后进行后续的处理。

登陆、权限拦截、日志处理,以及各种 Java 框架,如 Spring,Hibernate,JUnit 提到注解就不能不说反射,Java 自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过 AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记录。反射的实现在 Java 应用层面上讲,是通过对 Class 对象的操作实现的,Class 对象为我们提供了一系列方法对类进行操作。在 JVM 这个角度来说,Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目按严格的顺序紧凑的排列在 Class 文件中,里面包含了类、方法、字段等等相关数据。通过对 Class 数据流的处理我们即可得到字段、方法等数据。