概述
注解定义
注解(Annotation)在JDK1.5之后增加的一个新特性,注解的引入意义很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。注解作为程序的元数据嵌入到程序。注解可以被解析工具或编译工具解析,此处注意注解不同于注释(comment)。
本文目标
讲述Java Annotation的原理,以及如何自定义Java注解以及通过反射解析注解
为什么学习注解?
学习注解有什么好处?
学完之后能做什么?
- 可以读懂大牛们的代码,特别是和框架相关的代码(如:Spring,MyBatis)
- 让编程更加简洁,让代码更加清晰
注解的分类
按运行机制分类
- 源码注解: 注解只在源码.java文件中存在,编译成.class文件后就不存在了
- 编译时注解: 在源码.java文件和.class文件中都存在(如:@Override,@Deprecated,@Suppvisewarnnings)
- 运行时注解: 在运行阶段还会起作用,甚至会影响运行逻辑的注解(如:@Autowired等)
对于编译器处理的注解,可以使用APT处理。
按来源来分类
- 来自JDK的注解
Java 定义了一套注解,共有 7 个,3 个在 java.lang 包中,剩下 4 个在 java.lang.annotation包中
作用在代码的注解
@Override
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
用途:用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息。
注解类型分析:@Override可适用元素为方法,仅仅保留在java源文件中。
@Deprecated
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
用途:用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,如果使用该方法,会报编译警告,此时应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。
注解类型分析: @Deprecated可适合用于除注解类型声明之外的所有元素,保留时长为运行时VM。
@SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL\_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
用途:用于告知编译器忽略特定的警告信息,压制警告。例在泛型中使用原生数据类型。
注解类型分析: @SuppressWarnings可适合用于除注解类型声明和包名之外的所有元素,仅仅保留在java源文件中。
该注解有方法value(),可支持多个字符串参数,例如:
@SuppressWarnings({“uncheck”,”deprecation”})前面讲的@Override,@Deprecated都是无需参数的,而@SuppressWarnings(压制警告)是需要带有参数的,可用参数如下:
- deprecation: 使用了过时的类或方法时的警告
- unchecked:执行了未检查的转换时的警告
- fallthrough: 当Switch程序块进入进入下一个case而没有Break时的警告
- path: 在类路径、源文件路径等有不存在路径时的警告
- serial: 当可序列化的类缺少serialVersionUID定义时的警告
- finally: 任意finally子句不能正常完成时的警告
all: 以上所有情况的警告
三种注解的对比
Override—METHOD(Target)—SOURCE(Retention)
SuppressWarnings—除ANNOTATION_TYPE和PACKAGE外的所有(Target)—SOURCE(Retention)
Deprecated—除ANNOTATION_TYPE外的所有(Target)—RUNTIME(Retention)
作用在其他注解的注解(元注解)
给注解进行注解的注解(注解的注解称为元注解), 用来进行自定义注解。
@Retention
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION\_TYPE) public @interface Retention { RetentionPolicy value(); }
用途:定义自定义注解的生命周期(由RetentionPolicy封装),表示注解类型的存活时长。标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
表示该注解类型的注解保留的时长。当注解类型声明中没有@Retention元注解,则默认保留策略为RetentionPolicy.CLASS。关于保留策略(RetentionPolicy)是枚举类型,共定义3种保留策略.
// 注解保留策略,用于Retention注解类型
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE, // 只在Java源码显示,经过编译器后便丢弃相应的注解
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS, // 存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME // 存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解
}
@Target
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION\_TYPE) public @interface Target { ElementType[] value(); }
用途:用来定义自定义注解的作用域(作用域由ElementType类封装),表示注解类型所适用的程序元素的种类,标记这个注解应该是哪种Java 成员。
表示该注解类型的所使用的程序元素类型。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。关于程序元素(ElementType)是枚举类型,共定义8种程序元素。
// 程序元素类型,用于Target注解类型
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, // 类/接口声明
/** Field declaration (includes enum constants) */
FIELD, // 字段声明(包括枚举常量)
/** Method declaration */
METHOD, // 方法声明
/** Formal parameter declaration */
PARAMETER, // 参数声明
/** Constructor declaration */
CONSTRUCTOR, // 构造方法声明
/** Local variable declaration */
LOCAL_VARIABLE, // 局部变量声明
/** Annotation type declaration */
ANNOTATION_TYPE, // 注解类型声明
/** Package declaration */
PACKAGE, // 包声明
/**
* Type parameter declaration
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* @since 1.8
*/
TYPE_USE
}
上面源码@Target的定义中有一行@Target(ElementType.ANNOTATION_TYPE),意思是指当前注解的元素类型是注解类型。
@Documented - 标记这些注解是否包含在用户文档中。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION\_TYPE) public @interface Documented { }
用途:生成javadoc时会包含注解信息,表示含有该注解类型的元素(带有注释的)会通过javadoc或类似工具进行文档化
表示拥有该注解的元素可通过javadoc此类的工具进行文档化。该类型应用于注解那些影响客户使用带注释(comment)的元素声明的类型。如果类型声明是用Documented来注解的,这种类型的注解被作为被标注的程序成员的公共API。
上面代码@Retention的定义中有一行@Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。
@Inherited
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION\_TYPE) public @interface Inherited { }
用途:标记这个注解是继承于哪个注解类(默认注解并没有继承于任何子类) ,允许子类继承(接口继承无效),只会继承类上面的注解,表示注解类型能被自动继承。(标识型元注解)
表示该注解类型被自动继承,如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类.
从 Java 7 开始,额外添加了 3 个注解
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
自定义注解
当一个接口直接继承_java.lang.annotation.Annotation_接口时,仍是接口,而并非注解。要想自定义注解类型,只能通过@interface关键字的方式,其实通过该方式会隐含地继承Annotation接口。
自定义注解的语法要求
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String desc();
String author();
int age() default 18;
}
说明
- 使用public @interface关键字定义注解
- 成员以无参无异常的方式声明(如:String desc();)
- 注解和接口类似,内部可以定义常量和方法
- 可以给成员指定一个默认的值(如:int age() default 18;)
- 成员的类型是受限的,合法的类型包括(原始数据类型(*),Sting(*),Class,Annotation,Enumeration),以及前面这些类型的数组类型
- 如果注解只有一个成员,则成员名必须取名value(),在使用时可以忽略成员名和赋值号“=”
- 注解类可以没有成员,没有成员的注解成为标识注解
使用自定义注解
@\<注解名>(\<成员名1>=\<成员值1>, \<成员名2>=\<成员值2>, …)
@Decription(desc="I am eyeColor", author="Mooc boy", age=18)
public String eyeColor() {
return "red";
}
当注解只有一个成员时的定义,成员名必须取名value(),在使用时可以忽略成员名和赋值号“=”。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String value();
}
使用方式
@Decription("I am eyeColor")
public String eyeColor() {
return "red";
}
标识注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
}
使用方式
@Decription
public String eyeColor() {
return "red";
}
来自第三方框架的注解(*)
Spring
- @Autowired
- @Service
- @Repository
MyBatis
- @InsertProvider
- @UpdateProvider
- @Options
后续再学习Spring和Mybatis框架时在给予解释
自定义注解解析
通过反射技术来解析自定义注解,获取类,函数或成员上的运行时注解信息, 从而实现动态控制程序运行的逻辑。关于反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement,该接口定义了注释相关的几个核心方法,如下:
- T getAnnotation(Class annotationClass);
当存在该元素的指定类型注解,则返回相应注解,否则返回null
- Annotation[] getAnnotations();
返回此元素上存在的所有注解
- Annotation [] getDeclaredAnnotations();
返回直接存在于此元素上的所有注解
- boolean isAnnotationPresent(Class\<? extends Annotation> annotationClass);
当存在该元素的指定类型注解,则返回true,否则返回false
public class ParseAnnotation {
public static void main(String[] args) {
// 1. 使用类加载器加载类
try {
Class c = Class.forName("com.sh2zqp.Man");
// 2. 找到类上面的注解
boolean isCExist =
c.isAnnotationPresent(Description.class);// 判断是否存在Description这个注解
if (isCExist) {
// 3. 拿到Description注解实例
Description descriptionAnno = (Description) c.getAnnotation(Description.class);
System.out.println(descriptionAnno.desc());
// 4. 解析找到方法上的Description注解
Method[] declaredMethods = c.getDeclaredMethods();
for (Method m : declaredMethods) {
boolean isMExist = m.isAnnotationPresent(Description.class);
if (isMExist) {
Description description = m.getAnnotation(Description.class);
System.out.println(description.desc());
}
}
// 另一种解析方法
for (Method m : declaredMethods) {
Annotation[] annotations = m.getAnnotations();
for (Annotation a : annotations) {
if (a instanceof Description) {
System.out.println(((Description) a).desc());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
注解的实战
项目说明
项目取自一个公司的持久层框架,用来替代Hibernate的解决方案,核心代码就是通过注解来实现的。
项目需求
- 有一张用户表,字段有用户ID,用户名,昵称,年龄,性别,所在城市,邮箱,手机号;
- 方便对每个字段或字段的组合条件进行检索,并打印出SQL语句;
- 使用方式足够简单。
代码示例
参考资料
全面解析Java注解
Java注解(Annotation)
关于注解、反射的内容,可直接查看oracle提供的Java API
Java自定义注解
Java中的注解是如何工作的?
Java注解