Java Annotations

1,380 阅读7分钟

Java Annotations

Java 从JDK1.5引入了注解,用于对元数据(metadata)的支持。通过注解可以对程序元素如类,成员方法,成员变量添加额外的补充信息。

​ 通过注解我们可以对程序元素进行注释说明,甚至改变其行为,不过需要我们对其进行相应的解析处理,否则它除了注释以外不会起到任何实际性的作用。

​ jdk通过java.lang.annotation包提供对注解的支持,注解类型其实是一种特殊的接口(interface)类型,所以不能同时定义相同名称的类,接口或注解类型,为了与接口类型区分,在接口interface前面加一个@符号,即@interface,所有的注解类型默认实现了java.lang.annotation.Annotation接口。

​ 此外,jdk预定义了一系列注解,按其作用可以将它们元注解(用于注解其他注解类型)与普通注解(用于注解其他程序元素),通过元注解我们还可以创建自定义注解,这与系统定义的普通注解没什么区别。

一.元注解

这类注解专门作用于对其他类型的注解,具有相同的Target@Target(ElementType.ANNOTATION_TYPE)修饰

  • @Documented

    @since 1.5: 通过@Documented注解的注解类型,可以通过javadoc工具提取其信息到修饰的程序元素API中。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }
    
  • @Target

    @since 1.5: 用于指定被修饰注解类型可以作用的范围。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        ElementType[] value();
    }
    

    所有范围定义在ElementType枚举当中。

    类型 作用
    TYPE 类,接口(包括注解类型),或枚举声明的地方
    FIELD 变量声明(包括枚举常量)的地方
    METHOD 方法声明的地方
    PARAMETER 形参声明的地方
    CONSTRUCTOR 构造方法声明的地方
    LOCAL_VARIABLE 本地变量声明的地方
    ANNOTATION_TYPE 注解类型声明的地方
    PACKAGE 包声明的地方
    TYPE_PARAMETER 类型参数声明的地方 @since 1.8
    TYPE_USE 类型使用的地方 @since 1.8
  • @Retention

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        RetentionPolicy value();
    }
    

    @since 1.5: 用于指定所修饰注解的保留范围,具体由RetentionPolicy指定。

    public enum RetentionPolicy {
        /** 仅在原文件保留,编译时被丢弃 */
        SOURCE,
      	/** 保留在生成的class文件当中,但不会被VM加载。注意:局部变量上的注解不会被编译进class文件 */
        CLASS,
        /** 保留在生成的class文件当中,并且在VM运行时保留,可以通过反射读取 */
        RUNTIME
    }
    
  • @Inherited

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }
    

    @since 1.5: 用于指定所修饰注解可以被它所注解的类的子类继承。例如注解@A注解了类Base,则它的子类Sub class也会被@A注解。

  • @Repeatable

    默认是不能有多个相同的annotation同时注解同一程序元素的。在jdk1.8以前,我们只能使用一个容器(元素是另一个annotation类型的数组)来间接达到相同的目的,如下面代码:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Author {
        String value();
    }
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Authors {
        Author[] value();
    }
    
    @Authors({@Author("Yannis Zhao"), @Author("Jack")})
    public class Demo {
    }
    

    jdk1.8为重复注解提供了支持,通过Repeatable注解,我们可以在程序元素上直接使用重复的注解,只需对上面代码稍加调整即可:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    /**
     * 指定可重复注解的容器注解类型
     */
    @Repeatable(Authors.class)
    public @interface Author {
        String value();
    }
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Authors {
        Author[] value();
    }
    
    /**
     * 现在就可以直接使用重复注解了
     */
    @Author("Yannis Zhao")
    @Author("Jack")
    public class Demo {
    }
    

    **注意:**Authors的保留范围要不能比Author的小,还有@Repeatable的value指定的annotation,其value返回必须是@Repeatable所注解的类型的数组

  • @Native

二.基本注解
  • @Override

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
    

    标记注解(Marker Annotation),指示子类重写或者了一个父类/接口的方法,编译器会检查父类,接口中有没有这样一个方法。

  • @Deprecated

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
    public @interface Deprecated {
    }
    
    

    标记一个程序元素已经过时,并将在后期的版本中删除。作者应该尽可能注明将在哪个版本中将其移除,并给出(如果有)替换方案。使用者不应该在新代码中使用它,而且应该尽快修改之前用到它的部分。

  • @SuppressWarning

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
        String[] value();
    }
    
    

    指定编译器取消其注解的元素及子元素上的编译警告,如泛型检查相关的警告

  • @Safevarargs

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
    public @interface SafeVarargs {}
    
    

    参数安全类型注解,jdk1.7引入,在jdk1.7后,编译器将会对泛型进行更严格的检查,防止发生类型转换异常。通过此注解来告诉编译器自己的代码是类型安全的,不要抛出类型检查警告。

  • @FunctionalInterface

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface FunctionalInterface {}
    
    

    函数式接口(接口中只有一个抽象方法)注解,jdk1.8引入,用于支持lambda表达式支持

