java注解学习

前言 注解(Annotation)很重要,未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,现在已经有不少的人开始用注解了,注解是JDK1.5之后才有的新特性
JDK1.5之后内部提供的三个注解
@Deprecated 意思是“废弃的,过时的”
@Override 意思是“重写、覆盖”
@SuppressWarnings 意思是“压缩警告”
注解的定义 Java 官方文档对应注解的描述:注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解与元与注解 元注解就是注解的注解;用于对注解的使用条件进行限定
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 元素。
@Inherited -如果一个父类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了父类的注解
【java注解学习】主要是@Retention和@Target
@Retention 标记注解的的存活时间。取值如下:
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。一般这类Annotation用作IDE或者Compiler的检查,比如@Override。
RetentionPolicy.CLASS 默认值,注解只被保留到编译进行的时候,在class文件中可用,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以通过反射获取到它们。
注:从这里看,注解可以做很多事情,编译时检查错误,自动生成代码;运行时通过反射赋值,注入操作。
@Target:标记这个注解可以应用到哪种 Java 元素。取值如下:
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
ElementType.TYPE_PARAMETER,标明注解可以用于类型参数声明(1.8新加入)
ElementType.TYPE_USE 类型使用声明(1.8新加入)
注解的属性 注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型

@Retention(RetentionPolicy.RUNTIME) //元注解 @Target({ElementType.METHOD,ElementType.TYPE}) //元注解 //新建一个Annotation 注解类 public @interface MyAnnotation { //可以为注解类添加属性 /*注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明, * 其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型 * */ String name() default "weijuncheng"; String id() default "1"; String test(); /* * 在注解中定义属性时它的类型必须是 8 种基本数据类型(int,float,boolean,byte,double,char,long,short) * 和(String,Class,enum,Annotation,各类型的数组 * 注意添加的类型必须是注解类型 * */}

@Option注解中定义了多个属性。在注解中定义属性时它的类型必须是 8 种基本数据类型(int,float,boolean,byte,double,char,long,short)和(String,Class,enum,Annotation,各类型的数组)。注解中属性可以有默认值,默认值需要用 default 关键值指定。在使用的时候,我们应该给它们进行赋值
获取注解的相关方法
// 元素上是否存在指定类型的注解 public boolean isAnnotationPresent(Class annotationClass) {} // 元素上如果存在指定类型的注解,则返回Annotation 对象,否则返回 null。 publicA getAnnotation(Class annotationClass) {} // 返回此元素上存在的所有注解,包括从父类继承的 public Annotation[] getAnnotations() {} // 返回直接存在于此元素上的所有注解, // 注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组。 public Annotation[] getDeclaredAnnotations() {}

