学习的过程是不断深入的过程,但有时也需要对基础知识重新深入的学习一遍。
java注解Annotation
1 注解的概念
《java编程思想》中注解的定义:
注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。
元数据:元始的意思,元数据就是描述数据的数据,注解的可以称为描述源码的数据。
不太好理解,我自己的理解是注解类似注释,相同点就是都不会影响源代码逻辑,但注解可以利用注解解析器获取,然后利用注解标识的信息生成新的代码从而对整体产生影响。所以注解类似某些标记,编译代码时利用工具读取这些标记作相应的操作。还有就是类似IOC框架注解可以简化配置文件。
2 默认提供注解和元注解
java语言提供的默认注解:
@Override 继承时标识重写的注解,如果添加了该注解,拼写错误或者父类中不存在都会提示错误信息。
@Deprecated过时注解,标识了该注解的程序,使用时编译期会发出过时警告
@SuppressWarnings,该注解关闭编译期发出的警告信息,
java还提供了四种基础的注解(其实是五种1.8添加了重复注解),被称为元注解,专门负责新注解的创建:
@Target
表示该注解可以用于什么地方,可能的ElementType参数有:
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Retention
表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
SOURCE:注解将被编译器丢弃,编译完成之后什么都不会留下,类似@Override, @SuppressWarnings都属于这类注解。
CLASS:注解在class文件中可用,但会被VM丢弃,所以再类加载时会被丢弃,在字节码文件的处理中有用。注解默认使用这种方式。
RUNTIME:VM将在运行期间保留注解(注解信息一致会保留),因此可以通过反射机制读取注解的信息。
@Document
将注解包含在Javadoc中
@Inherited
允许子类继承父类中的注解
jdk8 提供了一种新的元注解:
@Repeatable
定义注解重复,是java8 提供的新特性。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
重复注解不利用@Repeatable也可以实现,就是定义注解的数组,有了@Repeatable之后就会很方便,类似语法糖。
什么时候会用到重复注解呢?有时候一个对象或者函数它的属性可以是一种也可以是另外一种,像一个人既可以是学生也可以是司机。
3 创建注解
创建新的注解一般至少会用到@Target和@Retention注解,也可以不添加:
3.1 注解简单定义:
注解定义很简单利用@interface 类似接口:
public @interface Demo {
}
java提供的@Override注解源码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Override注解竟然什么都没有做,那它是怎么实现功能的呢,这个实现功能的人是谁呢?他就是程序员编写的注解解析器,这里需要再次重复,注解什么都没有做,它只是标识了某些信息。
如何使用注解,就跟我们使用@Override一样,直接写在相应的函数或者类或者构造函数上就可以了。
3.2 带有元素的注解
没有元素的注解称为标记注解类似@Test,@Override等
注解带有元素,类似方法定义:
@Retention(RUNTIME)
@Target(METHOD)
public @interface Demo1 {
public int id();
public String desc() default "no info";
}
注解的元素可以有默认值,如果在使用注解时没有指定注解参数的值,就会使用默认值,如果定义注解时没有默认值,则使用时必须给定值。
@Demo1(id=1)
public void getData() {
}
如果只有一个参数,类似只有一个value,则使用时可以不写value=1,直接写值就可以了。
//定义
public @interface Demo {
public int value();
}
//使用
@Demo(1)
public void getData2() {
}
3.3 重复注解
不使用重复注解:
public @interface Demo {
public int value();
}
@Retention(RUNTIME)
@Target(METHOD)
public @interface Demos {
public Demo[] value();
}
//使用
@Demos({
@Demo(1),
@Demo(2)
})
public void getDat3() {
}
//获取注解
public void getAnnoInfo2() {
Class clazz = GetAnno.class;
//获得所有的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
method.setAccessible(true);//禁用安全机制
if (method.isAnnotationPresent(Demos.class)) {//检查是否使用了Demo1注解
Demos demos = method.getAnnotation(Demos.class);//获得注解实例
Demo[] demo=demos.value();
for(Demo data : demo)
{
data.value();
}
}
}}
使用之后:
@Repeatable(Demos.class)
public @interface Demo {
public int value();
}
/*@Demos({
@Demo(1),
@Demo(2)
})*/
@Demo(1)
@Demo(2)
public void getDat3() {
}
4 注解的解析
注解的解析方式和@Retention有关,如果是源码级注解需要利用apt,如果是运行时注解可以利用反射解析。
4.1 获取运行时注解
@Retention(RetentionPolicy.RUNTIME)
RUNTIME 注解获取,首先使用注解,反射使用注解的类获取注解:
public void getAnnoInfo() {
Class clazz = GetAnno.class;
//获得所有的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
method.setAccessible(true);//禁用安全机制
if (method.isAnnotationPresent(Demo1.class)) {//检查是否使用了Demo1注解
Demo1 demo1 = method.getAnnotation(Demo1.class);//获得注解实例
String name = method.getName();//获得方法名称
if (demo1.id() != 0) {
} else {
}
if (demo1.desc() != null) {
} else {
}
}
}
}
4.2 获取编译期注解
@Retention(RetentionPolicy.SOURCE)或者CLASS注解的获取,编译期注解只存在源码中,所以无法利用反射获取注解,想要获取注解需要使用APT工具,类似greendao,黄油刀等都是编译期注解,APT读取注解之后生成新的java源码文件完成逻辑。生成java源码比价好用的库就是
APT(Annotation Processing Tool)是一种注解解析工具,他对源代码进行检测,并找出源代码所包含的Annotation信息,然后针对Annotation信息进行额外的处理。使用APT工具处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其他的文件(文件的具体内容由Annotation处理器的编写者决定),APT还会将编译生成的源代码文件和原来的源文件一起生成Class文件。
Java提供的javac.exe工具有一个-processor选项,该选项可指定一个Annotation处理器,如果在编译java源文件时指定了该Annotation处理器,那么这个Annotation处理器将会在编译时提取并处理Java源文件中的Annotaion.
每一个Annoataion处理器都需要实现javax.annotataion.processor包下的Processor接口,不过实现该接口必须实现该接口下的所有的方法,因此通常会采用继承AbstractProcessor的方式来实现Annotation的处理器。一个Annotation处理器可以处理一个或多个Annotaion注解。
下面提供一个解析的例子:
//apt处理源码注解,一般会生成新的java文件
//指定该注解支持java平台的最新版本为6.0
@SupportedSourceVersion(SourceVersion.RELEASE_6)
//指定可以处理的注解
@SupportedAnnotationTypes({"Demo2"})
public class ProcessorDemo extends AbstractProcessor{
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
//定义文件输出流,用于生成额外的文件
try{
for(Element t:roundEnv.getElementsAnnotatedWith(Demo2.class)){
//获取正在处理的类名称
Name className=t.getSimpleName();
//获得类定义前的注解
Demo2 per= t.getAnnotation(Demo2.class);
//遍历元素信息
for(Element f:t.getEnclosedElements())
{
//只处理成员变量上的Annotation
if(f.getKind()==ElementKind.FIELD)
{
//获取成员变量定义前的@Id Annotation
Demo1 demo1=f.getAnnotation(Demo1.class);
//获取成员变量前的@Property Annotation
Demo demo=f.getAnnotation(Demo.class);
}
}
}
}catch(Exception e)
{
e.printStackTrace();
}
return true;
}
5 注解的使用场景
注解可能大家使用的不太多,但是几乎每个框架都用到了,总结下注解的使用场景。
注解有许多用处,主要如下:
- 源码注解提供信息给编译器: 编译器可以利用注解来探测错误和警告信息 ,也可以生成代码。
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成java代码、xml配置文件和其他操作。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取,进行必要逻辑。