java中的注解
java中的注解
注解的作用
起初是用来标注程序的辅助信息,后面框架越来越多,生态越来越好,主要有如下作用:
- 代替xml中的配置作用(也不能完全替代),用注解做为配置更方便一些
- 通过注解设计一些通用的、增强原有逻辑的功能。自定义注解有个作用:被其他代码读到,所以经常需要配置【扫描的包】
- 自解释:所有的代码都有解释自己的功能,良好的命名规范可以少写或不写注释。
- 注解的英文是
annotation
,注解和泛型一样,是对代码添加的标记,注解作为标记的作用更加强大,注解可以被其它程序读取(这是区别于注释最直观的特点),以实现各种各样的功能和业务逻辑; - 随着 JDK 的演进,可以在几乎所有的地方使用注解。使用注解我们可以把一些附加的、通用的方法「动态地」加在原有的代码上,而 不用修改原来的代码。这些附加的、通用的方法可能是和原有的业务无关的方法,例如:统计代码的执行时间、记录日志等;也可能与原有业务有关,例如:自动化生成
get
,set
方法,构造器等。
注解的标记功能
我们最早接触的注解可能是 @Override
,它修饰在方法上,被注解 @Override
修饰的方法表示:此方法覆盖了父类(或者父类的父类)的某个方法。有了 @Override
注解,如果父类的方法名、参数个数、参数类型、返回值类型和原方法不一致,编译器就会报错,如果不加@Override
注解且参数个数、参数类型、返回值类型和原方法不一致,那编译器就认为这是一个方法重载。所以注解在这里有两个作用:
- 给人看:父类也有这个方法,在子类被重写了;
- 给编译器看:帮我检查一下,我是不是重写对了。
因此,注解的一个作用是给代码加上的标记,是对代码内容的说明。设计注解是为了设计更通用的功能,常见的框架的设计有很大一部分是通过==「自定义注解 + 反射」==实现强大功能的。
JDK提供的常用注解
@Override
,标记方法重写@Deprecated
,标记弃用,表示不再建议使用的方法或类@SuppressWarnings
,用于抑制编译器产生特定类型的警告信息@FunctionlInterface
,声明函数式接口,标注在接口上,表示这是一个【函数式接口】(即只有一个待实现的抽象方法的接口)
元注解
元注解就是解释注解的注解,一共有5个:Target
,Retention
,Documented
,Inherited
,Repeatable
,最常用的是前两个,但掌握所有的元注解用法是高阶玩家的必修课。
@Target
作用:声明被修饰的注解在哪些位置使用,它可以传入值来具体指定使用位置,取值定义在ElementType
枚举中
ElementType.TYPE
:可以应用于类、接口(包括注解类型)、枚举。ElementType.FIELD
:可以应用于字段(成员变量)。ElementType.METHOD
:可以应用于方法。ElementType.PARAMETER
:可以应用于方法参数。ElementType.CONSTRUCTOR
:可以应用于构造器。ElementType.LOCAL_VARIABLE
:可以应用于局部变量。ElementType.ANNOTATION_TYPE
:可以应用于其他注解。ElementType.PACKAGE
:可以应用于包声明。
为显式声明,这可在任意元素上标注。
@Retention
作用:指定被修饰的注解的保留级别,即注解的存活周期。它有三个取值,定义在RetentionPolicy
枚举中。
RetentionPolicy.SOURCE
:注解只在源文件(.java
)中存在,编译时会被丢弃,不会保留在字节码文件(.class
)中。RetentionPolicy.CLASS
:默认值,注解会保留在字节码文件中,但在运行时 JVM 不会读取。RetentionPolicy.RUNTIME
:注解不仅会保留在字节码文件中,在运行时 JVM 也能够读取到注解的信息。这使得我们可以在运行时通过反射机制获取注解并进行相应的处理。例如,Spring 框架中用于依赖注入的@Autowired
注解就使用了这个策略,以便在运行时根据注解信息进行依赖注入操作。
@Repeatable
作用:标识一个注解可以在同一个元素(类、方法、字段等)上重复使用
在 Java 8 之前,同一个注解默认不能在同一个元素上重复标注。如果需要对一个元素多次使用同类型注解,通常需要借助一个 “容器注解” 来包裹多个同类型注解,使用起来不够直观。
@Repeatable
的出现解决了这个问题,它的作用是声明某个注解是可重复的,并指定该注解对应的 “容器注解”(用于在底层存储重复的注解实例)。
-
定义可重复的注解,并通过
@Repeatable
指定容器注解:import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;// 声明该注解可重复,容器注解为 Roles.class @Repeatable(Roles.class) @Retention(RetentionPolicy.RUNTIME) public @interface Role {String value(); }
-
定义容器注解(用于存储重复的
@Role
注解):import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME) public @interface Roles {// 容器注解必须包含一个返回该可重复注解数组的方法Role[] value(); }
-
重复使用注解:
// 直接重复使用 @Role 注解(无需显式包裹在 @Roles 中) @Role("admin") @Role("user") public class User { }
-
获取重复的注解:
运行时通过反射获取注解时,会自动封装到容器注解中,可通过容器注解获取所有重复的实例:
public class Main {public static void main(String[] args) {Roles roles = User.class.getAnnotation(Roles.class);for (Role role : roles.value()) {System.out.println(role.value()); // 输出 "admin" 和 "user"}} }
核心要点:
@Repeatable
的参数是 “容器注解” 的Class
对象,容器注解必须包含一个返回可重复注解数组的方法(通常命名为value()
)。- 重复标注的注解在编译后会被自动封装到容器注解中,反射获取时需通过容器注解获取所有实例。
@Documented
作用:表示被修饰的注解会被javadoc
工具提取成文档。如果一个注解a标注了该注解,那么在生成API文档时,使用了注解a的元素的文档会包含这个注解的信息。
@Inherited
作用:指定被修饰的注解具有继承性。如果一个类使用了被Inherited
修饰的注解,那么它的子类也会自动继承这个注解。
需要注意的是,该注解仅对类有效,对接口无效
自定义注解
基本定义与使用
格式
public @interface 注解名{public 类型 属性名() default 默认值;
}
-
注解本质上也是一个类,每次使用注解都将产生对应注解类的实例。
-
注解这个特殊类使用
@interface
修饰,命名规则和类的命名规则一致,使用时在前面加上@
号,编译后的文件是.class
文件。
示例
定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {// 注解元素,带有默认值String value() default "";int count() default 1;
}
使用注解
-
使用注解:
在符合注解目标(如上述示例中的方法)上使用自定义注解,并可以为注解元素赋值(如果没有使用默认值)。
public class AnnotationUsageExample {@MyAnnotation(value = "example", count = 3) //以键值对的方式为注解属性赋值public void myMethod() {System.out.println("This is my method.");}
}
注:
当注解只有一个value属性时,使用注解时,value的名称可以省略,如上,当MyAnnotation
注解只有value
属性时,使用注解时,可以直接写成:@MyAnnotation("example")
注解的解析
注解是程序的标记,要想获得注解信息,必须先获得被注解的程序对象。java的反射机制提供了这样的功能。
运行时解析注解
当注解的@Retention
的为RetentionPolicy.RUNTIME
时(存活到运行时),可以使用反射机制在运行时获取和解析注解。
要想解析谁身上的注解,就应该先拿到谁,Class
,Method
,Field
,Constructor
都实现了AnnotatedElement
接口,都拥有解析注解的能力。
解析注解API
AnnotatedElement
接口提供的常用解析注解的方法:
方法 | 描述 |
---|---|
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 判断当前元素上是否存在指定注解实例 |
Annotation[] getAnnotations() | 返回当前元素上存在的所有注解实例(重复注解返回的是容器注解实例) |
Annotation[] getDeclaredAnnotations() | 返回当前元素上存在的所有显式声明的注解实例(重复注解返回的是容器注解实例) |
<T extends Annotation>T getAnnotation(Class<T> annotationClass) | 返回当前元素上存在的指定注解实例,不存在返回null |
<T extends Annotation>T getDeclaredAnnotation(Class<T> annotationClass) | 返回当前元素上存在的显式声明的指定注解实例,不存在返回null |
<T extends Annotation>T[] getAnnotationsByType(Class<T> annotationClass) | 返回当前元素上存在的所有注解实例,包括重复注解的所有实例 |
<T extends Annotation>T[] getDeclaredAnnotationsByType(Class<T> annotationClass) | 返回当前元素上存在的所有显式声明的注解实例,包括重复注解的所有实例 |
Annotation
实现类的实例方法:
方法 | 描述 |
---|---|
Class<? extends Annotation> annotationType() | 返回此注解实例的类型 |
equals(Object obj) | 逻辑判断 |
int hashCode() | 返回hash值 |
String toString() | 返回字符串表示 |
案例上手
import java.lang.reflect.Method;public class AnnotationParserExample {@MyAnnotation(value = "example", count = 3) //以键值对的方式为注解实例属性赋值public void myMethod() {System.out.println("This is my method.");}public static void main(String[] args) {try {Class<?> clazz = AnnotationUsageExample.class; //获取当前类的Class对象Method method = clazz.getMethod("myMethod"); //获取myMethod方法//获取方法上的@MyAnnotation注解MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); if (myAnnotation!= null) {System.out.println("Value: " + myAnnotation.value()); // 访问注解的value属性System.out.println("Count: " + myAnnotation.count()); // 访问注解的Count属性}} catch (NoSuchMethodException e) {e.printStackTrace();}}
}
深入探究
是否获取继承性注解的实例
可以发现上述AnnotatedElement
接口提供的常用解析注解的方法中,核心是通过程序元素(类,方法,字段)获取其标注的注解实例,每个获取注解实例的方法,都有对应的获取显式声明的注解实例的方法(方法名特点是:Declared
),它们的核心区别是是否包含继承的注解(仅限标注在类上的注解,且该注解要用元注解@Inherited
修饰)。
以getAnnotations()
与getDeclaredAnnotations()
区别为例
前面提到@Inherited可以让注解随着类的继承而继承,getAnnotations()
可以获取到所有注解实例(包括继承而来的),getDeclaredAnnotations()
获取到所有显式声明的注解实例(不包括继承而来的)
//没有继承性的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Capacity{String value();
}//有继承性的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {String value();
}
@MyAnnotation("fun")
public class TestUser {
}@Capacity("son")
public class TestUserSon extends TestUser {
}
public class Main {public static void main(String[] args) throws CloneNotSupportedException {Annotation[] anns1 = TestUserSon.class.getAnnotations();Annotation[] anns2 = TestUserSon.class.getDeclaredAnnotations();for(Annotation ann : anns1){System.out.println(ann.annotationType());}System.out.println("-----------------");for(Annotation ann : anns2){System.out.println(ann.annotationType());}}
}
/*输出:
interface com.gezishan.analysis.MyAnnotation
interface com.gezishan.analysis.Capacity
-----------------
interface com.gezishan.analysis.Capacity
*/
是否获取全部重复性注解实例
jdk8之后对重复注解做了有力支持,通过getAnnotationsByType
和getDeclaredAnnotationsByType
(方法名的特点是:后缀为ByType
)可获取重复性注解的所有实例。
以getAnnotations()
与getAnnotationsByType()
区别为例
同样是返回所有注解实例
getAnnotations()
,返回所有标注在此元素上的注解,对于重复注解,返回的是重复性注解的容器注解的实例(对于容器注解的内容见上:@Repeatable)。getAnnotationsByType
返回指定重复性注解的所有实例。
// 可重复的注解
@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {String value();
}// 容器注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {Role[] value();
}//
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Capacity{String value();
}
@Role("admin")
@Role("user")
@Capacity("stu")
public class TestUser {
}
public class Main {public static void main(String[] args) throws CloneNotSupportedException {Annotation[] anns1 = TestUser.class.getAnnotations();Annotation[] anns2 = TestUser.class.getAnnotationsByType(Role.class); //指定重复性注解for(Annotation ann : anns1){System.out.println(ann.annotationType());}System.out.println("-----------------");for(Annotation ann : anns2){System.out.println(ann.annotationType());}}
}
/*输出:
interface com.gezishan.analysis.Roles
interface com.gezishan.analysis.Capacity
-----------------
interface com.gezishan.analysis.Role
interface com.gezishan.analysis.Role
编译时注解解析
编译时注解处理允许在编译期间根据注解生成代码或进行检查。这需要创建一个实现 javax.annotation.processing.Processor
接口的处理器类,并注册该处理器。
相关概念
- 注解处理器(Processor):实现
javax.annotation.processing.Processor
接口的类,负责处理特定的注解。处理器会在编译期间被调用,对使用了特定注解的代码元素进行处理。 - 抽象处理器(AbstractProcessor):一个抽象类,实现了
Processor
接口,为开发者提供了一个更方便的基础类来编写自定义处理器。通常,自定义处理器会继承AbstractProcessor
类并实现其process
方法(处理源自前一轮的类型元素的一组注解类型,并返回此处理器是否声明了这些注解类型。)。 - 处理轮次(Round):编译过程中,注解处理器可能会被多次调用,每一次调用称为一个处理轮次。在每一轮处理中,处理器可以处理新发现的注解。如果在某一轮处理中生成了新的源文件,那么可能会触发新的一轮处理。
定义注解处理器
- 继承
AbstractProcessor
类。 - 使用
@SupportedAnnotationTypes
注解指定该处理器处理的注解类型。 - 使用
@SupportedSourceVersion
注解指定支持的 Java 源版本。
以下是一个简单的编译时注解处理器示例框架:
@SupportedAnnotationTypes("MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {MyAnnotation myAnnotation = element.getAnnotation(MyAnnotation.class);// 在这里进行编译时的处理逻辑,如生成代码或检查processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing MyAnnotation on " + element);}return true;}
}
编译时的注解解析的内容还是比较多的,包括:
-
获取处理环境
ProcessingEnvironment
提供了很多有用的工具和信息,例如Messager
用于报告错误、警告和其他信息,Filer
用于生成新的源文件或资源文件或代码等。 -
处理注解方法,
process
方法是注解处理器的核心。它接收两个参数:annotations
是这一轮要处理的注解类型集合,roundEnv
提供了对当前处理轮次中使用了这些注解的元素的访问。在process
方法中,可以遍历使用了特定注解的元素,并根据注解的信息进行相应的处理。 -
服务加载机制。
-
构建工具集成。
如果说运行时通过反射解析注解是通过程序对象找到注解,那编译时通过process
方法解析注解就是通过注解找到被注解的程序对象。
更多详情就不在这里叙述了(作者自己还没完全搞懂…),后面有机会再研究。