java注解的基本原理
xml以松耦合的方式完成了框架中几乎所有的配置,但是随着项目的越来越庞大,xml的内容也越来越复杂,维护成本变高,为了解决这一问题,提出了一种标记式高耦合的配置方式,注解。方法,类,字段属性都可以使用注解,几乎需要配置的地方都需要注解。
注解可以提供更大的便捷性,易于维护,但耦合度高,xml相对于注解则相反。
注解的本质
注解的本质是一个继承了Anotation接口的接口。一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析他的代码,可能连注释都不如。
解析一个类或者方法的注解往往有两种形式,编译时直接扫描,运行时反射。编译时扫描是指编译器在对java代码编译字节码的过程中检测到某个类或者某个方法被一些注解修饰,这时就会对这些注解进行处理。
典型的就是注解@Override,一旦编译器检查某个方法被修饰了@Override注解,编译器就会检查当前方法的签名是否真正重写了父类的某个方法,即比较父类中是否具有一个同样的方法签名。
这种情况适合用于编译器已经知道的注解类,例如JDK的内置注解。然而自定义的注解,编译器是不会知道注解的作用的,于是也就不知道如果处理,往往只会根据该注解的作用范围来选择是否编译进字节码文件。
元注解
元注解用于修饰注解的注解,通常用于注解的定义,例如:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
在代码中,我们定义了@Override注解,在其中的@Target,@Retention两个注解就是我们所谓的元注解,元注解一般用于指定某个注解生命周期以及作用目标等信息。
java中有以下几个元注解:
- @Target:注解的作用目标
- @Retention:注解的生命周期
- @Documented:注解是否因该被包含在JavaDoc文档中
- @Inherited:是否允许子类继承该注解
其中,@Target用于指明被修饰的注解最终可以作用的目标是谁,,即指明你的注解到底是用来修饰方法的,类的,还是属性字段。
@Target定义如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
可以通过以下的方法来为这个value传值:
@Target(value={ElementType.FIELD})
被这个@Target注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType是一个枚举类型,有以下一些值:
- ElementType.TYPE:允许被修饰的注解作用于类,接口,和枚举上
- ElementType.FIELD:允许作用在属性字段上
- ElementType.METHOD:允许作用于方法上
- ElementType.PARAMETER:允许作用与方法参数上
- ElementType.CONSTRUCTOR:允许作用于构造器上
- ElementType.LOCAL_VARIABLE:允许作用于本地局部变量上
- ElementType.ANNOTATION_TYPE:允许作用在注解上
- ElementType.PACKAGE:允许作用在包上
@Retention用于指明当前注解的生命周期,基本定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
同样的,它也有一个value属性:
@Retention(value = RetentionPolicy.SOURCE)
RetentionPolicy依然是一个枚举类型,可以有以下几个枚举值:
- RetentionPolicy.SOURCE:当前注解编译器可见,不会写入class文件
- RetentionPolicy.CLASS:类加载阶段丢弃,会写入class文件
- RetentionPolicy.RUNTIME:永久保存,可反射获取
@Retention注解制定了被修饰的注解的生命周期,一种是编译期可见,编译后被丢弃,一种会被编译器编译进class文件中,无论是类或是方法,乃至字段,它们都是有属性表的,而java虚拟机也定义了几种注解属性表用于存储注解信息,但这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久保存
@Document注解修饰的注解,当我们执行JavaDoc文档打包时会被保存到doc文档,反之将在打包时丢弃。@Inherited注解修饰的注解是有继承性的,当我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
java的内置三大注解
java预定义的三种注解:
- @Override
- @Deprecated
- @SuppressWarnings
@Override定义如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
它没有任何属性,并不能存储任何其他信息。只能作用于方法之上,编译结束后被丢弃。
它是一种标记式注解,仅被编译器可知,编译器在对java文件进行编译成字节码的过程中,一旦检测到某个方法被修饰了该注解,就会去匹配对父类中是否具有一个同样的方法签名的函数,如果不是,不能通过编译。
@Depressed的基本定义:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
依然是一种标记是注解,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等属性已经不再被推荐使用了,可能下一次JDK版本会被删除
编译器不会强制要求你做什么,只会告诉你JDK已经不再推荐使用当前的方法或者类了,建议使用某个替代者
@SuppressWarnings主要用来压制java警告,基本定义如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
它有一个value属性需要你主动的传值,value代表的就是需要被压制的警告类型。例如:
package anotation;
import java.util.Date;
public class Demo {
public static void main(String[] args) {
Date date = new Date(2020, 7, 22);
System.out.println(date.toString());
}
}
运行时,编译器会报一个警告。
如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用@SuppressWarnings注解并给它的value属性传入一个参数值来压制编译器的检查
package anotation;
import java.util.Date;
public class Demo {
@SuppressWarnings("deprecated")
public static void main(String[] args) {
Date date = new Date(2020, 7, 22);
System.out.println(date.toString());
}
}
编译器不会检查main方法下是否会有过时的方法调用,也压制了编译器对这种警告的检查。
java中还有很多的警告类型,它们都会对应一个字符串,通过设置value的属性值即可压制对于这一类的警告类型的检查。
注解与反射
package anotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
String value();
}
这里指定了Hello这个注解只能修饰字段和方法,并且注解永久保存,以便我们反射获取。虚拟机定义了一系列和注解相关的属性表,无论是字段,方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
- RuntimeVisibleAnnotations:运行时可见的注解
- RuntimeInVisibleAnnotations:运行时不可见的注解
- RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解
- RuntimeInVisibleParameterAnnotations:运行是不可见的方法参数注解
- AnnotationDefault:注解类元素的默认值
对于一些类或者接口来说,Class类中提供了以下一些方法用于反射注解
- getAnnotation:返回指定的注解
- isAnnotationPresent:判断当前注解是否被指定注解修饰
- getAnnotations:返回所有的注解
- getDeclaredAnnotation:返回本元素的指定注解
- getDeclaredAnnotations返回本元素的所有注解,不包含父类继承而来的
方法,字段中相关反射注解的方法基本是类似的
例子:
package anotation;
import java.lang.reflect.Method;
public class Test {
@Hello("hello")
public static void main(String[] args) throws NoSuchMethodException {
Class testClass = Test.class;
Method method = testClass.getMethod("main", String[].class);
Hello annotation = method.getAnnotation(Hello.class);
System.out.println(annotation.value);
}
}
注解本质上继承了Annotation接口的接口,可以通过反射,即getAnnotation方法获取一个注解类实例,通过JDk动态代理机制生成一个实现注解接口的代理类。代理类实现接口Hello并重写其所有方法,包括value方法以及接口Hello从Annotation接口继承而来的方法
总结
- 可以通过键值对的形式为注解属性赋值,例如:@Hello(value = “hello”)
- 用注解修饰某个元素,编译器将会在编译期间扫描每个类或者方法上的注解,会做一个基本的检查,检查注解是否允许作用在当前位置,最后会将注解信息写入元素的属性表
- 当进行反射的时候,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它
- 虚拟机采用JDK动态代理机制生成一个目标注解的代理类,并初始化好处理器