Java注解(Annotation)
Java注解需要配合反射来使用
一、注解的定义及其解释
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
1.快速理解定义
注解如同标签可以贴在各个地方
可供参考的博客
二、注解的语法
1.注解通过 @interface 关键字进行定义。
public @interface myAnnotation
{
}2.元注解及其使用
- 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
- 元注解有 @Retention、@Documented、@Target、@Inherited、@Repeatable5 种。@Retention: 定义注解的保留策略
 首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含 @Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得, @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target:定义注解的作用目标
源码为:
@Documented  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.ANNOTATION_TYPE)  
public @interface Target {  
    ElementType[] value();  
}  
@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR)  //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
@Repeatable :Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 的新特性
为什么注解需要可重复?
看现实世界中的例子:一个人可以贴上输入同一类型的多张标签。这样可以更加真实的反应现实世界中的事物(面向对象思想)
@interface Persons {
    Person[]  value();
}
@Repeatable(Persons.class)
@interface Person{
    String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="player")
public class Me{
}使用举例
定义带有元注解的注解
注解的属性用[返回值类型 ]名称 () 来定义;可以为其设置默认值
注意:只用名称为value的属性可以通过 @注解名(“xxxx”)为其赋值;一般通过@注解名(value=”xxxx”)来使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationOne {
	String value() default "an";
}使用(对某个类进行注解)
//以下两种方法等价(因为其有value这个属性所以可以这么写)
@AnnotationOne("ABCD")
public class TestgetAnnotation {
}
@AnnotationOne(value="ABCD")
public class TestgetAnnotation2 {
}三、注解的提取
需要反射机制作为基础;注解通过反射获取。
1.首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
2.然后通过 getAnnotation() 方法来获取 Annotation 对象。
 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
3.或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
举例
Check.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
	String an();
}AnnotationOne.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//@Target(ElementType.TYPE)//元注解 
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationOne {
	String value() default "an";
}TestgetAnnotation.java
import java.lang.reflect.Field;
@AnnotationOne("NMY")
public class TestgetAnnotation {
	@Check(an = "ABC")
	String aString="ABC";
	
	public static void main(String[] args) {
		// 通过反射方式(3中方式) 获取注解(注解的提取)
		// 通过Class类的静态方法 反射
		try {
			Class<?> cTestgetAnnotation = Class.forName("TestgetAnnotation");// 获取反射类
			boolean isAnnotation = cTestgetAnnotation.isAnnotationPresent(TestOne.class);// 通过反射类调用isAnnotation方法
			printAnnotation(isAnnotation);// 调用打印方法
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 2.通过类名反射
		boolean hasAnnotation = TestgetAnnotation.class.isAnnotationPresent(TestOne.class);
		printAnnotation(hasAnnotation);
		// 3.通过对象名.getClass()反射
		TestgetAnnotation testgetAnnotation = new TestgetAnnotation();
		boolean isAno = testgetAnnotation.getClass().isAnnotationPresent(TestOne.class);
		printAnnotation(isAno);
		//获取字段注解
		System.out.println("字段注解测试");
		testGetFieldAnnotation();
	}
	public static void printAnnotation(boolean isAn) {
		if (isAn) {
			TestOne testOne = TestgetAnnotation.class.getAnnotation(TestOne.class);
			System.out.println(testOne.value());
		} else {
			System.out.println("no");
		}
	}
	
	public static void testGetFieldAnnotation() {
		try {
			Class<?> cTestgetAnnotation = Class.forName("TestgetAnnotation");// 获取反射类
			Field field = cTestgetAnnotation.getDeclaredField("aString");
			field.setAccessible(true);
			Check fieldAnnotationWithString = field.getAnnotation(Check.class);
			if(fieldAnnotationWithString!=null) {
				//猜想的功能:用于检查该字段的内容 是否和注解提供的内容一致?
				Object obj = cTestgetAnnotation.newInstance();
				String string=(String)field.get(obj);
				System.out.println("annotation -String"+fieldAnnotationWithString.an());
				System.out.println("field - String"+string);
				if(fieldAnnotationWithString.an().equals(string)) {
					System.out.println("一致");
				}else {
					System.out.println("不一致");
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
				
	}
}运行结果
NMY
NMY
NMY
字段注解测试
annotation -StringABC
field - StringABC
一致
四、注解优点及其应用场景
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
注解应用实例
注解运用的地方太多了,因为我是 Android 开发者,所以我接触到的具体例子有下:
注解有什么用,给谁用?给 编译器或者 APT 用的。
注解应用实例
JUnit
JUnit 这个是一个测试框架,典型使用方法如下:
@Test 标记了要进行测试的方法 addition_isCorrect().
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}ButterKnife
ButterKnife 是 Android 开发中大名鼎鼎的 IOC 框架,它减少了大量重复的代码。
public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv_test)
    TextView mTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }Dagger2
也是一个很有名的依赖注入框架。
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}Retrofit
 Http 网络访问框架
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();
GitHubService service = retrofit.create(GitHubService.class);五、总结
1.注解的应用场景
- 生成doc文档; 
- 编译器类型格式检查; 
- 运行时处理如注入依赖等 - 2.自定义注解注意事项
- 注解的类型是@interface,里面只有以“无形参的方法”的形式来声明的成员变量。其方法名定义了成员变量的名字,返回值类型定义了该成员变量的类型。在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。 
- 注解可以使用default指定默认值,但只有一个成语变量时,其属性名为value,目标处使用时可省略该成员变量名的描述。 
- 需要用元注解 @Retention、@Documented、@Target、@Inherited来描述注解的生效时间、是否生成doc文档、使用目标位置,以及是否能被子类继承(注意只有类的注解可以被子类继承保留,方法及接口注解不会被实现类继承)。 
- 在JDK8中新增了@Repeatable 注解,表示容器注解,它的值是一个数组,该注解可以在目标位置使用多次,表示多个不同的值。 
- 通过反射获取注解属性值,并做相应的处理。 
 
                     
                     
                        
                        