自JDK1.5引入注解后,他就成为了Java编程语言重要的组成部分,在开发过程中,我们也时常用到@Override、@ToString等这样的注解。在这篇文章中,将介绍什么是注解、为什么引用注解、它是如何工作的、如何编写自定义注解、以及如何测试自定义注解。
一、什么是注解?
用一个词就可以描述注解,那就是元数据,即一种描述数据的数据,可以理解为描述数据的一种标记。如以下代码:
@Override
public void doSomething() {
System.out.println("复写了父类doSomething()!)");
}
总的来说,Annotation(注解)是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符;Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中;它是一种由JSR-175标准选择用来描述元数据的一种工具。
二、为什么引入注解?
使用Annotation注解之前,XML被广泛的应用于描述元数据。随着项目的开发与维护,XML的表现越来越糟糕,此时,人们希望在一些场合使用紧耦合的方式进行代码描述。当然,并不是说XML方式不好,两者各有优势:
假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连;如果你想对代码进行配置说明、编译提示等时,那么使用Annotation注解会更好一些。
注解的用处:
1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2依赖注入,未来java开发,将大量注解配置,具有很大用处,比如现在的Springboot框架;
3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
开发时我们可以根据它的用处来权衡使用XML方式与使用Annotation注解的利弊来选择是否使用Annotation注解。(参考:https://blog.csdn.net/PORSCHE_GT3RS/article/details/80304701 )
三、注解是如何工作的?
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
关于更详细的了解可以参考这篇blog:https://blog.csdn.net/lylwo317/article/details/52163304。在这篇文章中详细介绍了如何查看与获取生产的代理对象的字节码文件,从而获悉生产的代理对象的具体的代码实现。
四、如何编写自定义注解
编写自定义的注解之前,首先得了解注解相关的基本概念——元注解。
元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解),起到指定注解(自定义注解)的作用范围、生命周期等作用。java.lang.annotation提供了四种元注解:
1)@Documented——注解是否将包含在JavaDoc中,一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中
2)@Retention ——什么时候使用该注解,定义该注解的生命周期
3)@Target? ——注解用于什么地方。如果不明确指出,该注解可以放在任何地方。以下是一些可用的参数。需要说明的是:属性的注解是兼容的,如果你想给7个属性都添加注解,仅仅排除一个属性,那么你需要在定义target包含所有的属性。
ElementType.TYPE:用于描述类、接口或enum声明
ElementType.FIELD:用于描述实例变量
ElementType.METHOD:用于描述实例方法
ElementType.PARAMETER:用于描述实例方法参数
ElementType.CONSTRUCTOR:用于描述实例的构造函数
ElementType.LOCAL_VARIABLE:用于描述局部变量
ElementType.ANNOTATION_TYPE 另一个注释
ElementType.PACKAGE 用于记录java文件的package信息
4)@Inherited —— 是否允许子类继承该注解
但是从JDK1.8开始,又新增加了两个元注解:
@Native——指示可从本机代码引用定义常量值的字段。
@Repeatable——用于指示其声明(元)注释的注释类型是可重复的。
下面我们开始编写自定义注解。语法如下,使用@interface来表明声明的是一个注解
public @interface 注解名称 {
//String weather() default "";//注解中可以没有属性,并且在注解中属性是以方法的形式存在
}
但是,编写注解的时候仅仅做到如上代码的样子还远远不够,通常,还需要为他们指定注解的作用范围、生命周期等,这时候就需要用到元注解对我们定义的注解进行注解(标识说明)了。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WeatherAnnotation {
String weather() default "";
}
以上注解通过ElementType.FIELD限制了该注解只能标识属性,通过RetentionPolicy.RUNTIME指定了注解的生命周期——代码运行时生效,自定义注解基本上都是使用该枚举字段声明其生命周期。接下来,开始编写测试注解的代码:
package com.wyfx.nio.annotation;
public class Today {
@WeatherAnnotation(weather="hello,今天是晴天")
private String dayWeather;
public String getDayWeather() {
return dayWeather;
}
public void setDayWeather(String dayWeather) {
this.dayWeather = dayWeather;
}
@Override
public String toString() {
return "Today{" +
"dayWeather='" + dayWeather + '\'' +
'}';
}
}
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args){
/*Annotation annotations=new Today().getClass().getAnnotation(WeatherAnnotation.class);*/
try {
Class aClass=Class.forName("com.wyfx.nio.annotation.Today");
Field[] fields= aClass.getDeclaredFields();
String weather="";
for (Field field : fields) {
if(field.isAnnotationPresent(WeatherAnnotation.class)){
WeatherAnnotation weatherAnnotation=field.getAnnotation(WeatherAnnotation.class);
weather= weatherAnnotation.weather();
}
}
System.out.println("--annotation---:"+weather);
}catch (Exception e){
e.printStackTrace();
}
}
}
以上代码顺利打印出“hello,今天是晴天”,说明自定义注解成功了,值得注意的是,注解的作用域是属性,所以在通过反射进行测试的时候,必须在Field的基础上去判断是否是Annotation接口的子类(field.isAnnotationPresent(),然后获取注解子类对象。