Java Annotations

Java Annotations

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

​ 经过注解咱们能够对程序元素进行注释说明,甚至改变其行为,不过须要咱们对其进行相应的解析处理,不然它除了注释之外不会起到任何实际性的做用。数组

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

​ 此外,jdk预约义了一系列注解,按其做用能够将它们元注解(用于注解其余注解类型)与普通注解(用于注解其余程序元素),经过元注解咱们还能够建立自定义注解,这与系统定义的普通注解没什么区别。微信

一.元注解

这类注解专门做用于对其余类型的注解,具备相同的Target@Target(ElementType.ANNOTATION_TYPE)修饰mybatis

  • @Documentedapp

    @since 1.5: 经过@Documented注解的注解类型,能够经过javadoc工具提取其信息到修饰的程序元素API中。框架

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }
    复制代码
  • @Targetide

    @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());
            }
    
        }
    }
    
    复制代码

全部文章微信公众号第一时间更新

相关文章
相关标签/搜索