常见注解
@Documented @Retention(value=https://www.it610.com/article/RUNTIME) public @interface Deprecated

@Target(value=https://www.it610.com/article/METHOD) @Retention(value=SOURCE) public @interface Override

@Target(value=https://www.it610.com/article/{TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE}) @Retention(value=SOURCE) public @interface SuppressWarnings

示例
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) //元注解 /* RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。一般这类Annotation用作IDE或者Compiler的检查,比如@Override。 RetentionPolicy.CLASS 默认值,注解只被保留到编译进行的时候,在class文件中可用,它并不会被加载到 JVM 中。 RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以通过反射获取到它们。 * */@Target({ElementType.METHOD,ElementType.TYPE}) //元注解 /* * ElementType.ANNOTATION_TYPE 可以给一个注解进行注解 ElementType.CONSTRUCTOR 可以给构造方法进行注解 ElementType.FIELD 可以给属性进行注解 ElementType.LOCAL_VARIABLE 可以给局部变量进行注解 ElementType.METHOD 可以给方法进行注解 ElementType.PACKAGE 可以给一个包进行注解 ElementType.PARAMETER 可以给一个方法内的参数进行注解 ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举 ElementType.TYPE_PARAMETER,标明注解可以用于类型参数声明(1.8新加入) ElementType.TYPE_USE 类型使用声明(1.8新加入) * */ //新建一个Annotation 注解类 public @interface MyAnnotation { //可以为注解类添加属性 /*注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明, * 其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型 * */ String name() default "weijuncheng"; String id() default "1"; String test(); /* * 在注解中定义属性时它的类型必须是 8 种基本数据类型(int,float,boolean,byte,double,char,long,short) * 和(String,Class,enum,Annotation,各类型的数组 * 注意添加的类型必须是注解类型 * */}

import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface SimulateHiddenAPI {}

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface FieldAnnotation { String s1() default "s1"; String s2() default "s2"; }

import java.lang.reflect.Field; public class SetFieldofTestAnnotation {public static void changeFieldofTestAnnotation() { try { Field f1 = TestAnnotation.class.getDeclaredField("s1"); /* getDeclaredField是可以获取一个类的所有字段. getField只能获取类的public 字段. * */ if(f1.isAnnotationPresent(FieldAnnotation.class)) { f1.setAccessible(true); try { f1.set(String.class, f1.getAnnotation(FieldAnnotation.class).s1()); } catch (IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (NoSuchFieldException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); }try { Field f2 = TestAnnotation.class.getDeclaredField("s2"); if(f2.isAnnotationPresent(FieldAnnotation.class)) { f2.setAccessible(true); try { f2.set(String.class, f2.getAnnotation(FieldAnnotation.class).s2()); } catch (IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (NoSuchFieldException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); }}}

import java.lang.reflect.Field; @MyAnnotation(id="2", test = "") //自定义注解 //给了id的值为2,没给就是默认的值 //即没在这里赋值,又没给默认值就会报错 public class TestAnnotation {@FieldAnnotation private static String s1 = "o1"; @FieldAnnotation private static String s2 = "o2"; @SimulateHiddenAPI public static void test1() { try { if(TestAnnotation.class.getDeclaredMethod("test1").isAnnotationPresent(SimulateHiddenAPI.class)) { System.out.println("test1 禁止调用"); } else { System.out.println("test1 正常调用"); } } catch (NoSuchMethodException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } }public static void test2() { try { if(TestAnnotation.class.getDeclaredMethod("test2").isAnnotationPresent(SimulateHiddenAPI.class)) { System.out.println("test2 禁止调用"); } else { System.out.println("test2 正常调用"); } } catch (NoSuchMethodException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } }public static void main(String[] args) { // TODO Auto-generated method stub /* // 元素上是否存在指定类型的注解 public boolean isAnnotationPresent(Class annotationClass) {} // 元素上如果存在指定类型的注解,则返回Annotation 对象,否则返回 null。 publicA getAnnotation(Class annotationClass) {} // 返回此元素上存在的所有注解,包括从父类继承的 public Annotation[] getAnnotations() {} // 返回直接存在于此元素上的所有注解, // 注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组。 public Annotation[] getDeclaredAnnotations() {} */ if(TestAnnotation.class.isAnnotationPresent(MyAnnotation.class)) { //元素上存在一个注解类型,则返回一个注解对象 MyAnnotation annotation = (MyAnnotation)TestAnnotation.class.getAnnotation(MyAnnotation.class); System.out.println("----------------->annotation = "+annotation); System.out.println("----------------->annotation.id = "+annotation.id()); System.out.println("----------------->annotation.name = "+annotation.name()); System.out.println("----------------->annotation.test = "+annotation.test()); //对于method的注解可以改变method的执行流程 test1(); test2(); System.out.println(s1); System.out.println(s2); SetFieldofTestAnnotation.changeFieldofTestAnnotation(); //注解+反射 //要修改的值通过注解得到,然后通过反射来修改这些值System.out.println(s1); System.out.println(s2); } }}

----------------->annotation = @MyAnnotation(name=weijuncheng, id=2, test=) ----------------->annotation.id = 2 ----------------->annotation.name = weijuncheng ----------------->annotation.test = test1 禁止调用 test2 正常调用 o1 o2 s1 s2

从中可以看到获取注解的方法,通过注解改变执行方式,通过注解提供的值+反射来改变类中的私有属性(常见)
注解的使用场景 注解有许多用处,主要如下:
提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
值得注意的是,注解不是代码本身的一部分。注解只是某些工具的的工具。注解主要针对的是编译器和其它工具软件(SoftWare tool)。当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)
工作中遇到 CTS框架就用来这个方法,将提供的属性值(config文件)通过注解的方式改变框架中相应数据结构中的值,具体见OptionSetter.java
延伸学习 注解应用实例
  • butterknife 基于Android的视图依赖注入框架,其原理是使用编译时处理注解生成相关辅助代码,在运行时进行辅助类的加载从而调用相关方法完成视图的注入。
  • Dagger2 依赖注入的框架,把类实例的初始化过程都挪至一个容器中统一管理,具体需要哪个实例时,就从这个容器取出。原理是通过注解建立起实例需求端,实例容器,实例供应端的对应关系,在运行时将初始化好的实例注入到需求端。
  • EventBus 一个简化Andorid、Fragment、Threads、Service之间信息传递的一个发布/订阅事件集。原理是在运行时通过注解将发布的消息,分发给各个订阅者。
  • JUnit4 Java单元测试框架
总结 个人理解:注解有点类似于接口和抽象类的结合,一般用于很多类共享的属性和方法,节省工作效率;目前一般都用在框架工具的开发上

    推荐阅读