框架基础:深入理解Java注解类型(@Annotation)
正文
注解的概念
注解的官方定义
首先看看官方对注解的描述:
An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.
翻译:
注解是一种能被添加到 java 代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。
通过官方描述得出以下结论:
注解是一种元数据形式。即注解是属于 java 的一种数据类型,和类、接口、数组、枚举类似。
注解用来修饰,类、方法、变量、参数、包。
注解不会对所修饰的代码产生直接的影响。
注解的使用范围
继续看看官方对它的使用范围的描述:
Annotations have a number of uses, among them:Information for the complier - Annotations can be used by the compiler to detect errors or suppress warnings.Compiler-time and deployment-time processing - Software tools can process annotation information to generate code, XML files, and so forth.Runtime processing - Some annotations are available to be examined at runtime.
翻译:
注解又许多用法,其中有:为编译器提供信息 - 注解能被编译器检测到错误或抑制警告。编译时和部署时的处理 - 软件工具能处理注解信息从而生成代码,XML 文件等等。运行时的处理 - 有些注解在运行时能被检测到。
##2 如何自定义注解
基于上一节,已对注解有了一个基本的认识:注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。因此可以得出自定义注解使用的基本流程:
第一步,定义注解——相当于定义标记;
第二步,配置注解——把标记打在需要用到的程序代码中;
第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。
基本语法
注解类型的声明部分:
注解在 Java 中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface
。在底层实现上,所有定义的注解都会自动继承 java.lang.annotation.Annotation 接口。
public @interface CherryAnnotation {
}
注解类型的实现部分:
根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。咱们来看看其语法:
public @interface CherryAnnotation { public String name(); int age() default 18; int[] array(); }
定义注解类型元素时需要注意如下几点:
-
访问修饰符必须为 public,不写默认为 public;
-
该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
-
该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为 value(后面使用会带来便利操作);
-
() 不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
-
default 代表默认值,值必须和第 2 点定义的类型一致;
-
如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
常用的元注解
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用 JDK 自带注解的时候发现,有些注解只能写在方法上面(比如 @Override);有些却可以写在类的上面(比如 @Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。
@Target
@Target 注解,是专门用来限定某个自定义注解能够被应用在哪些 Java 元素上面的。它使用一个枚举类型定义如下:
public enum ElementType {
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 属性的声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
FIELD,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 方法的声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
METHOD,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 方法形式参数声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
PARAMETER,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 构造方法的声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
CONSTRUCTOR,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 局部变量声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
LOCAL_VARIABLE,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 注解类型声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
ANNOTATION_TYPE,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 包的声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
PACKAGE
}
//@CherryAnnotation 被限定只能使用在类、接口或方法上面 @Target(value = {ElementType.TYPE,ElementType.METHOD}) public @interface CherryAnnotation {String name(); int age() default 18; int[] array(); }
@Retention
@Retention 注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:1、Java 源文件阶段;2、编译到 class 文件阶段;3、运行期阶段。同样使用了 RetentionPolicy 枚举类型定义了三个阶段:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* (注解将被编译器忽略掉)
*/
SOURCE,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* 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文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
CLASS,
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* 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.
* (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
* </span><span style="color: rgba(128, 128, 128, 1)">@see</span><span style="color: rgba(0, 128, 0, 1)"> java.lang.reflect.AnnotatedElement
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
RUNTIME
}
我们再详解一下:
-
如果一个注解被定义为 RetentionPolicy.SOURCE,则它将被限定在 Java 源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读 Java 文件的人看到;
-
如果一个注解被定义为 RetentionPolicy.CLASS,则它将被编译到 Class 文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时 JVM(Java 虚拟机)会忽略它,我们在运行期也不能读取到;
-
如果一个注解被定义为 RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到 Class 对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的 RetentionPolicy.RUNTIME;
自定义注解
在具体的 Java 类上使用注解
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @Documented public @interface CherryAnnotation {String name(); int age() default 18; int[] score(); }
public class Student { @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77}) public void study(int times){ for(int i = 0; i < times; i++){ System.out.println("Good Good Study, Day Day Up!");} } }
简单分析下:
-
CherryAnnotation 的 @Target 定义为 ElementType.METHOD,那么它书写的位置应该在方法定义的上方,即:public void study(int times) 之上;
-
由于我们在 CherryAnnotation 中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上 (),并且在() 内以“元素名 = 元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;
注解与反射机制
为了运行时能准确获取到注解的相关信息,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 的数组 |
简单案例演示如下:
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DocumentA { }
package com.zejian.annotationdemo;
import java.lang.annotation.Annotation;
import java.util.Arrays;
@DocumentA
class A{
}
//继承了 A 类
@DocumentB
public class DocumentDemo extends A{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String... args){
Class</span><?> clazz = DocumentDemo.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">根据指定注解类型获取该注解</span>
DocumentA documentA=clazz.getAnnotation(DocumentA.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
System.out.println(</span>"A:"+<span style="color: rgba(0, 0, 0, 1)">documentA);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取该元素上的所有注解,包含从父类继承</span>
Annotation[] an=<span style="color: rgba(0, 0, 0, 1)"> clazz.getAnnotations();
System.out.println(</span>"an:"+<span style="color: rgba(0, 0, 0, 1)"> Arrays.toString(an));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取该元素上的所有注解,但不包含继承!</span>
Annotation[] an2=<span style="color: rgba(0, 0, 0, 1)">clazz.getDeclaredAnnotations();
System.out.println(</span>"an2:"+<span style="color: rgba(0, 0, 0, 1)"> Arrays.toString(an2));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">判断注解DocumentA是否在该元素上</span>
<span style="color: rgba(0, 0, 255, 1)">boolean</span> b=clazz.isAnnotationPresent(DocumentA.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
System.out.println(</span>"b:"+<span style="color: rgba(0, 0, 0, 1)">b);
}
}
执行结果:
A:@com.zejian.annotationdemo.DocumentA()an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()] an2:@com.zejian.annotationdemo.DocumentB() b:true
通过反射获取上面我们自定义注解
public class TestAnnotation {
public static void main(String[] args){
try {
//获取 Student 的 Class 对象
Class stuClass = Class.forName("pojos.Student");
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">说明一下,这里形参不能写成Integer.class,应写为int.class</span>
Method stuMethod = stuClass.getMethod("study",<span style="color: rgba(0, 0, 255, 1)">int</span>.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(stuMethod.isAnnotationPresent(CherryAnnotation.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)){
System.out.println(</span>"Student类上配置了CherryAnnotation注解!"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取该元素上指定类型的注解</span>
CherryAnnotation cherryAnnotation = stuMethod.getAnnotation(CherryAnnotation.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
System.out.println(</span>"name: " + cherryAnnotation.name() + ", age: " +<span style="color: rgba(0, 0, 0, 1)"> cherryAnnotation.age()
</span>+ ", score: " + cherryAnnotation.score()[0<span style="color: rgba(0, 0, 0, 1)">]);
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
System.out.println(</span>"Student类上没有配置CherryAnnotation注解!"<span style="color: rgba(0, 0, 0, 1)">);
}
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (ClassNotFoundException e) {
e.printStackTrace();
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
运行时注解处理器
了解完注解与反射的相关 API 后,现在通过一个实例(该例子是博主改编自《Tinking in Java》)来演示利用运行时注解来组装数据库 SQL 的构建语句的过程
/** * Created by ChenHao on 2019/6/14. * 表注解 */ @Target(ElementType.TYPE)//只能应用于类上 @Retention(RetentionPolicy.RUNTIME)//保存到运行时 public @interface DBTable {String name() default ""; }/**
- Created by ChenHao on 2019/6/14.
- 注解 Integer 类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
//该字段对应数据库表列名
String name() default "";
//嵌套注解
Constraints constraint() default @Constraints;
}/**
Created by ChenHao on 2019/6/14.
注解 String 类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {//对应数据库表的列名
String name() default "";//列类型分配的长度,如 varchar(30) 的 30
int value() default 0;Constraints constraint() default @Constraints;
}/**
- Created by ChenHao on 2019/6/14.
- 约束注解
*/@Target(ElementType.FIELD)//只能应用在字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
//判断是否作为主键约束
boolean primaryKey() default false;
//判断是否允许为 null
boolean allowNull() default false;
//判断是否唯一
boolean unique() default false;
}/**
Created by ChenHao on 2019/6/14.
数据库表 Member 对应实例类 bean
*/
@DBTable(name = "MEMBER")
public class Member {
//主键 ID
@SQLString(name = "ID",value = 50, 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;//个人描述//省略 set get.....
}
上述定义 4 个注解,分别是 @DBTable(用于类上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上) 并在 Member 类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表 MEMBER 的构建语句,在这里有点需要注意的是,我们使用了嵌套注解 @Constraints,该注解主要用于判断字段是否为 null 或者字段是否唯一。必须清楚认识到上述提供的注解生命周期必须为 @Retention(RetentionPolicy.RUNTIME),即运行时,这样才可以使用反射机制获取其信息。有了上述注解和使用,剩余的就是编写上述的注解处理器了,前面我们聊了很多注解,其处理器要么是 Java 自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写,但上述定义处理 SQL 的注解,其处理器必须由我们自己编写了,如下
package com.chenHao.annotationdemo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
- Created by chenhao on 2019/6/14.
- 运行时注解处理器,构造表创建语句
*/
public class TableCreator {
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 the name is empty, use the Class name:
if(tableName.length() < 1)
tableName = cl.getName().toUpperCase();
List<String> columnDefs = new ArrayList<String>();
//通过 Class 类 API 获取到所有成员字段
for(Field field : cl.getDeclaredFields()) {
String columnName = null;
//获取字段上的注解
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1)
continue; // Not a db table column
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">判断注解类型</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(anns[0] <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> SQLInteger) {
SQLInteger sInt </span>= (SQLInteger) anns[0<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取字段对应列名称,如果没有就是使用字段名称替代</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(sInt.name().length() < 1<span style="color: rgba(0, 0, 0, 1)">)
columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> field.getName().toUpperCase();
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> sInt.name();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构建语句</span>
columnDefs.add(columnName + " INT" +<span style="color: rgba(0, 0, 0, 1)">
getConstraints(sInt.constraint()));
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">判断String类型</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(anns[0] <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> SQLString) {
SQLString sString </span>= (SQLString) anns[0<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Use field name if name not specified.</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(sString.name().length() < 1<span style="color: rgba(0, 0, 0, 1)">)
columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> field.getName().toUpperCase();
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> sString.name();
columnDefs.add(columnName </span>+ " VARCHAR(" +<span style="color: rgba(0, 0, 0, 1)">
sString.value() </span>+ ")" +<span style="color: rgba(0, 0, 0, 1)">
getConstraints(sString.constraint()));
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">数据库表构建语句</span>
StringBuilder createCommand = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringBuilder(
</span>"CREATE TABLE " + tableName + "("<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)">(String columnDef : columnDefs)
createCommand.append(</span>"\n " + columnDef + ","<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Remove trailing comma</span>
String tableCreate =<span style="color: rgba(0, 0, 0, 1)"> createCommand.substring(
</span>0, createCommand.length() - 1) + ");"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> tableCreate;
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* 判断该字段是否有其他约束
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> con
* </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
<span style="color: rgba(0, 128, 0, 1)">*/</span>
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 {
String[] arg={"com.zejian.annotationdemo.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for" +
className + "is :\n" + createTableSql(className));
}
}
}
输出结果:
Table Creation SQL for com.zejian.annotationdemo.Member is : CREATE TABLE MEMBER( ID VARCHAR(50) 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 语句。这便是利用注解结合反射来构建 SQL 语句的简单的处理器模型。