Java
注解是在JDK5
引入的新特性,目前大部分框架都使用了注解简化代码并提高编码效率,因此掌握并深入理解Java
注解对于一个Java
工程师来说是很有必要的事。🚀本篇章代码 Demo
# 理解注解
Java
注解与普通修饰符public
static
void
的使用方法并没有多大区别。
package top.rem.rain.annotation.demo; | |
import org.junit.jupiter.api.Test; | |
import org.springframework.boot.test.context.SpringBootTest; | |
@SpringBootTest | |
class DemoApplicationTests { | |
//@Test 注解修饰方法 a 可直接测试此方法 | |
@Test | |
public void a(){ | |
System.out.println("Test..."); | |
} | |
// 一个方法上可以拥有多个不同的注解 | |
@Deprecated // 标识过时方法 | |
@SuppressWarnings("uncheck") // 压制编译器警告 | |
public static void b(){ | |
} | |
} |
- 在方法上通过使用
@Test
注解后,在运行该方法时,测试框架会自动识别该方法并单独调用。 @Test
是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。@Deprecated
和@SuppressWarnings(“uncheck”)
则是Java
本身内置的注解。@Deprecated
:使用此注解标记的类或方法已过期不建议再使用。@SuppressWarnings(“uncheck”)
:在方法上使用此注解则表示忽略指定警告。
# 注解基本使用
# 声明元注解
package top.rem.rain.annotation.demo; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 自定义 Test 注解 | |
* @author LightRain | |
*/ | |
// 用作于方法 | |
@Target(ElementType.METHOD) | |
// 保留到运行期间 | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Test { | |
} |
@interface
: 声明Test
注解类。@Target
注解传入ElementType.METHOD
参数来标记此注解只能用作于方法上。@Retention
注解传入RetentionPolicy.RUNTIME
则用表示该注解保留到运行期间。- 对于
@Target
和@Retention
是由Java
提供的元注解,所谓元注解就是标记其他注解的根注解。 @Target
用来约束注解可以作用的地方如方法、字段、类,其中ElementType
是枚举类型,参数如下示例。
package top.rem.rain.annotation.demo.custom.enums; | |
/** | |
* 枚举类型可用值 | |
* @author LightRain | |
*/ | |
public enum ElementType { | |
/** 标明该注解可以用于类、接口(包括注解类型)或 enum 声明 */ | |
TYPE, | |
/** 标明该注解可以用于字段 (域) 声明,包括 enum 实例 */ | |
FIELD, | |
/** 标明该注解可以用于方法声明 */ | |
METHOD, | |
/** 标明该注解可以用于参数声明 */ | |
PARAMETER, | |
/** 标明注解可以用于构造函数声明 */ | |
CONSTRUCTOR, | |
/** 标明注解可以用于局部变量声明 */ | |
LOCAL_VARIABLE, | |
/** 标明注解可以用于注解声明 (应用于另一个注解上) */ | |
ANNOTATION_TYPE, | |
/** 标明注解可以用于包声明 */ | |
PACKAGE, | |
/** | |
* 标明注解可以用于类型参数声明(1.8 新加入) | |
* @since 1.8 | |
*/ | |
TYPE_PARAMETER, | |
/** | |
* 类型使用声明(1.8 新加入) | |
* @since 1.8 | |
*/ | |
TYPE_USE | |
} |
- 注意:当注解未指定
Target
值时,此注解可以用于任何元素之上,多个值使用{}
包含并用逗号隔开。
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.Target; | |
import static java.lang.annotation.ElementType.*; | |
/** | |
* 自定义 Test2 注解示例可使用多个值的 @Target | |
* @author LightRain | |
*/ | |
@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) | |
public @interface Test2 { | |
} |
@Retention
元注解用来约束注解的生命周期,分别有三个值:SOURCE(源码级别)
注解将被编译器丢弃,该类型的注解信息只会保留在源码中,源码经过编译后,注解信息将被丢弃,不会保留进编译好的class
文件中。CLASS(类文件级别)
注解将会被保留到class
文件中,但会被Java
虚拟机的VM
丢弃,该类型的注解信息会保留在源码和class
文件中,在执行时不会加载到虚拟机中,请注意:当注解未定义Retention
值时,默认值是CLASS
, 如Java
内置注解:@Override
@Deprecated
@SuppressWarnning
等。RUNTIME(运行时级别)
注解将保留到运行期 (JVM
) 中,因此可以通过反射机制读取注解的信息 (源码、class
文件和执行的时候都有注解信息),如:SpringMvc
中的@Controller
、@Autowired
、@RequestMapping
等。
# 注解元素及其数据类型
- 通过上述对
@Test
注解的定义,我们了解了注解定义的过程,由于@Test
内部没有定义其他元素。 - 所以
@Test
也称为标记注解marker annotation
, 但在自定义注解中一般都会包含一些元素以表示某些值。
package top.rem.rain.annotation.demo; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 对应数据表注解 | |
* @author LightRain | |
*/ | |
// 作用于类上 | |
@Target(ElementType.TYPE) | |
// 保留到运行期 | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface DBTable { | |
String name() default ""; | |
} |
- 上述定义了一个名为
DBTable
的注解,该注解主要用于数据库表与Bean
类的映射,与前面Test
注解不同的是我们声明一个String
类型的name
元素,其默认值为空。 - 但是必须注意到对应任何元素的声明采用方法的声明方式,同时可选择使用
default
提供默认值。
package top.rem.rain.annotation.demo; | |
/** | |
* @Author: LightRain | |
* @Description: 数据表实体映射 | |
* @DateTime: 2023-11-10 16:02 | |
* @Version:1.0 | |
**/ | |
// 在类上使用该注解 | |
@DBTable(name = "MEMBER") | |
public class Member { | |
//... | |
} |
- 关于注解支持的元素数据类型如下:
int
float
boolean
byte
double
char
long
short
String
Class
enum
Annotation
- 以及上述类型的数组
注意:如使用了其它数据类型,编译器将会丢出一个编译错误,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是注解嵌套,以下代码演示上述类型的使用。
package top.rem.rain.annotation.demo; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 数据类型使用示例 | |
* @author LightRain | |
*/ | |
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
@interface Reference { | |
boolean next() default false; | |
} | |
/** | |
* @author LightRain | |
*/ | |
public @interface AnnotationElementDemo { | |
/** | |
* 枚举类型 | |
*/ | |
enum Status {FIXED, NORMAL}; | |
/** | |
* 声明枚举 | |
* @return Status | |
*/ | |
Status status() default Status.FIXED; | |
/** | |
* 布尔类型 | |
* @return boolean | |
*/ | |
boolean showSupport() default false; | |
/** | |
* String 类型 | |
* @return String | |
*/ | |
String name() default ""; | |
/** | |
* class 类型 | |
* @return Class | |
*/ | |
Class<?> testCase() default Void.class; | |
/** | |
* 注解嵌套 | |
* @return Reference | |
*/ | |
Reference reference() default @Reference(next = true); | |
/** | |
* 数组类型 | |
* @return long [] | |
*/ | |
long[] value(); | |
} |
# 编译器对默认值的限制
- 编译器对元素的默认值有些挑剔。
- 首先,元素不能有不确定的值,也就是说元素必须要么具有默认值,要么在使用注解时提供元素的值。
- 其次,对于非基本类型的元素,无论是在源代码中声明还是在注解接口中定义默认值,都不能以
null
作为值。 - 但造成一个元素的存在或缺失状态,因为每个注解的声明中所有的元素都存在,并且都具有相应的值为了绕开这个限制只能定义一些特殊值,例如:空字符串或负数来表示某个元素不存在。
# 注解不能继承
- 注解是不支持继承的,因此不能使用关键字
extends
来继承某个@interface
。 - 但注解在编译后,编译器会自动继承
java.lang.annotation.Annotation
接口。 - 我们这里反编译一下前面定义的
DBTable
注解。
package top.rem.rain.annotation.demo; | |
import java.lang.annotation.Annotation; | |
// 反编译后的代码 | |
public interface DBTable extends Annotation { | |
public abstract String name(); | |
} |
虽然反编译后发现
DBTable
注解继承了Annotation
接口,请记住即使Java
的接口可以实现多继承,但定义注解时依然无法使用extends
关键字来继承@interface
。
# 快捷方式
- 快捷方式即使注解中定义了名为
value
的元素,并且在使用该注解是,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value
的语法。 - 只需要在括号内给出
value
元素所需要的值即可,这可以应用于任何合法类型的元素,记住这限制了元素名必须为value
。
package top.rem.rain.annotation.demo; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* @Author: LightRain | |
* @Description: 快捷方式 | |
* @DateTime: 2023-11-10 16:29 | |
* @Version:1.0 | |
**/ | |
// 定义注解 | |
@Target(ElementType.FIELD) | |
@Retention(RetentionPolicy.RUNTIME) | |
@interface IntegerValue { | |
int value() default 0; | |
String name() default ""; | |
} | |
/** | |
* 使用注解 | |
* @author LightRain | |
*/ | |
public class QuicklyWay { | |
/** | |
* 当只想给 value 赋值时,可以使用以下快捷方式 | |
*/ | |
@IntegerValue(20) | |
public int age; | |
/** | |
* 当 name 也需要赋值时必须采用 key=value 的方式赋值 | |
*/ | |
@IntegerValue(value = 10000, name = "MONEY") | |
public int money; | |
} |
# 内置注解与其他元注解
- Java 提供的内置注解主要有
5
个,如下:
# @Override
package java.lang; | |
import java.lang.annotation.*; | |
@Target(ElementType.METHOD) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Override { | |
} |
# @Deprecated
package java.lang; | |
import java.lang.annotation.*; | |
import static java.lang.annotation.ElementType.*; | |
@Documented | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) | |
public @interface Deprecated { | |
String since() default ""; | |
boolean forRemoval() default false; | |
} |
# @SuppressWarnings
- 内部
String
数组主要接参数如下:deprecation
:使用了不赞成使用的类或方法时的警告。unchecked
:执行了未检查的转换时的警告,例如当使用集合时未使用泛型 (Generice
) 来指定集合保存的类型。fallthrough
:当Switch
程序块直接通往下一种情况而没有break
时的警告。path
:在类路径、源文件路径等中有不存在的路径是的警告。serial
:当在可序列化的类上缺少serialVersionUID
定义时的警告。finally
:然后finally
子句不能正常完成时的警告。all
:关于以上所有情况的警告。
package java.lang; | |
import java.lang.annotation.*; | |
import static java.lang.annotation.ElementType.*; | |
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface SuppressWarnings { | |
String[] value(); | |
} |
# @SafeVarargs
package java.lang; | |
import java.lang.annotation.*; | |
@Documented | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) | |
public @interface SafeVarargs { | |
} |
# @FunctionalInterface
package java.lang; | |
import java.lang.annotation.*; | |
@Documented | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(ElementType.TYPE) | |
public @interface FunctionalInterface { | |
} |
# 另外两种元注解
前面我们分析了两种元注解:
@Target
和@Retention
除了这两种元注解外,Java
还提供了另外两种元注解,@Documented
和@Inherited
。
@Documented
:被修饰的注解会生成到Javadoc
中。
// 使用 @Documented | |
@Documented | |
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface DocumentA { | |
} | |
// 没有使用 @Documented | |
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface DocumentB { | |
} | |
// 使用注解 | |
@DocumentA | |
@DocumentB | |
public class DocumentDemo { | |
public void A(){ | |
} | |
} |
使用
javadoc
命令生成文档:javadoc DocumentDemo.java DocumentA.java DocumentB.java
@Inherited
:可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited
可以让子类Class
对象使用getAnnotations()
方法来获取父类被@Inherited
修饰的注解。
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.*; | |
/** | |
* 带 @Inherited 的注解示例 | |
* @author LightRain | |
*/ | |
// 使用 @Inherited | |
@Inherited | |
@Documented | |
@Target(java.lang.annotation.ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface DocumentA { | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 不带 @Inherited 注解示例 | |
* @author LightRain | |
*/ | |
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface DocumentB { | |
} |
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.DocumentA; | |
import top.rem.rain.annotation.demo.custom.annotations.DocumentB; | |
import java.util.Arrays; | |
@DocumentA | |
class A { | |
} | |
class B extends A { | |
} | |
@DocumentB | |
class C { | |
} | |
class D extends C { | |
} | |
/** | |
* @Author: LightRain | |
* @Description: 测试 @Inherited 注解 | |
* @DateTime: 2023-11-10 16:40 | |
* @Version:1.0 | |
**/ | |
public class DocumentDemo { | |
public static void main(String... args) { | |
A instanceA = new B(); | |
System.out.println("已使用的@Inherited注解:" + Arrays.toString(instanceA.getClass().getAnnotations())); | |
C instanceC = new D(); | |
System.out.println("没有使用的@Inherited注解:" + Arrays.toString(instanceC.getClass().getAnnotations())); | |
} | |
/* | |
运行结果: | |
已使用的 @Inherited 注解:[@top.rem.rain.annotation.demo.custom.annotations.DocumentA ()] | |
没有使用的 @Inherited 注解:[] | |
*/ | |
} |
# 注解 & 反射机制
- 前面经过反编译后,我们知道
Java
所有注解都继承了Annotation
接口,也就是说Java
使用Annotation
接口代表注解元素,该接口是所有Annotation
类型的父接口。 - 同时为了运行时能准确获取到注解的相关信息,
Java
在java.lang.reflect
反射包下新增了AnnotatedElement
接口。 - 主要用于表示目前正在
VM
中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术读取注解的信息。 - 如反射包的
Constructor类
、Field类
、Method类
、Package类
和Class类
都实现了AnnotatedElement
接口。Class
:类的 Class 对象定义。Constructor
:代表类的构造器定义。Field
:代表类的成员变量定义。Method
:代表类的方法定义。Package
:代表类的包定义。
- 下面是
AnnotatedElement
中相关的 API 方法,以上 5 个类都实现以下的方法。
返回值 | 方法名称 | 说明 |
---|---|---|
<A extends Annotation> | getAnnotation(Class<A> annotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null 。 |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的。 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true ,否则返回 false 。 |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为 0 的数组 |
案例如下:
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.DocumentA; | |
import top.rem.rain.annotation.demo.custom.annotations.DocumentB; | |
import java.lang.annotation.Annotation; | |
import java.util.Arrays; | |
@DocumentA | |
class A2 { | |
//... | |
} | |
/** | |
* @Author: LightRain | |
* @Description: 继承了 A2 类 | |
* @DateTime: 2023-11-10 16:47 | |
* @Version:1.0 | |
**/ | |
@DocumentB | |
public class DocumentDemo2 extends A2 { | |
public static void main(String... args) { | |
Class<?> clazz = DocumentDemo2.class; | |
// 根据指定注解类型获取该注解 | |
DocumentA documentA = clazz.getAnnotation(DocumentA.class); | |
System.out.println("A:" + documentA); | |
// 获取该元素上的所有注解,包含从父类继承 | |
Annotation[] an = clazz.getAnnotations(); | |
System.out.println("an:" + Arrays.toString(an)); | |
// 获取该元素上的所有注解,但不包含继承! | |
Annotation[] an2 = clazz.getDeclaredAnnotations(); | |
System.out.println("an2:" + Arrays.toString(an2)); | |
// 判断注解 DocumentA 是否在该元素上 | |
boolean b = clazz.isAnnotationPresent(DocumentA.class); | |
System.out.println("b:" + b); | |
/* | |
执行结果: | |
A:@top.rem.rain.annotation.demo.custom.annotations.DocumentA () | |
an:[@top.rem.rain.annotation.demo.custom.annotations.DocumentA (), @top.rem.rain.annotation.demo.custom.annotations.DocumentB ()] | |
an2:[@top.rem.rain.annotation.demo.custom.annotations.DocumentB ()] | |
b:true | |
*/ | |
} | |
} |
# 注解处理器
- 了解完注解与反射的相关
API
后,现在通过一个实例来演示利用运行时注解来创建数据库SQL
构建语句的过程。
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 对应数据表注解 | |
* @author LightRain | |
*/ | |
// 作用于类上 | |
@Target(ElementType.TYPE) | |
// 保留到运行期 | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface DBTable { | |
/** | |
* 数据表名称 | |
* @return String | |
*/ | |
String name() default ""; | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 注解 Integer 类型的字段 | |
* @author LightRain | |
*/ | |
// 只作用于字段 | |
@Target(ElementType.FIELD) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface SQLInteger { | |
/** | |
* 该字段对应数据库表列名 | |
* @return String | |
*/ | |
String name() default ""; | |
/** | |
* 嵌套注解 | |
* @return Constraints | |
*/ | |
Constraints constraint() default @Constraints; | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 注解 String 类型的字段 | |
* @author LightRain | |
*/ | |
// 只作用于字段 | |
@Target(ElementType.FIELD) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface SQLString { | |
/** | |
* 对应数据库表的列名 | |
* @return String | |
*/ | |
String name() default ""; | |
/** | |
* 列类型分配的长度,如:varchar (30) | |
* @return int | |
*/ | |
int value() default 0; | |
/** | |
* 嵌套注解 | |
* @return Constraints | |
*/ | |
Constraints constraint() default @Constraints; | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 约束注解 | |
* @author LightRain | |
*/ | |
// 只作用于字段 | |
@Target(ElementType.FIELD) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Constraints { | |
/** | |
* 判断是否作为主键约束 | |
* @return boolean | |
*/ | |
boolean primaryKey() default false; | |
/** | |
* 判断是否允许为 null | |
* @return boolean | |
*/ | |
boolean allowNull() default false; | |
/** | |
* 判断是否唯一 | |
* @return boolean | |
*/ | |
boolean unique() default false; | |
} |
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.Constraints; | |
import top.rem.rain.annotation.demo.custom.annotations.DBTable; | |
import top.rem.rain.annotation.demo.custom.annotations.SQLInteger; | |
import top.rem.rain.annotation.demo.custom.annotations.SQLString; | |
/** | |
* @Author: LightRain | |
* @Description: 数据表实体映射 | |
* @DateTime: 2023-11-10 16:02 | |
* @Version:1.0 | |
**/ | |
@DBTable(name = "MEMBER") | |
public class Member { | |
/** | |
* 主键 ID | |
*/ | |
@SQLString(name = "ID", value = 108, constraint = @Constraints(primaryKey = true)) | |
private String id; | |
/** | |
* 名称 | |
*/ | |
@SQLString(name = "NAME", value = 30) | |
private String name; | |
/** | |
* 年龄 | |
*/ | |
@SQLInteger(name = "AGE") | |
private int age; | |
/** | |
* 个人描述 | |
*/ | |
@SQLString(name = "DESCRIPTION", value = 150, constraint = @Constraints(allowNull = true)) | |
private String description; | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public int getAge() { | |
return age; | |
} | |
public void setAge(int age) { | |
this.age = age; | |
} | |
public String getDescription() { | |
return description; | |
} | |
public void setDescription(String description) { | |
this.description = description; | |
} | |
} |
- 上述定义
4
个注解,分别是@DBTable(用于类)
、@Constraints(用于字段)
、@SQLString(用于字段)
、@SQLInteger(用于字段)
并在Member
类中使用这些注解。 - 这些注解的作用是用于帮助注解处理器生成创建数据库表
MEMBER
的构建语句,在这里需要注意的是,我们使用了嵌套注解@Constraints
该注解主要用于判断字段是否为null
或者字段是否唯一。 - 必须清楚认识到上述提供的注解生命周期必须为
@Retention(RetentionPolicy.RUNTIME)
运行时,这样才可以使用反射机制获取其信息。 - 剩余的就是编写上述的注解处理器,前面我们聊了很多注解,其处理器要么是
Java
自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写。 - 但上述定义处理
SQL
的注解,其处理器必须由我们自己编写,如下。
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.Constraints; | |
import top.rem.rain.annotation.demo.custom.annotations.DBTable; | |
import top.rem.rain.annotation.demo.custom.annotations.SQLInteger; | |
import top.rem.rain.annotation.demo.custom.annotations.SQLString; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.Field; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* @Author: LightRain | |
* @Description: 注解处理器,构造表创建语句 | |
* @DateTime: 2023-11-10 19:22 | |
* @Version:1.0 | |
**/ | |
public class TableCreator { | |
/** | |
* 创建表 SQL | |
* | |
* @param className 类名称 | |
* @return String | |
* @throws ClassNotFoundException 未找到类异常 | |
*/ | |
public static String createTableSql(String className) throws ClassNotFoundException { | |
// 获取字节码文件 | |
Class<?> cl = Class.forName(className); | |
// 获取指定注解类型的注解 | |
DBTable dbTable = cl.getAnnotation(DBTable.class); | |
// 如果没有表注解,直接返回 | |
if (dbTable == null) { | |
System.out.println("No DBTable annotations in class " + className); | |
return null; | |
} | |
// 获取数据表名称 | |
String tableName = dbTable.name(); | |
// 判断数据表名称长度 | |
if (tableName.length() < 1) { | |
// 如果数据表名称为空则使用类名代替 | |
tableName = cl.getName().toUpperCase(); | |
} | |
// 创建 List 集合 | |
List<String> columnDefs = new ArrayList<String>(); | |
// 通过字节码文件获取到所有成员字段 | |
for (Field field : cl.getDeclaredFields()) { | |
String columnName = null; | |
// 获取字段元素上的注解 | |
Annotation[] declaredAnnotations = field.getDeclaredAnnotations(); | |
// 判断长度是否存在注解,没有则跳过进入下次循环 | |
if (declaredAnnotations.length < 1) { | |
continue; | |
} | |
// 判断注解类型 | |
if (declaredAnnotations[0] instanceof SQLInteger) { | |
SQLInteger sqlInt = (SQLInteger) declaredAnnotations[0]; | |
// 获取字段对应名称,如果没有就是使用字段名称替代 | |
if (sqlInt.name().length() < 1) { | |
columnName = field.getName().toUpperCase(); | |
} else { | |
columnName = sqlInt.name(); | |
} | |
// 构建语句 | |
columnDefs.add(columnName + " INT" + getConstraints(sqlInt.constraint())); | |
} | |
// 判断注解类型 | |
if (declaredAnnotations[0] instanceof SQLString) { | |
SQLString sqlStr = (SQLString) declaredAnnotations[0]; | |
// 获取字段对应名称,如果没有就是使用字段名称替代 | |
if (sqlStr.name().length() < 1) { | |
columnName = field.getName().toUpperCase(); | |
} else { | |
columnName = sqlStr.name(); | |
} | |
// 构建语句 | |
columnDefs.add(columnName + " VARCHAR(" + sqlStr.value() + ")" + getConstraints(sqlStr.constraint())); | |
} | |
} | |
// 数据库表构建语句 | |
StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "("); | |
for (String columnDef : columnDefs) { | |
createCommand.append("\n ").append(columnDef).append(","); | |
} | |
// 删除尾部逗号并返回 | |
return createCommand.substring(0, createCommand.length() - 1) + ");"; | |
} | |
/** | |
* 判断该字段是否有其他约束 | |
* | |
* @param con Constraints | |
* @return String | |
*/ | |
private static String getConstraints(Constraints con) { | |
// 约束条件 | |
String constraints = ""; | |
if (!con.allowNull()) { | |
constraints += " NOT NULL"; | |
} | |
// 主键 | |
if (con.primaryKey()) { | |
constraints += " PRIMARY KEY"; | |
} | |
// 唯一 | |
if (con.unique()) { | |
constraints += " UNIQUE"; | |
} | |
return constraints; | |
} | |
public static void main(String[] args) throws Exception { | |
// 打印建表语句 | |
System.out.println("Table Creation SQL for " + "top.rem.rain.annotation.demo.custom.clas.Member" + " is :\n" + createTableSql(Member.class.getName())); | |
/* | |
输出结果: | |
Table Creation SQL for top.rem.rain.annotation.demo.custom.clas.Member is : | |
CREATE TABLE MEMBER ( | |
ID VARCHAR (108) NOT NULL PRIMARY KEY, | |
NAME VARCHAR (30) NOT NULL, | |
AGE INT NOT NULL, | |
DESCRIPTION VARCHAR (150)); | |
*/ | |
} | |
} |
- 上述我们通过传递
Member
的全路径通过Class.forName()
方法获取到Member
的class
对象,然后利用Class
对象中的方法获取所有成员字段Field
。 - 最后我们利用
field.getDeclaredAnnotations()
方法遍历每个Field
上的注解再通过注解的类型判断来构建建表语句。 - 这便是利用注解结合反射来构建
SQL
语句的简单的处理器模型。
# 注解增强
# @Repeatable 元注解
@Repeatable
元注解是JDK1.8
加入的,它表示在同一个位置重复相同的注解。- 在没有该注解前一般无法在同一个类型上使用相同的注解。
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.FilterPath; | |
/** | |
* @Author: LightRain | |
* @Description: 使用重复过滤器注解示例,Java8 之前无法这样使用 | |
* @DateTime: 2023-11-11 18:46 | |
* @Version:1.0 | |
**/ | |
@FilterPath("/web/post") | |
@FilterPath("/web/get") | |
public class FilterDemo { | |
//... | |
} |
Java8
之前如果想实现类似功能,我们需要定义@FilterPath
注解时定义一个数组元素接收多个值。
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 自定义过滤器 | |
* @author LightRain | |
*/ | |
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface FilterPath { | |
String [] value(); | |
} |
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.FilterPath; | |
/** | |
* @Author: LightRain | |
* @Description: 过滤器注解使用示例 | |
* @DateTime: 2023-11-11 18:46 | |
* @Version:1.0 | |
**/ | |
@FilterPath({"/web/post","/web/get"}) | |
public class FilterDemo2 { | |
} |
- 但在
Java8
新增了@Repeatable
注解后就可以采用以下方式定义并使用。
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.*; | |
/** | |
* 自定义过滤器,Java8 增强 | |
* 使用 Java8 新增 @Repeatable 元注解 | |
* | |
* @author LightRain | |
*/ | |
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) | |
@Retention(RetentionPolicy.RUNTIME) | |
// 参数指明接收的注解 class | |
@Repeatable(FilterPaths.class) | |
public @interface FilterPath { | |
String value(); | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 自定义过滤器数组 | |
* @author LightRain | |
*/ | |
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface FilterPaths { | |
/** | |
* 此处使用 FilterPath 数组进行接收 | |
* @return FilterPath | |
*/ | |
FilterPath[] value(); | |
} |
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.FilterPath; | |
/** | |
* @Author: LightRain | |
* @Description: 使用 java8 增强后的重复过滤器注解示例 | |
* @DateTime: 2023-11-11 18:58 | |
* @Version:1.0 | |
**/ | |
@FilterPath("/web/get") | |
@FilterPath("/web/post") | |
public class FilterDemo { | |
//... | |
} |
- 我们可以简单的理解为通过使用
@Repeatable
元注解后,将使用@FilterPaths
注解作为接收同一个类型上重复注解的容器。 - 而每个
@FilterPath
则负责保存指定的路径串,为了处理上述的新增注解,Java8
还在AnnotatedElement
接口新增了getDeclaredAnnotationsByType()
和getAnnotationsByType()
两个方法并在接口给出了默认实现。 - 在指定
@Repeatable
的注解时可以通过这两个方法获取到注解相关信息,但请注意旧版API
中的getDeclaredAnnotationsByType()
和getAnnotation
是不对@Repeatable
注解的处理的。 - 除非该注解没有在同一个声明上重复出现,注意
getDeclaredAnnotationsByType()
方法获取到的注解不包含父类,其中当getAnnotationsByType()
方法调用是其内部先执行了getDeclaredAnnotationsByType
方法。 - 只有当前类不存在指定注解时
getAnnotationsByType()
才会继续从其父类寻找,但请注意如果@FilterPath
和@FilterPaths
没有使用了@Inherited
的话扔然无法获取。
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.FilterPath; | |
@FilterPath("/web/test") | |
class TestDemo { | |
} | |
/** | |
* @Author: LightRain | |
* @Description: 获取 @Repeatable 注解相关信息 | |
* @DateTime: 2023-11-11 19:03 | |
* @Version:1.0 | |
**/ | |
@FilterPath("/web/get") | |
@FilterPath("/web/post") | |
public class GetRepeatableAnnotationsInformation extends TestDemo { | |
public static void main(String[] args) { | |
// 获取字节码对象 | |
Class<?> clazz = GetRepeatableAnnotationsInformation.class; | |
// 通过 getAnnotationsByType 方法获取所有重复注解 | |
FilterPath[] annotationsByType = clazz.getAnnotationsByType(FilterPath.class); | |
FilterPath[] annotationsByType2 = clazz.getDeclaredAnnotationsByType(FilterPath.class); | |
for (FilterPath filter : annotationsByType) { | |
System.out.println("1:" + filter.value()); | |
} | |
System.out.println("-----------------"); | |
for (FilterPath filter : annotationsByType2) { | |
System.out.println("2:" + filter.value()); | |
} | |
System.out.println("使用getAnnotation的结果:" + clazz.getAnnotation(FilterPath.class)); | |
/* | |
执行结果:当前类拥有该注解 @FilterPath 时则不会从 TestDemo 父类寻找 | |
1:/web/get | |
1:/web/post | |
----------------- | |
2:/web/get | |
2:/web/post | |
使用 getAnnotation 的结果:null | |
*/ | |
} | |
} |
咱们来看执行结果,如果当前类拥有
@FilterPath
注解则getAnnotationsByType()
方法不会去父类中寻找,下面来看另一种情况,即GetRepeatableAnnotationsInformation
类上面没有@FilterPath
注解的示例。
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.*; | |
/** | |
* 自定义过滤器,Java8 增强 | |
* 使用 Java8 新增 @Repeatable 元注解 | |
* 并添加 @Inherited 可继承元注解 | |
* | |
* @author LightRain | |
*/ | |
// 添加可继承的元注解 | |
@Inherited | |
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) | |
@Retention(RetentionPolicy.RUNTIME) | |
// 参数指明接收的注解 class | |
@Repeatable(FilterPaths.class) | |
public @interface FilterPath { | |
String value(); | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 自定义过滤器数组 | |
* 同样添加 @Inherited 可继承元注解 | |
* @author LightRain | |
*/ | |
// 添加可继承的元注解 | |
@Inherited | |
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface FilterPaths { | |
/** | |
* 此处使用 FilterPath 数组进行接收 | |
* @return FilterPath | |
*/ | |
FilterPath[] value(); | |
} |
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.FilterPath; | |
@FilterPath("/web/test") | |
class TestDemo { | |
} | |
/** | |
* @Author: LightRain | |
* @Description: 获取父类 @Repeatable 注解相关信息 | |
* @DateTime: 2023-11-11 19:13 | |
* @Version:1.0 | |
**/ | |
//@FilterPath("/web/get") | |
//@FilterPath("/web/post") | |
public class GetRepeatableAnnotationsInformation extends TestDemo { | |
public static void main(String[] args) { | |
// 获取字节码对象 | |
Class<?> clazz = GetRepeatableAnnotationsInformation.class; | |
// 通过 getAnnotationsByType 方法获取所有重复注解 | |
FilterPath[] annotationsByType = clazz.getAnnotationsByType(FilterPath.class); | |
FilterPath[] annotationsByType2 = clazz.getDeclaredAnnotationsByType(FilterPath.class); | |
for (FilterPath filter : annotationsByType) { | |
System.out.println("1:" + filter.value()); | |
} | |
System.out.println("-----------------"); | |
for (FilterPath filter : annotationsByType2) { | |
System.out.println("2:" + filter.value()); | |
} | |
System.out.println("使用getAnnotation的结果:" + clazz.getAnnotation(FilterPath.class)); | |
/* | |
执行结果:将当前类拥有的 @FilterPath 注解注释掉后 getAnnotationsByType () 方法会去父类寻找 | |
1:/web/test | |
1:/web/demo | |
----------------- | |
使用 getAnnotation 的结果:null | |
*/ | |
} | |
} |
需要值得注意的是在定义
@FilterPath
和@FilterPaths
注解时必须添加@Inherited
元注解才可以通过父类被获取到,否则无法通过getAnnotationsByType()
方法从父类中获取@FilterPath
注解,这是为什么呢?下面来看下getAnnotationsByType()
方法的实现原理。
package java.lang.reflect; | |
import java.lang.annotation.Annotation; | |
import java.lang.annotation.AnnotationFormatError; | |
import java.lang.annotation.Repeatable; | |
import java.util.Arrays; | |
import java.util.LinkedHashMap; | |
import java.util.Map; | |
import java.util.Objects; | |
import java.util.function.Function; | |
import java.util.stream.Collectors; | |
import sun.reflect.annotation.AnnotationSupport; | |
import sun.reflect.annotation.AnnotationType; | |
public interface AnnotatedElement { | |
/** | |
* 返回与该元素关联的注解,接口默认实现方法 | |
* @param annotationClass 注释类 | |
* @return T [] | |
* @param <T> T | |
*/ | |
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) { | |
// 内部会先调用 getDeclaredAnnotationsByType () 方法 | |
T[] result = getDeclaredAnnotationsByType(annotationClass); | |
// 判断当前类获取到的注解数组是否为 0 | |
if (result.length == 0 && this instanceof Class && | |
// 判断定义注解上是否使用了 @Inherited 元注解 | |
AnnotationType.getInstance(annotationClass).isInherited()) { | |
// 从父类中获取 | |
Class<?> superClass = ((Class<?>) this).getSuperclass(); | |
if (superClass != null) { | |
// 获取父类中的注释 | |
result = superClass.getAnnotationsByType(annotationClass); | |
} | |
} | |
return result; | |
} | |
} |
# 两种新 ElementType 类型
ElementType
在Java8
中新增了两个新的枚举成员,TYPE_PARAMETER
和TYPE_USE
在Java8
前注解只能标注在一个声明的类、方法、字段上。- 在
Java8
之后新增的TYPE_PARAMETER
可以用于标注类型参数,而TYPE_USE
则可以用于标注任意类型但不包含class
。
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 使用 TYPE_PARAMETER 类型 | |
* TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中,泛型 例如:<@ElementTypeDemo1 T> | |
* @author LightRain | |
*/ | |
@Target(ElementType.TYPE_PARAMETER) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface TypeParam { | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* 使用 TYPE_USE 类型 | |
* TYPE.USE:表示可以在任何用到类型的地方使用 | |
* @author LightRain | |
*/ | |
@Target(ElementType.TYPE_USE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Not { | |
} |
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.Not; | |
import top.rem.rain.annotation.demo.custom.annotations.TypeParam; | |
/** | |
* @Author: LightRain | |
* @Description: 类型注解 @TypeParam 跟 @Not | |
* @DateTime: 2023-11-11 22:59 | |
* @Version:1.0 | |
**/ | |
public class ElementTypeDemo<@TypeParam T> { | |
@Not long lon; | |
public <@TypeParam T extends Integer> void test(@Not String str){ | |
@Not boolean boo; | |
} | |
} |
package top.rem.rain.annotation.demo.custom.annotations; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Target; | |
/** | |
* @author LightRain | |
*/ | |
@Target(ElementType.TYPE_USE) | |
public @interface Path { | |
} |
package top.rem.rain.annotation.demo.custom.clas; | |
import top.rem.rain.annotation.demo.custom.annotations.Not; | |
import top.rem.rain.annotation.demo.custom.annotations.Path; | |
import top.rem.rain.annotation.demo.custom.annotations.TypeParam; | |
/** | |
* @Author: LightRain | |
* @Description: 类型注解 @TypeParam 跟 @Not | |
* @DateTime: 2023-11-11 22:59 | |
* @Version:1.0 | |
**/ | |
public class ElementTypeDemo{ | |
public static void main(String[] args) { | |
// 用于构造函数 | |
String path = new @Path String("/usr/bin"); | |
System.out.println(path); | |
/* | |
还可以用于一下两种情况: | |
用于指定异常 | |
public Person read () throws @Localized IOException. | |
用于通配符绑定 | |
List<@ReadOnly ? extends Person> | |
List<? extends @ReadOnly Person> | |
*/ | |
} | |
} |
主要说明一下
TYPE_USE
注解类型是用来支持在Java
程序中做强类型检查和配合第三方插件工具的如:CheckerFramework
,可以在编译期间检测出RuntimeError
如:UnsupportedOperationException
、NullPointerException
异常,避免异常延续到运行期后发现,从而提高代码质量这就是类型注解的作用。