转载:https://www.jianshu.com/p/65c4af2ce8f0
一、Java注解相关Annotation 转载
注解也会被编译成class文件
注解的声明
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE,PACKAGE})
public @interface HelloAnnotation {
}
在定义注解时需要一些元注解,如@Retention和@Target。
五种元注解
元注解 | 说明 | 取值 |
---|---|---|
@Target | 表示该注解可以用在什么地方 | ElementType 的枚举值: TYPE:用于类、接口(包括注解类型)或enum声明 FIELD:用于字段声明,包括enum实例 METHOD:用于方法声明 PARAMETER:用于参数声明 CONSTRUCTOR:用于构造函数声明 LOCAL_VARIABLE:用于局部变量声明 ANNOTATION_TYPE:用于注解可用于注解声明(应用于另一个注解上) PACKAGE:用于包声明 TYPE_PARAMETER:用于类上泛型参数的说明(JDK1.8加入) TYPE_USE:用于任何类型的声明(JDK1.8加入) |
@Retention | 表示该注解在什么级别下被保存 注解未定义 Retention值时,默认值是 CLASS级别 | RetentionPolicy 的枚举值: SOURCE:只会保留在源码里,编译后不存在该注解信息 CLASS:保留至 class文件,在执行的时候,不会被加载到虚拟机中 RUNTIME:VM 在运行期间保留注解,可以通过反射机制读取到注解的信息 |
@Documented | 表示是否将注解包含在 JavaDoc 中 | 跳转到项目的目录,打开命令行, 执行 javadoc -encoding utf-8 -charset utf-8 -package com.test.annotation 命令 生成文档 |
@Inherited | 表示允许子类继承父类中的注解 | 父类使用该注解后,子类也可以获得该注解 |
@Repeatable | 看下面详细说明 |
注解的属性
属性 | 说明 |
---|---|
支持的属性类型 | 所有的基本类型(int、float、boolean)等 String Class enum Annotaion 以及以上类型的数组 |
注解添加属性 | 基本语法是: 类型 属性名() @interface WorldAnnotation { //基本类型及其数组类型 int intAttr(); } |
指定属性默认值 | 语法为:属性 属性名() default 默认值; @interface WorldAnnotation { //基本类型及其数组类型 int intAttr() default -1; } |
注解默认值限制 | 1、注解中的元素,必须 要么有默认值,要么在使用注解时提供元素的值 2、非基本类型的元素,不能以 null 作为其值 |
value 属性 | 一个注解中只有一个名称为 value 的属性 或者,注解有多个属性,只有 value 属性没有默认值 填写注解值时,可以省略掉 “value=” 部分 @WorldAnnotation(“hello”) @WorldAnnotation(“value = hello”) //可以省略调 “value=” |
1、@Target 元注解
表示该注解可以用在什么地方。
1)使用方式
// TYPE:用于类、接口(`包括注解类型`)或enum声明
@HelloAnnotation
class AnnotationDemo {
//CONSTRUCTOR:用于构造函数声明
@HelloAnnotation
public AnnotationDemo(String name) {
this.name = name;
}
//FIELD:用于字段声明,包括enum实例
@HelloAnnotation
private String name;
//METHOD:用于方法声明
@HelloAnnotation
public void sayHello() {
System.out.println("hello every one");
}
//PARAMETER:用于参数声明
public void saySometing(@HelloAnnotation String text) { }
//LOCAL_VARIABLE:用于局部变量声明
public int add(int a, int b) {
@HelloAnnotation int total = 0;
return a + b;
}
//ANNOTATION_TYPE:用于注解可也用于注解声明(应用于另一个注解上)
@HelloAnnotation
@interface AnnotationTwo {
}
}
2)对包进行注解修饰,需要在当前包下创建【package-info.java 文件】
-
package-info.java 文件的作用:
i. 为标注在包上 Annotation提供便利
ii. 声明包的 私有类和常量
iii.提供包的整体注释说明
(如果是项目是分“包”开发,
也就是说一个包实现一个业务逻辑或功能点、或模块、或组件,
则需要对一个包有很好的说明,
说明这个包是干啥的,有啥作用,版本变迁,特别说明等等)/** * 主要是为了测试包的注解 */ @HelloAnnotation package com.test.annotation;
3)JDK1.8中新增的 TYPE_PARAMETER、TYPE_USE
-
TYPE_PARAMETER 用于 修饰类上的【泛型参数】
@Retention(RetentionPolicy.RUNTIME) @Target(value = {TYPE_PARAMETER}) public @interface HelloAnnotation { } class A<@HelloAnnotation T> {}
-
TYPE_USE 用于任何类型声明(也包含修饰类上的泛型参数)
@Retention(RetentionPolicy.RUNTIME) @Target(value = {TYPE_USE}) public @interface HelloAnnotation { } @HelloAnnotation class AnnotationDemo<@HelloAnnotation T> { @HelloAnnotation public AnnotationDemo(String name) { this.name = name; } @HelloAnnotation private String name; public void saySometing(@HelloAnnotation String text) { } public int add(int a, int b) { @HelloAnnotation int total = 0; return a + b; } @HelloAnnotation @interface AnnotationTwo { } }
2)@Retention 元注解
表示该注解在什么级别下被保存。
当注解未定义Retention值时,默认值是CLASS级别
3)@Documented 元注解
表示是否将注解包含在 JavaDoc 中
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
}
@HelloAnnotation
class AnnotationDemo {
@HelloAnnotation
public AnnotationDemo(String name) {
this.name = name;
}
@HelloAnnotation
private String name;
public void saySometing(@HelloAnnotation String text) { }
public int add(int a, int b) {
@HelloAnnotation int total = 0;
return a + b;
}
}
跳转到项目的目录,打开命令行,
执行 javadoc -encoding utf-8 -charset utf-8 -package com.test.annotation 命令
(这里是所有的文件都是放在annotaton包下的,你可以根据你自己的包名为该包下的所有.java文件生成Doc文档)
如果为 该注解 指定了 @Documented 元注解,生成的文档中,会包含 该注解。
4)@Inherited 元注解
表示允许子类继承父类中的注解。
@Target(ElementType.TYPE)
@Inherited // @Hello 注解允许子类继承
@Retention(RetentionPolicy.RUNTIME)
@interface Hello {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface World {
}
@Hello
class Person { //父类中使用了 @Hello 注解
}
// 子类虽然没有直接使用 @Hello 注解,但是会继承父类使用的 @Hello 注解
@World
class Man extends Person {
public static void main(String[] args) {
Annotation[] annotations = Man.class.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.annotationType());
}
}
}
//输出结果
interface annotation.Hello
interface annotation.World
5)@Repeatable 元注解 (JDK1.8 新增)
表示,可以在同一个地方多次使用同一种注解类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class)//添加@Repeatable元注解,注意其中的值【FilterPaths.class】
public @interface FilterPath {
String value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPaths {
FilterPath[] value();//注解其中的【数组类型为 FilterPath】
}
//现在可以在同一个地方使用同一注解啦~
@FilterPath("hello/java")
@FilterPath("hello/android")
class FileOperate {
}
@Repeatable元注解使用 注意事项:
为了处理@Repeatble注解,
JDK1.8 在 AnnotatedElement 接口中提供了 getAnnotationsByType 与 getDeclaredAnnotationsByType 方法,
(注意:如果我们采用传统的方法,也就是 getAnnotation(Class<A> annotationClass)方法来获取声明的注解,
我们需要传入注解容器的class,而不是需要重复的注解的class)。
public staticvoid main(String[] args) {
//从该类上获取 FilterPath 注解信息
FilterPath filterPath = FileOperate.class.getAnnotation(FilterPath.class);
System.out.println(filterPath);//输出结果 null
//从该类上获取 FilterPaths 注解信息
FilterPaths filterPaths = FileOperate.class.getAnnotation(FilterPaths.class);
//输出结果 @annotation.FilterPaths(value=[@annotation.FilterPath(value=hello/java), @annotation.FilterPath(value=hello/android)])
System.out.println(filterPaths);
for (FilterPath path : filterPaths.value()) {
/*输出结果
hello/java
hello/android
*/
System.out.println(path.value());
}
//通过 getAnnotationsByType
FilterPath[] annotationsByType = FileOperate.class.getAnnotationsByType(FilterPath.class);
if (annotationsByType != null) {
for (FilterPath path : annotationsByType) {
/*输出结果
hello/java
hello/android
*/
System.out.println(path.value());
}
}
//通过 getDeclaredAnnotationsByType
FilterPath[] declaredAnnotationsByType = FileOperate.class.getDeclaredAnnotationsByType(FilterPath.class);
if (declaredAnnotationsByType != null) {
for (FilterPath path : declaredAnnotationsByType) {
/*输出结果
hello/java
hello/android
*/
System.out.println(path.value());
}
}
}
不能通过 getAnnotation(FilterPath.class)获取注解(获得的注解为null)
需要通过 getAnnotation(FilterPaths.class)来获取)
getAnnotationsByType(FilterPath.class)、getDeclaredAnnotationsByType(FilterPath.class)就能获取到正确的值。
getAnnotationsByType 与 getDeclaredAnnotationsByType 方法的区别:
子类调用 getAnnotationsByType 方法且该子类的父类中声明了用@Inherited修饰的注解,那么可以获得父类中的注解。
而 getDeclaredAnnotationsByType 是获取不到父类中声明的注解的。
3、注解支持属性类型(注解元素中可以定义的属性)
1)注解支持的属性类型
所有的基本类型(int、float、boolean)等
String
Class
enum
Annotaion
以及以上类型的数组
2)注解添加属性
基本语法是: 类型 属性名();
//声明枚举
enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
String text();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {
//基本类型及其数组类型
int intAttr();
float floatAttr() ;
boolean booleanAttr() ;
int[] intArray() ;
float[] floatArray() ;
boolean[] booleanArry() ;
//String类型及其数组类型
String stringAttr() ;
String[] stringArray();
//Class类型及其数组类型
Class classAttr() ;
Class[] classArray();
//enum类型及其数组类型
Week day() ;
Week[] week();
//注解嵌套
HelloAnnotation HelloAnnotation();
}
3)为属性 指定缺省值(默认值)
语法为:属性 属性名() default 默认值;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {
//基本类型及其数组类型
int intAttr() default -1;
float floatAttr() default -1f;
boolean booleanAttr() default false;
int[] intArray() default {1, 2, 3};
float[] floatArray() default {1f, 2f, 3f};
boolean[] booleanArry() default {true, false, true};
//String类型及其数组类型
String stringAttr() default "";
String[] stringArray();
//Class类型及其数组类型
Class classAttr() default Class.class;
Class[] classArray();
//enum类型
Week day() default Week.MONDAY;
//enum数组类型
Week[] week() default {Week.MONDAY,Week.THURSDAY};
//注解嵌套
HelloAnnotation HelloAnnotation() default @HelloAnnotation(text = "word");
}
4)注解默认值限制
注解中的元素,必须【要么有默认值,要么在使用注解时提供元素的值】
非基本类型的元素,不能以 null 作为其值
这个约束使得处理器很难发现一个元素的存在和缺失的状态,
因为在每个注解的声明中,所有的元素都存在,并且都具有相应的值。
为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或负数,以此表示某个元素不存在。
5)value 属性
如果一个注解中有一个名称为value的属性,且你只想设置value属性
(即其他属性都采用默认值或者你只有一个value属性),那么可以省略掉“value=”部分
/* 第一种情况,只有一个vaule属性,
那么你在使用时候可以直接 @WorldAnnotation("hello")
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {
String value();
}
//第二种情况,有多个属性但是其属性都有默认值,
//你只使用value属性,那么你在使用时候可以直接@WorldAnnotation("hello")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {
int intAttr() default -1;
float floatAttr() default -1f;
String value();
}
4、注解与反射机制
1)注解与反射机制的关系
反射机制:在运行状态中,动态获取信息、动态调用对象方法的功能。
生命周期为 @Retention(RetentionPolicy.RUNTIME) 的注解
编译时会添加到class文件中。
JVM加载的 Class对象,也会带有注解信息。
通过Class对象中的 Constructor、Field、Method 等类,就能获取其上声明的注解信息。
编译后,其实注解最终会继承 Annotation接口。
注解最终会以 java.lang.annotation.Annotation对象 的形式在Class对象中进行展示或存储。
2)注解的处理
Constructor、Field、Method、Class、Package类都实现了 AnnotatedElement接口
注解处理交给了 AnnotatedElement 接口 来实现
public interface AnnotatedElement {
//返回元素上指定类型的注解,如果无,则返回为null
<T extends Annotation> T getAnnotation(Class<T> clazz);
//返回元素上存在的所有注解,包括从父类继承的
Annotation[] getAnnotations();
/**返回元素上指定的类型的注解数组,包括父类的注解,
* 如果无,返回长度为0的数组,
* 该方法与 getAnnotation(Class<T> clazz) 的主要区别是:
* 该方法【可以检查注解是不是重复的。】
*/
default <T extends Annotation> T[] getAnnotationsByType(Class<T> clazz) {
return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, clazz);
}
//返回该元素上【指定类型】的所有注解,【不包括父类的注解】,如果无,返回长度为0的数组
default <T extends Annotation> T getDeclaredAnnotation(Class<T> clazz) {
Objects.requireNonNull(clazz);
for (Annotation annotation : getDeclaredAnnotations()) {
if (clazz.equals(annotation.annotationType())) {
return clazz.cast(annotation);
}
}
return null;
}
//同getAnnotationsByType(Class<T> clazz)方法类似,只是获取的注解中【不包括父类的注解】
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> clazz) {
return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, clazz);
}
//返回该元素上的所有的注解,不包括父类的注解,如果无,返回长度为0的数组
Annotation[] getDeclaredAnnotations();
}
getAnnotationsByType(Class clazz)与 getDeclaredAnnotationsByType(Class clazz)方法
是jdk 1.8之后提供的接口默认实现方法。支持 @Repeatable 元注解。
其他方法是不支持 @Repeatable 的。
3)注解的使用
[1]定义注解
//什么人
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Who {
String name();
int age();
}
//在哪里
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Where {
String country();
String province();
String city();
}
//做了什么事
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface DoSomething {
String value();
}
[2]使用定义的注解
@Who(name = "AndyJenifer", age = 18)
class Person {
@Where(country = "中国", province = "四川", city = "成都")
private String where;
@DoSomething("写博客")
public void doSomething() {
}
public static void main(String[] args) {
Class<Person> personClass = Person.class;
StringBuffer sb = new StringBuffer();
//获取类上的注解
Who who = personClass.getAnnotation(Who.class);
sb.append(who.name());
sb.append(who.age());
//获取字段上的注解
Field[] fields = personClass.getDeclaredFields();
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
if (annotations.length > 0 && annotations[0] instanceof Where) {
Where where = (Where) annotations[0];
sb.append(where.country());
sb.append(where.province());
sb.append(where.city());
}
}
//获取方法上的注解
Method[] methods = personClass.getMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
if (annotations.length > 0 && annotations[0] instanceof DoSomething) {
DoSomething doSomething = (DoSomething) annotations[0];
sb.append(doSomething.value());
}
}
System.out.println(sb.toString());
}
}
getXXXX():获得 某个类、包括父类 的所有的公共(public)的元素(如Constructor、Field、Method、Package),
包括父类声明的。
getDeclaredXXXX():获得 某个类、不包括父类 的所有声明的元素(如Constructor、Field、Method、Package),
即 包括 public、private、proteced,但是不包括父类声明的。