原文鏈接 - Joker's
歡迎大家訪問我的個人博客:)
大約有一個月沒寫博啦,中間經(jīng)歷了急性支氣管炎、大雪封山、懶癌附身種種,這次想寫的是「注解」,一個之前一直很懼怕的東西。
起因是跟著【手把手】JavaWeb 入門級項目實戰(zhàn) -- 文章發(fā)布系統(tǒng) (第四節(jié))這篇博文在做入門的 jsp 項目,所以接觸到了。接觸到了,自然就要了解一下。部分內容出自原博客,侵刪。
我是后端新手,以前完全沒接觸過后端項目,所以做的時候理解上會有問題,同時我也想把自己遇到的這些問題,用淺顯易懂的方式說出來,可以供有相同疑惑的朋友參考。如有不足之處,歡迎指正。
這篇文章可能有點長,但寫的應該比較容易看懂,如果你也是新手,希望你能看到最后,不明白的部分可以留言給我討論。文章整體脈絡應該是比較清晰:首先介紹注解是什么,然后再看注解的定義,最后是如何使用,把這些講完之后我又舉了一個完整的實例來講解。
目錄
Step 1. 注解是什么
首先介紹一下,注解到底是個啥?為什么用途這么廣泛?
Step 2. 注解怎么寫
初步了解以后,讓我們直接上手寫一個!
Step 3. 注解如何用
寫完注解不會用相當于沒有寫,那么我們如何使用呢?
3.1 給誰用?
3.2 怎么用?
3.3 幾個注意點
Step 4. 看一個完整的例子:獲取數(shù)據(jù)庫表名
從零開始,展示一個完整的注解用法。
4.1 第 0-1 步:給誰用?(JavaBean)
4.2 第 0-2 步:注解怎么寫(定義注解)
4.3 第 1 步:獲取類的對象
4.4 第 2 步:獲得相應的方法/域
4.5 第 3 步:獲取注解的實例
4.6 第 4 步:獲取對應的屬性
參考
正文
Step 1. 注解是什么
原博主寫了一篇文章非常好,推薦仔細看一遍:用大白話聊聊JavaSE -- 自定義注解入門。我看了三遍,才感覺理解個差不多。
我簡單總結一下原博主的思想:注解就像是寫給電腦看的注釋,有了注解,我們可以通過代碼來獲得關于一個方法或者一個類的信息。在平時的開發(fā)過程中,我們會給一個類、方法或者域添加注釋,比如,我們會在方法前面添加關于入?yún)?、返回值、以及方法作用的注釋。同樣的,我們可以給一個類、方法或者域添加注解。
同時,你仔細觀察的話,會發(fā)現(xiàn)注解非常像一個空的接口。
了解了這些內容,我來介紹一下元注解的概念。典型的元注解有 @Target 和 @Retention ,這是我們在定義一個注解時需要用到的,
@Target 用來定義你的注解用在什么地方:
-
@Target(ElementType.FIELD)表示用于一個域 -
@Target(ElementType.METHOD)表示用于一個方法 -
@Target(ElementType.TYPE)則表示用于一個類。
@Retention 用來定義注解在哪一個級別可用:
-
@Retention(RetentionPolicy.SOURCE)表示在源代碼中可用 -
@Retention(RetentionPolicy.CLASS)表示在類文件中可用 -
@Retention(RetentionPolicy.RUNTIME)表示在運行時可用,一般用的比較多。
Step 2. 注解怎么寫
下面讓我們來看一下注解的格式,我們參照原作者的方式,來給方法寫一個注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation {
// 方法作用,有默認值
String description() default "作者很懶,沒有寫本方法的作用";
// 方法創(chuàng)建時間
String createTime();
}
很顯然,我們可以通過這個注解來獲取方法的作用和創(chuàng)建時間,當然,我們也可以加上作者,無非是再加這么一行:String author() default "Joker";。此外,在定義注解的過程中,默認值是可選的。
Step 3. 注解如何用
好,通過上面兩部分,我們已經(jīng)了解了注解是什么,以及如何寫一個簡單的注解,接下來看一看注解是怎么用的。這要分為兩部分來說。
3.1 給誰用?
既然明白了我們剛剛編寫的注解時用來寫給方法的,那么我們想獲得哪個方法的作用和創(chuàng)建時間呢?
讓我們來隨便寫一個:
package util;
import java.util.Date;
import java.text.SimpleDateFormat;
public class DateUtil {
@MethodAnnotation(createTime = "2018-01-01")
public static String formatDate(Date date , String formatPattern){
return new SimpleDateFormat(formatPattern).format(date);
}
}
現(xiàn)在目標明確了,我們是想獲得 formatDate 這個方法的作用和創(chuàng)建時間,那就先給方法加上我們剛剛創(chuàng)建的注解,也就是@MethodAnnotation()。括號里面要填入注解中對應的參數(shù)和值,有默認值的可以不用寫。如果參數(shù)沒有默認值,且在這里沒有填寫的話(比如在這里只寫一個空括號),ide 會報 The annotion @MethodAnnotation must define the attribute createTime錯誤。
3.2 怎么用?
計算機如何在 Java 類中獲取我們上面定義的注解信息呢?
那我們不得不聊聊另外一個名詞:反射。我們先來看一個例子(這里我還是使用原作者的例子,侵刪),在 DateUtil.java 中創(chuàng)建一個單元測試(main 方法)如下:
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Class classOfDateUtil = DateUtil.class;
Method formatDate = classOfDateUtil.getMethod("formatDate", Date.class,String.class);
MethodAnnotation methodNote = formatDate.getAnnotation(MethodAnnotation.class);
System.out.println("方法描述:" + methodNote.description());
System.out.println("創(chuàng)建日期:" + methodNote.createTime());
}
首先讀一下上面的代碼,自己想一想,然后再來看我的總結。
---------------------------------------------------分割線---------------------------------------------------
關于上面的反射過程,我個人總結為四部曲:
- 獲取類的對象。如果在編譯器能夠知道名字的話,可以使用上面的方法;如果在編譯器不知道類的名字,但是可以在運行期獲得到類名字符串的話,可以使用以下方法:
String className = ... ;//在運行期獲取的類名字符串
Class class = Class.forName(className);
- 根據(jù)類對象獲得相應的方法/域,如果注解是加在類上的,可以省略這一步。主要方法有(具體用法可以自己去查詢 API 文檔):
- Field getField()
- Field[] getFields() 返回 Field 數(shù)組,包括這個類及其父類的公有域。
- Field[] getDeclaredFields() 返回 Field 數(shù)組,包括這個類的所有域。
- Method getMethod()
- Method[] getMethods() 返回所有的公有方法,包括從父類繼承來的公有方法。
- Method[] getDeclaredMethods() 返回所有的方法,但不包括由父類繼承來的方法。
- 根據(jù)獲得的域/方法/類對象,結合 getAnnotations() 方法來獲取注解的實例。
- 根據(jù)注解實例,獲取對應的信息。結束!
3.3 幾個注意點
- 不要漏寫 default 值。
- 在第二步時,注意看下用到的方法是否拋出異常。如果有的話,在3中不要忘記拋出。
Step 4. 看一個完整的例子:獲取數(shù)據(jù)庫表名
這里的情景是,用戶在登陸頁面上輸入了用戶名和密碼,點登陸的時候,發(fā)送請求到我們的 controller 層,此時我們把用戶數(shù)據(jù)封裝為一個 JavaBean,再把 JavaBean 轉化為建表語句。為了簡化過程,最后一步我省略掉,我們只看怎么獲取表名即可。
關于 JavaBean 大家可以看看原博主的這篇文章:用大白話聊聊JavaSE -- 如何理解Java Bean(一),寫的非常好。
我簡單解釋一下 JavaBean 和數(shù)據(jù)庫的對應關系:寫 JavaBean 的過程其實就相當于是數(shù)據(jù)庫的設計過程,類對應著表,屬性對應著字段。
4.1 第 0-1 步:給誰用?(JavaBean)
既然是用戶,我們新建一個 User 類,我只抽了一部分。
public class User {
private String username;
private String password;
...
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
4.2 第 0-2 步:注解怎么寫(定義注解)
我們新建一個 Annotation 文件,就叫 Table。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
public String tableName();
}
這里把導包過程省略了,請大家自行補充。
這步做完之后,我們給 0-1 中的 User 類加上注解,參數(shù)設置為 t_user,即表名:
@Table(tableName = "t_user")
public class User {
}
4.3 第 1 步:獲取類的對象
這里我們封裝一個 TableUtils 作為數(shù)據(jù)庫表的工具類,來使用注解,真正的調用過程我們在單元測試中進行。在剛剛講解的過程中,其實是把這兩部分合二為一了。在實際開發(fā)過程中,可能要將不同的模塊分包處理,比如 annotions 包、util 包、bean 包等等。
// 作為工具類API使用,直接傳入類的對象
public class TableUtils {
public static String getCreateTablename(Class<?> clazz) {
}
}
// 在這里傳入類
public class TestMain {
public static void main(String[] args) {
TableUtils.getCreateTablename(User.class);
}
}
4.4 第 2 步:獲得相應的方法/域
因為我們的注解是加在類上的,所以這一步可以省略。
4.5 第 3 步:獲取注解的實例
既然是獲取注解的實例,肯定是在直接使用注解的地方,也就是 TableUtils 中進行。
public static String getCreateTablename(Class<?> clazz) {
Table table = clazz.getAnnotation(Table.class);
}
4.6 第 4 步:獲取對應的屬性
這里我們要獲取表名,可以控制臺打印下,看看是不是我們的 t_user。
public static String getCreateTablename(Class<?> clazz) {
Table table = clazz.getAnnotation(Table.class);
String tableName = table.tableName();
System.out.println(tableName);
}
至此,一個完整的注解 demo 完成,感謝大家看到最后~
參考
- java 類 - 極客學院
- 【手把手】JavaWeb 入門級項目實戰(zhàn) -- 文章發(fā)布系統(tǒng) (第四節(jié))
- 用大白話聊聊JavaSE -- 自定義注解入門
- 創(chuàng)建 MySQL 表 - 極客學院
- 《Java 核心技術 5.7 反射》
- 《Java 編程思想 第20章 注解》