• Java 注解 初探 (一)


      自JDK1.5之后,就开始出现注解。想要了解注解的来源和注解的用法,通过搜索引擎大都是针对某一个注解的解释,很难找到关于注解系列的文章,便自己看下。

      基于Annotation的注释,说明Annotaion是所有注解类型扩展的公共接口。当自定义为@inferface便实现该接口。可用 instanceof去校验@interface是否属于Annotation.而上面的Target属于注解其它的注解的元注解。元注解有四个:Target、Retention、Documented、

    Inherited。

      Target

        TYPE: 类
        FIELD: 字段
        METHOD: 方法
        PARAMETER: 参数
        CONSTRUCTOR: 构造函数
        LOCAL_VARIABLE:本地变量
        ANNOTATION_TYPE:注解
        PACKAGE:包

      现在演示一下Target

      自定一个注解,作用于FIELD的:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface AnnotationTarget {
        String[] value() default "iamsb";
    }

      被标记的字段拥有了超能力,是的,其它的字段仍然很傻,以下是被标记的字段赋予超能力的过程

    public class AnnotationTargetProcessor {
        
        public static void process(Class clazz, Object obj) {
            Field[] fields = clazz.getDeclaredFields();
            for(Field field : fields) {
                if(field.isAnnotationPresent(AnnotationTarget.class)){
                    try {
                        field.setAccessible(true);   // 被注解的字段是private ,所以设置true
                        field.set(obj, "now,i am still sb");
                    } catch (IllegalArgumentException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
    }

      最后的测试类

      

    public class SBVO {
        private String a;
        
        private String b;
        
        @AnnotationTarget("zz")
        private String c;
        
        private Integer d;
        
        private Boolean  bool = false;
        
        public static void main(String[] args) {
            SBVO sv = new SBVO();
            AnnotationTargetProcessor.process(SBVO.class,sv);
            String c = sv.c;
            String b = sv.b;
            System.out.println( "c ==== " + c);  //  c ==== now,i am still sb
            System.out.println( "b ==== " + b); //   b ==== null
        }
    }
    说明:1.@AnnotationTarget声明的是@Target(ElementType.FIELD),所以在用注解的时候,只能作用于字段上,而当你把@AnnotationTarget("zz")想放在类上面时,编译都不让你过,提示:The annotation @AnnotationTarget is disallowed for this location
       2.注解本身不具备超能力,它本身属于被动的。而是我们的规则处理类发现某个元素有某种注解,就按照某种规则来给这个元素赋予某种能力。比如我上面的AnnotationTargetProcessor扫描到字段c 有个AnnotationTarget注解,那我就给与你什么能力。同理,springboot的
        某个类(目前不知道是哪个)发现有个类被标记了@Service,那我就把它来赋予Service能力。
       3.@interface里面只能定义返回类型为 所有基本类型、String、enum、Annotation、Class和 它们的数组。
       4.用注解的时候,跟我们调用方法一样 注解名(元素名=元素值),比如我们springboot的启动类 @SpringBootApplication(scanBasePackages = "xxxx",exclude={xxx.class,yyy.class}) ,可以查到SpringBootApplication注解下scanBasePackages和exclude
        元素。
       5.@AnnotationTarget("zz")这里要说一下,这个原本是这样:@AnnotationTarget("value=zz"),如果声明的注解里只对value赋值,那么在引用的时候value=可以省略。

      接着再看下元注解

      Documented

      它没有元素,所以也被称为标记注解,我在SBVO中添加一行代码:

     

    @AnnotationTarget("nc")
    public static final String e = "SPRING SUMMER"; 

      然后用javadoc  -encoding UTF-8 AnnotationTarget.java SBVO.java 生成文档,这里我的类里面有中文,所以添加了utf8方式,生成的doc文档,入口是index.html,见下图:

      就是标红椭圆的注解是否被添加到doc文档的差别。如果没有添加元注解Documented,这里是没有红色椭圆的文本信息的。所以定义了Documented的自定义注解,在生成javadoc文档时,就会添加该注解文本信息。

      接着看

      Retention

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         */
        SOURCE,
    
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         */
        CLASS,
    
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         *
         * @see java.lang.reflect.AnnotatedElement
         */
        RUNTIME
    }

      1. SOURCE ,编译器将丢弃注解

      2. CLASS,编译器将在类文件中记录注解,但是不需要在运行时被VM保留。这是默认值的行为。

      3. RUNTIME,编译器将在类文件中记录注解,并且在运行时被VM保留。因此在反射时可以读取得到它们。

      作何解释呢?我先把我自定义注解AnnotationTarget的保留策略改成RetentionPolicy.SOURCE

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface AnnotationTarget {
        String[] value() default "iamsb";
    }

      仍然用下面这个类来引用它

    public class SBVO {
        private String a;
        
        private String b;
        
        @AnnotationTarget("zz")
        private String c;
        
        private Integer d;
        
        private Boolean  bool = false;
        
        @AnnotationTarget("nc")
        public static final String e = "SPRING SUMMER"; 
        
        @AnnotationTarget("")
        private String f = "i do not want to be a sb.";
        
        public static void main(String[] args) {
            SBVO sv = new SBVO();
            AnnotationTargetProcessor.process(SBVO.class,sv);
            String c = sv.c;
            String b = sv.b;
            String f = sv.f;
            System.out.println( "c ==== " + c);
            System.out.println( "b ==== " + b);
            System.out.println( "e ==== " + e);
            System.out.println( "f ==== " + f);
        }
    }

      经过编译后,把引用的SBVO.class反编译,可以看到

    public class SBVO
    {
      private String a;
      private String b;
      private String c;                                   // 未保留
      private Integer d;
      private Boolean bool = Boolean.valueOf(false);
      public static final String e = "SPRING SUMMER";     // 未保留
      private String f = "i do not want to be a sb.";     // 未保留
      
      public static void main(String[] args)
      {
        SBVO sv = new SBVO();
        AnnotationTargetProcessor.process(SBVO.class, sv);
        String c = sv.c;
        String b = sv.b;
        String f = sv.f;
        System.out.println("c ==== " + c);
        System.out.println("b ==== " + b);
        System.out.println("e ==== SPRING SUMMER");
        System.out.println("f ==== " + f);
      }
    }

      这里就解释了策略为SOURCE时,编译后就把注解丢弃了。

      当我把策略改成RetentionPolicy.CLASS,编译后再来看SBVO.class

    public class SBVO
    {
      private String a;
      private String b;
      @AnnotationTarget({"zz"})
      private String c;                                  // 保留了注解
      private Integer d;
      private Boolean bool = Boolean.valueOf(false);
      @AnnotationTarget({"nc"})
      public static final String e = "SPRING SUMMER";    // 保留了注解
      @AnnotationTarget({""})
      private String f = "i do not want to be a sb.";    // 保留了注解
      
      public static void main(String[] args)
      {
        SBVO sv = new SBVO();
        AnnotationTargetProcessor.process(SBVO.class, sv);
        String c = sv.c;
        String b = sv.b;
        String f = sv.f;
        System.out.println("c ==== " + c);
        System.out.println("b ==== " + b);
        System.out.println("e ==== SPRING SUMMER");
        System.out.println("f ==== " + f);
      }
    }

      此时我来运行SBVO的main 方法,得到结果:

        c ==== null
      b ==== null
      e ==== SPRING SUMMER
      f ==== i do not want to be a sb.

      此时main方法里面的AnnotationTargetProcessor.process(SBVO.class, sv); 其中process方法if(field.isAnnotationPresent(AnnotationTarget.class)) 判断始终是false,反射取不到,说明注解没在虚拟机中驻留。所以,c、b、e、f都是原值,那我在把自定义注解

    AnnotationTarget策略更改成RetentionPolicy.RUNTIME,首先注解时保留在SBVO.class文件中的
      @AnnotationTarget({"zz"})
      private String c;                                 // 保留
      private Integer d;
      private Boolean bool = Boolean.valueOf(false);
      @AnnotationTarget({"nc"})
      public static final String e = "SPRING SUMMER";   // 保留
      @AnnotationTarget({""})
      private String f = "i do not want to be a sb.";   // 保留

       然后,运行SBVO中的main方法,结果为:

      java.lang.IllegalAccessException: Can not set static final java.lang.String field studiii.zlsj_test.annotation.target.SBVO.e to java.lang.String
            at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(Unknown Source)
            at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(Unknown Source)
            at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(Unknown Source)
            at java.lang.reflect.Field.set(Unknown Source)
            at studiii.zlsj_test.annotation.target.AnnotationTargetProcessor.process(AnnotationTargetProcessor.java:19)
            at studiii.zlsj_test.annotation.target.SBVO.main(SBVO.java:30)
      c ==== now,i am still sb                          // c 由空值改成当前值
      b ==== null                                             // b 未添加注解
      e ==== SPRING SUMMER                    // final 改不了,上面也抛异常了
      f ==== now,i am still sb                          // f 值由 i do not want to be a sb.改成 now,i am still sb

    此时看到AnnotationTargetProcessor.process通过反射把标记了注解的字段都更改了,e标记了注解,但e是final变量,所以当想要更改它时,是不允许的。

    接着看最后一个
      Inherited
      从它的源码可以看到,它没有定义任何元素,所以它和Documented一样,属于标记注解。再来看一下它的注释:
    /**
     * Indicates that an annotation type is automatically inherited.  If
     * an Inherited meta-annotation is present on an annotation type
     * declaration, and the user queries the annotation type on a class
     * declaration, and the class declaration has no annotation for this type,
     * then the class's superclass will automatically be queried for the
     * annotation type.  This process will be repeated until an annotation for this
     * type is found, or the top of the class hierarchy (Object)
     * is reached.  If no superclass has an annotation for this type, then
     * the query will indicate that the class in question has no such annotation.
     *
     * <p>Note that this meta-annotation type has no effect if the annotated
     * type is used to annotate anything other than a class.  Note also
     * that this meta-annotation only causes annotations to be inherited
     * from superclasses; annotations on implemented interfaces have no
     * effect.
     *
     * @author  Joshua Bloch
     * @since 1.5
     * @jls 9.6.3.3 @Inherited
     */
    
    

      翻译一下:表明它是一个自动继承的注解类。如果一个声明的注解类(自定义注解)上存在这个继承元注解,并且用户去查找某个声明类上的自定义注解,并且这个声明类没有这个自定义注解,那么将自动去搜索这个类的父类的自定义注解。这个过程将

           重复,直到这个自定义注解被找到,或者搜索直到这个声明类的最顶层Object类。如果没有父类(这里指父、祖父、曾祖父直到Object)含有自定义注解,那么就表明这个声明类没有这个自定义注解。

           注意,如果自定义注解不是注释在一个类上的话,这个元注解(指Inherited)是没有效果的。还注意,这个元注解只会导致注解被继承自超类,实现接口上的注解是无效的。

      再翻译一下:1.这是一个自动继承的元注解,它是注解自定义注解的注解。

            2.如果某个类的父类、或者祖父类等有@Inherited注解或含有@Inherited的自定义注解,那么子类便可继承父类的注解。

            3.该注解只对类有效,对接口实现是无效的。

      例子:

      创建一个注解,标记Inherited

    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface InheAnnoTest {
        String value() default "ni da ye";
    }

      创建一个父类

    @InheAnnoTest("Just Parent")
    public class ParentClass {
        
        private String name ;
        
        @AnnotationTarget("yes, i am 15")
        String age;
        
        @MethodTestAnno("yeah")
        public void attack() {
            
        }
        @InheAnnoTest("ojbk")
        protected String plus() {
            return "";
        }
    }

      创建一个子类

    public class ChildClass extends ParentClass {
        
        private String name ;
        
        String age;
        
        public void attack() {
            
        }
        
        protected String plus() {
            return "plus";
        }
        
    }

      测试类

    public class Test {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Class<ChildClass> cc = ChildClass.class;
            Class<ParentClass> pc = ParentClass.class;
            System.out.println(pc.isAnnotationPresent(InheAnnoTest.class));        // true
            System.out.println(cc.isAnnotationPresent(InheAnnoTest.class));        // true
            Annotation[]  annos = cc.getAnnotations();
            for(Annotation anno : annos) {
                System.out.println(anno.annotationType().getName());               // studiii.zlsj_test.annotation.inheritedTest.InheAnnoTest
            }
        }
    
    }

      如果ChildClass和ParentClass 无继承关系,那么

        System.out.println(cc.isAnnotationPresent(InheAnnoTest.class));         // false
     并且 annos 就为空了。
     再来验证接口,创建一个接口:
      
    @InheAnnoTest("")                                                            // 打上了标记
    public interface ParentInterface {
        
        public static final String name = "daye";
        
        void atack();
    }

      实现类:

    public class ChildInterface implements ParentInterface{
    
        @Override
        public void atack() {
        
        }
    }

      得到结果:

    Class<ChildInterface> ci = ChildInterface.class;
    System.out.println(ci.isAnnotationPresent(InheAnnoTest.class));                      // false
      因此,就印证了Inherited注释里得说明。
      总结一下:注解Annotation 从JDK1.5之后被引入。它包含了4个元注解:Tagert、Documented、 Retention、 Inherited,元注解的意思就是可以注解其它注解的注解。就好比我们拼音有6个元音 a、o、e、i、u、u(..),可以组成很多双韵母,比如ai、ei、ui、ao、ou等等,进而拼出
           中华上下五千年沉淀的各种汉字。同理,4个元注解也可衍生出各种功能的注解。
           Tagert注解指明你定义的注解的作用域,是Field、Method还是Type等。
           Documented注解表示通过javadoc后,会再doc中显示出来。
           Retention注解,表示注解的保留策略,仅有三种,1.编译时就把注解丢弃。2.编译时保留,但再JVM中不保留。3.编译时保留,JVM中也保留,反射可从JVM中取到该注解。
          
    Inherited注解,表示子类可以取到父类中定义了该注解的注解。




     
  • 相关阅读:
    git工具命令整理
    使用nodeJs操作redis
    electron 7.x 设置开发环境与生产模式 隐藏菜单栏和开发者工具 devtools
    electron 打包问题 解决
    sso单点登录之跨域cookie共享 (跨域缓存共享)
    浏览器线程执行顺序
    JS如何将变量作为一个对象的Key
    DevOps流程的简单总结
    通过key 寻找数组内对象的某一项
    根据key查找对象数组中符合的一项 返回对象(递归)
  • 原文地址:https://www.cnblogs.com/lioa/p/9645350.html
Copyright © 2020-2023  润新知