三.自定义注解

​ 自定义注解与jdk中定义的如@Deprecated一样,只需要用@interface声明一个注解类型,并通过@Target指定其作用元素,@Retention指定其生存时间,@Document指定是否需要被javadoc提取即可。

​ 此外,注解还可以包含成员,类型可以是基本类型,String,Class,enum,Annotation,数组。并且每个成员还可以指定默认值。比如@Resource注解

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    String lookup() default "";
    Class<?> type() default java.lang.Object.class;
  
    enum AuthenticationType {
            CONTAINER,
            APPLICATION
    }
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
}

四.注解解析
  • 源代码

    ​ 对应RetentionPolicy.SOURCE,jdk并没有提供这种注解相应的解析方式,不过一些第三方工具提供了一些注解及其解析以帮助对代码进行检查。

  • 编译时

    ​ 编译时处理注解会扫描所有文件,包括新生成的文件,直到没有新的要处理的文件。

    ​ 在jdk5中,通过一个APT(Annotation Process Tool)工具来在编译时进行解析的,用com.sun.mirror.*下一系列类来描述代码的静态结构。用户需要实现两个接口AnnotationProcessorFactory(注解处理器工厂)和AnnotationProcessor(注解处理器)。这种方式不仅实现起来繁琐,而且它是Oracle的私有实现。所以在jdk6中通过 JSR 269对其进行了规范。提供了javax.annotation.processing(处理API)和javax.lang.model(Mirror API),并可以通过javac处理。

    ​ 在jdk6中,只需实现javax.annotation.processing.Processor或者一般继承抽象类javax.annotation.processing.AbstractProcessor即可。通过编译时处理,可以生成新的文件,还可以修改类的AST添加任何代码。

    关于编译时解析我会在另一篇文章中介绍

  • 运行时

    ​ 对于RetentionPolicy.RUNTIME的注解,编译器会将其编译进class文件的RuntimeVisibleAnnotations属性表中,所以可以通过Reflect API在运行时读取。运行时注解解析广泛应用在如Spring,mybatis等许多第三方框架中。

    ​ 这种方式主要涉及到一个接口AnnotatedElement, Class, Constructor,Method,Parameter等元素都实现了此接口,所以可以直接通过这些类调用AnnotatedElement接口中定义的相关方法获取其上的注解信息。

    public interface AnnotatedElement {
        /**
         * 判断元素是否被annotationClass注解
         */
        default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
            return getAnnotation(annotationClass) != null;
        }
    
       /**
         * 返回该元素上指定类型的注解对象
         */
        <T extends Annotation> T getAnnotation(Class<T> annotationClass);
    
        /**
         * 返回该元素上的所有注解
         */
        Annotation[] getAnnotations();
    
        /**
         * 1.8新增,获取指定类型的注解,并且会处理可重复注解
         */
        default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
             ...
         }
    
        /**
         * 1.8新增,返回且只返回直接注解在该元素上的注解,没有返回null
         */
        default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
             ...
         }
    
        /**
         * 1.8新增,返回且只返回直接注解在该元素上的注解,并且会处理可重复注解,没有返回空数组
         */
        default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
            ...
        }
    
        /**
         * 返回所有直接注解在该元素上的注解
         */
        Annotation[] getDeclaredAnnotations();
    }
    
    

    举个简单🌰,通过一个注解来实现后台操作日志的统一纪录:

    定义日志注解:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AdminLog {
    
        LogType type();
        String desc() default "";
    
        enum LogType {
            ADD(1), EDIT(2), DELETE(3);
    
            private int type;
    
            LogType(int type) {
                this.type = type;
            }
        }
    }
    
    

    在Controller方法上声明注解:

    @RequestMapping("addUser")
    @AdminLog(type = LogType.ADD, desc = "Add User")
    public String addUser(String name, Integer age) {
        return name + ":" + age;
    }
    
    

    在Spring拦截器中处理注解:

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
        ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    
        if (handler instanceof HandlerMethod) {
    
            HandlerMethod hm = (HandlerMethod) handler;
            Method method = hm.getMethod();
            AdminLog annotation = method.getAnnotation(AdminLog.class);
            if (annotation != null) {
                // write db
                System.out.println(annotation.type());
                System.out.println(annotation.desc());
                Map<String, String[]> parameterMap = request.getParameterMap();
                System.out.println(JSON.toJSONString(parameterMap));
                System.out.println(response.getStatus());
            }
    
        }
    }
    
    

所有文章微信公众号第一时间更新