java注解学习
前言
注解(Annotation)很重要,未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,现在已经有不少的人开始用注解了,注解是JDK1.5之后才有的新特性
JDK1.5之后内部提供的三个注解
@Deprecated 意思是“废弃的,过时的”
@Override 意思是“重写、覆盖”
@SuppressWarnings 意思是“压缩警告”
注解的定义
Java 官方文档对应注解的描述:注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解与元与注解
元注解就是注解的注解;用于对注解的使用条件进行限定
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 元素。
@Inherited -如果一个父类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了父类的注解
主要是@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。
public A getAnnotation(Class annotationClass) {}
// 返回此元素上存在的所有注解,包括从父类继承的
public Annotation[] getAnnotations() {}
// 返回直接存在于此元素上的所有注解,
// 注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组。
public Annotation[] getDeclaredAnnotations() {}
常见注解
@Documented
@Retention(value=RUNTIME)
public @interface Deprecated
@Target(value=METHOD)
@Retention(value=SOURCE)
public @interface Override
@Target(value={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。
public A 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单元测试框架
总结
个人理解:注解有点类似于接口和抽象类的结合,一般用于很多类共享的属性和方法,节省工作效率;目前一般都用在框架工具的开发上