Java中的註解原來是這樣回事的
前言
在我們平常的程式碼開發過程中,遇見過無數的註解,大多數註解都是我們使用的框架所給我們整合好了的,相信也很少有人使用自己編寫的註解,我也如此,但是隻有當你瞭解了註解背後的祕密後,一定會對它有不同的看法。
註解,也被稱為元資料,可以為我們在程式碼中新增資訊提供一種形式化的方法,使我們可以在稍後某個時刻非常方便地使用這些資料
註解的優點
使用註解有許多的優點:
- 註解能使編譯器來測試和驗證格式,儲存有關程式的額外資訊。
- 註解可以用來生成描述符檔案,有助於減輕編寫“樣板”程式碼的負擔、
- 使用註解可以將這些元資料儲存在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