前言
在我们平常的代码开发过程中,遇见过无数的注解,大多数注解都是我们使用的框架所给我们集成好了的,相信也很少有人使用自己编写的注解,我也如此,但是只有当你了解了注解背后的秘密后,一定会对它有不同的看法。
注解,也被称为元数据,可以为我们在代码中添加信息提供一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据
注解的优点
使用注解有许多的优点:
- 注解能使编译器来测试和验证格式,存储有关程序的额外信息。
- 注解可以用来生成描述符文件,有助于减轻编写“样板”代码的负担、
- 使用注解可以将这些元数据保存在Java源代码中。并利用annotation API为我们的注解构造处理工具。
- 注解提供编译器类型检查以及更加干净易读的便利。
Java中的注解
目前Java提供三种内置注解:
- @Override,表示当前的方法定义将覆盖超类中的方法。
- @Deprecated,如果程序员使用了注解为它的元素,那么编译器会发出警告信息。
- @SuppressWarnings,关闭不当的编译器警告信息。
除此之外,Java还另外提供了四种元注解,专门负责新注解的创建。可理解为注解的注解。
@Target:表示该注解可以用于什么地方。
参数 | 说明 |
---|---|
CONSTRUCTOR | 构造器的声明 |
FIELD | 域声明(包括enum实例) |
LOCAL_VARIABLE | 局部变量声明 |
METHOD | 方法声明 |
PACKAGE | 包声明 |
PARAMETER | 参数声明 |
TYPE | 类、接口(包括注解类型)或enum声明 |
@Retention:表示需要在什么级别保存该注解信息。
参数 | 说明 |
---|---|
SOURCE | 注解将被编译器丢弃 |
CLASS | 注解在class文件中可用,但会被JVM丢弃 |
RUNTIME | JVM将在运行期也保留注解,因此可以通过反射机制读取注解的信息 |
@Documented:将此注解包含在Javadoc中
@Inherited:允许子类继承父类中的注解
如何定义注解
注解的定义很像接口的定义,并且与其他任何Java接口一样,注解也将会编译成class文件。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
}
除了@
符号外,注解的定义很像一个空的接口。在定义一个注解的时候,会需要使用到我们上面的元注解,如@Target
和@Retention
。像我们这里定义的注解称为标记注解,因为在注解内没有任何元素。上面这个注解的使用方式:@Test。
下面我们来看一下hibernate
中的@Table
注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String name() default "";
String catalog() default "";
String schema() default "";
UniqueConstraint[] uniqueConstraints() default {};
Index[] indexes() default {};
}
从@Target
中可以看出这个注解是应用于类、接口上的,并且是在运行期保存注解信息。该注解中有5个元素,default
为默认值。注解的元素在使用时表现为名-值对的形式,例如我们可以使用@Table(name="myTable")
的方式设置该实体类对应的数据库表名为myTable
。
注解处理器
当我们编写好我们的注解后如果没有用来读取注解的工具的话,那么注解对于我们来说也就没有太大意义了。在Java SE5扩展了反射机制的API,以帮助程序员构造这类工具。
我们首先定义一个简单的注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Person{
String name() default "I don't have name";
int age() default 21;
}
我们将该注解用于一个实体类中:
public class MyLove {
@Person(name = "My name is zhy")
public String zhy(){
return "zhy";
}
@Person(name = "My name is xyx", age = 19)
public String xyx(){
return "xyx";
}
}
接下来我们编写注解处理器,通过反射机制来查找注解中的信息。
public class MyLoveTest {
public static void myLoveTest(List<Integer> ages, Class<?> cl){
Method[] methods = cl.getDeclaredMethods();
for(Method method : methods){
Person person = method.getAnnotation(Person.class);
if(person != null){
System.out.println("My name is " + person.name() + " and I'm " + person.age());
ages.remove(new Integer(person.age()));
}
}
for(int i : ages){
System.out.print("Missing age is " + i);
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 20, 21, 22);
myLoveTest(list, MyLove.class);
}
}
输出结果如下:
My name is xyx and I'm 19
My name is zhy and I'm 21
Missing age is 20
在这个注解处理器程序中,我们用到了两个反射的方法:getDeclaredMethods()
和getAnnotation()
,这两个都是AnnotatedElement
接口(Class、Method和Field等类都实现了该接口)。getAnnotation()
方法返回指定类型的注解对象,在这里就是Person
。如果被注解的方法上没有该类型的注解,则返回null值。然后我们通过调用name()
和age()
方法从Person对象中提取元素的值。
案例驱动
下面我们写一个注解小栗子,它将读取一个实体类,检查其上的数据库注解,并生成用来创建数据库的SQL命令:
下面是我们要使用到的注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: zhangocean
* @Date: 2018/9/23 15:36
*/
//@DBTable注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
String name() default "";
}
//@Constraints注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
}
//@SQLInteger注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
int value() default 10;
String name() default "";
Constraints constraints() default @Constraints;
}
//@SQLString注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 255;
String name() default "";
Constraints constraints() default @Constraints;
}
注解应用到的实体类:
/**
* @author: zhangocean
* @Date: 2018/9/23 15:38
*/
@DBTable(name = "user")
public class User {
@SQLInteger(constraints = @Constraints(primaryKey = true), name = "id")
private int id;
@SQLString(30)
private String username;
@SQLInteger
private Integer age;
}
最后就是注解处理器:
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* @author: zhangocean
* @Date: 2018/9/23 15:43
*/
public class TableCreator {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> userClass = Class.forName("User");
DBTable dbTable = userClass.getAnnotation(DBTable.class);
if(dbTable == null){
System.out.println("No DBTable annotations in class");
}
String tableName = dbTable.name();
if(tableName.length()<1){
tableName = userClass.getName().toUpperCase();
}
List<String> columnNameSqls = new ArrayList<>();
StringBuilder createTable = new StringBuilder();
createTable.append("CREATE TABLE ").append(tableName).append("(");
for(Field field : userClass.getDeclaredFields()){
String columnName = null;
Annotation[] annotations = field.getDeclaredAnnotations();
if(annotations.length < 1){
continue;
}
if(annotations[0] instanceof SQLInteger){
SQLInteger sqlInteger = (SQLInteger) annotations[0];
if(sqlInteger.name().length()<1){
columnName = field.getName().toUpperCase();
} else {
columnName = sqlInteger.name();
}
columnNameSqls.add(columnName + " INT(" +sqlInteger.value() + ") " + getConstraints(sqlInteger.constraints()));
}
if(annotations[0] instanceof SQLString){
SQLString sqlString = (SQLString) annotations[0];
if(sqlString.name().length() < 1){
columnName = field.getName().toUpperCase();
}else {
columnName = sqlString.name();
}
columnNameSqls.add(columnName + " VARCHAR(" + sqlString.value() + ") " + getConstraints(sqlString.constraints()));
}
}
for(int i=0;i<columnNameSqls.size();i++){
if(i != (columnNameSqls.size()-1)){
createTable.append("\n ").append(columnNameSqls.get(i)).append(",");
} else {
createTable.append("\n ").append(columnNameSqls.get(i));
}
}
createTable.append("\n);");
System.out.println(createTable);
}
private static String getConstraints(Constraints con){
String constranints = "";
if(!con.allowNull()){
constranints += "NOT NULL";
}
if(con.primaryKey()){
constranints += "PRIMARY KEY";
}
return constranints;
}
}
先来看看运行结果把:
CREATE TABLE user(
id INT(10) PRIMARY KEY,
USERNAME VARCHAR(30) ,
AGE INT(10)
);
在main()
方法中,使用forName()
加载User实体类,并使用getAnnotation(DBTable.class)
检查该实体类是否带有@DBTable注解。如果有,就将表名保存下来。然后读取这个类的所有域,并用getDeclaredAnnotations()
进行检查,该方法返回一个域上的所有注解。最后用instanceof
操作符来判断这些注解的类型。
在注解中嵌套使用的@Constraints
注解被传递到getConstraints()
方法中,由它负责构造一个包含SQL的String对象。
总结
通过一个简单的案例来说明注解的使用再合适不过了,对于注解的理解在我们平常的使用过程中也能更加得心应手。
更多文章请关注我的个人博客:www.zhyocean.cn