用自定义注解做点什么——自定义注解有什么用
用自定義注解做點什么
前言
你不一定聽過注解,但你一定對@Override不陌生。
當我們重寫父類方法的時候我們就看到了@Override。我們知道它表示父類方法被子類重寫了。
現在告訴你,@Override就是一個注解。
也許你會疑惑注解是什么?
注解(annotation)是JDK5之后引進的新特性,是一種特殊的注釋,之所以說它特殊是因為不同于普通注釋(comment)能存在于源碼,而且還能存在編譯期跟運行期,會最終編譯成一個.class文件,所以注解能有比普通注釋更多的功能。
接下來,先入個門,然后通過實戰來證明注解有多“猛”。
PS : 如果已經了解的小伙伴可自行跳到 自定義注解實戰。
自定義注解入門
我們對于注解的認識大多數來源于標準注解(也稱為內建注解)。
| @Override | 用于標識該方法繼承自超類 當父類的方法被刪除或修改了,編譯器會提示錯誤信息 |
| @Deprecated | 表示該類或者該方法已經不推薦使用 如果用戶還是要使用,會生成編譯的警告 |
| @SuppressWarnings | 用于忽略的編譯器警告信息 |
Java不僅僅提供我們原有的注解使用,它還允許我們自定義注解。比如你可以像這樣:
public @interface DoSomething {public String name() default "write"; }這是最簡單的注解聲明。
盡管看上去像是接口的寫法,但完全不是一回事。這一點要注意。
而使用注解也很簡單,可以像這樣:
需要注意的是當注解有value()方法時,不需要指明具體名稱。
public @interface DoSomething {public String value();public String name() default "write"; }@DoSomething("walidake") public class UseAnnotation {}然而“最簡單的自定義注解”并沒有特別的意義。所以,這時候我們需要引入一個元注解的概念。
我們需要知道這些概念:
“普通注解”只能用來注解“代碼”,而**“元注解”只能用來注解 “普通注解”**。
自定義注解是“普通注解”。
JDK5時支持的元注解有@Documented @Retention @Target @Inherited,接下來分別介紹它們修飾注解的效果。
@Documented @interface DocumentedAnnotation{}@interface UnDocumentedAnnotation{}@DocumentedAnnotation @UnDocumentedAnnotation public class UseDocumentedAnnotation{}打開小黑窗,運行javadoc UseDocumentedAnnotation.java
運行結果:
結論:可以看到,被@Documented修飾的注解會生成到javadoc中,如@DocumentedAnnotation。
而不被@Documented修飾的注解(@UnDocumentedAnnotation)不會生成到javadoc中。
注解的級別
@Retention可以設置注解的級別,分為三種,都有其特定的功能。
這個元注解是我們關注的重點,后面實戰我們會用到。
| SOURCE 源碼級別 | 注解只存在源碼中 | 功能是與編譯器交互,用于代碼檢測。 如@Override,@SuppressWarings。 額外效率損耗發生在編譯時 |
| CLASS 字節碼級別 | 注解存在源碼與字節碼文件中 | 主要用于編譯時生成額外的文件,如XML,Java文件等,但運行時無法獲得。 這個級別需要添加JVM加載時候的代理(javaagent),使用代理來動態修改字節碼文件 |
| RUNTIME 運行時級別 | 注解存在源碼,字節碼與Java虛擬機中 | 主要用于運行時反射獲取相關信息 |
限制注解使用的范圍
注解默認可以修飾各種元素,而使用@Target可以限制注解的使用范圍。
例如,可以限定注解只能修飾方法。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override {}上面的代碼將注解的使用范圍限制在了方法上,而不能用來修飾類。
試著用@Override修飾類會得到“The annotation @Override is disallowed for this location”的錯誤。
@Target支持的范圍(參見ElementType):
1) 類,接口,注解; 2) 屬性域; 3) 方法; 4) 參數; 5) 構造函數; 6) 局部變量; 7) 注解類型; 8) 包注解的繼承
@Inherited可以讓注解類似被“繼承”一樣。
通過使用@Inherited,可以讓子類對象使用getAnnotations()獲取父類@Inherited修飾的注解。
我們干脆用@Documented查看類結構。發現:
這是不是恰恰證明了這種是偽繼承的說法,而不是真正的繼承。
自定義注解實戰
引言
Java Web開發中,對框架的理解和掌握是必須的。而在使用大多數框架的過程中,一般有兩種方式的配置,一種是基于xml的配置方式,一種是基于注解的方式。然而,越來越多的程序員(我)在開發過程中享受到注解帶來的簡便,并義無反顧地投身其中。
ORM框架,像Hibernate,Mybatis就提供了基于注解的配置方式。我們接下來就使用自定義注解實現袖珍版的Mybatis,袖珍版的Hibernate。
這很重要
說明:實戰的代碼會被文章末尾附上。而實際上在之前做袖珍版框架的時候并沒有想到會拿來做自定義注解的Demo。因此給出的代碼涉及了其他的一些技術,例如數據庫連接池,動態代理等等,比較雜。
在這個篇幅我們只討論關于自定義注解的問題,至于其他的技術后面會開多幾篇博文闡述。(當然這么多前輩面前不敢造次,有個討論學習的氛圍是很好的~)
那么在自定義注解框架前,我們需要花點時間瀏覽以下幾個和Annotation相關的方法。
| Annotation getAnnotation(Class annotationType) | 獲取注解在其上的annotationType |
| Annotation[] getAnnotations() | 獲取所有注解 |
| isAnnotationPresent(Class annotationType) | 判斷當前元素是否被annotationType注解 |
| Annotation[] getDeclareAnnotations() | 與getAnnotations() 類似,但是不包括父類中被Inherited修飾的注解 |
Mybatis 自定義注解
本節目標:自定義注解實現Mybatis插入數據操作。
本節要求:細心觀察使用自定義注解的步驟。
Step 1 : 聲明自定義注解。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Insert {public String value(); }Step 2 : 在規定的注解使用范圍內使用我們的注解
public interface UserMapper {@Insert("insert into user (name,password) values (?,?)")public void addUser(String name,String password);}Step 3 : 通過method.getAnnotation(Insert.class).value()使用反射解析自定義注解,得到其中的sql語句
//檢查是否被@Insert注解修飾 if (method.isAnnotationPresent(Insert.class)) {//檢查sql語句是否合法//method.getAnnotation(Insert.class).value()取得@Insert注解value中的Sql語句sql = checkSql(method.getAnnotation(Insert.class).value(),Insert.class.getSimpleName());//具體的插入數據庫操作insert(sql, parameters); }Step 4 : 根據實際場景調用Step 3的方法
UserMapper mapper = MethodProxyFactory.getBean(UserMapper.class); mapper.addUser("walidake","665908");運行結果:
以上節選自annotation中Mybatis部分。具體CRUD操作請看源碼。
總結一下從上面學到的東西:
1.聲明自定義注解,并限制適用范圍(因為默認是通用)
2.規定范圍內使用注解
3.isAnnotationPresent(Insert.class)檢查注解,getAnnotation(Insert.class).value()取得注解內容
4.根據實際場景應用
Hibernate 自定義注解
本節目標:自定義注解使實體自動建表(即生成建表SQL語句)
本節要求:動手操作,把未給全的代碼補齊。
本節規劃:仿照Hibernate,我們大概會需要@Table,@Column,還有id,我們這里暫且聲明為@PrimaryKey
仿照自定義Mybatis注解的步驟:
/*** 可根據需要自行定制功能*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Table {String name() default "";}@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Column {// 列名 默認為""String name() default "";// 長度 默認為255int length() default 255;// 是否為varchar 默認為trueboolean varchar() default true;// 是否為空 默認可為空boolean isNull() default true; }/*** 有需要可以拆分成更小粒度* @author walidake**/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface PrimaryKey {String name() default ""; }完成Step 1,接下來是Step 2。
@Table public class Person {@PrimaryKeyprivate int id;@Column(isNull = false, length = 20)private String username;... }Step 3,新建一個叫做SqlUtil的類,使用Class(實體類).isAnnotationPresent(Table.class)取到@Table注解的內容。
而我們如何取到@Column和@PrimaryKey的內容?
使用反射,我們可以很容易做到。
反射的內容后面再寫。(感覺每一篇都給自己挖了很多坑后面去填)
Step 4套入使用場景
String createSql = SqlUtil.createTable(clazz); ... connection.createStatement().execute(createSql);運行結果:
運行結果正確!
自此我們完成了實戰模塊的內容。當然關于Hibernate的CRUD也可以用同樣的方法做到,更進一步還可以把二級緩存整合進來,實現自己的一個微型框架。盡管現有的框架已經很成熟了,但自己實現一遍還是能收獲很多東西。
可以看出來,注解簡化了我們的配置。每次使用注解只需要@注解名就可以了,就跟吃春藥一樣“爽”。不過由于使用了反射,后勁太“猛”,jvm無法對代碼優化,影響了性能。這一點最后也會提及。
另外提一點,之前想格式化hibernate生成的SQL,做大量搜索后被告知“Hibernate 使用的是開源的語法解析工具 Antlr,需要進行 SQL 語法解析,將 SQL 語句整理成語法樹”。也算一個坑吧~
不過后來找到一個除了建表SQL以外的格式化工具類,覺得還不錯就也分享了。可以在源碼中找到。
最后說點什么
可以發現我們使用運行時注解來搭建我們的袖珍版ORM框架,因為運行時注解來搭建框架相對容易而且適用性也比較廣,搭建的框架使用起來也比較簡單。但在此基礎上因為需要用到反射,其效率性能相對不高。因此,多數Web應用使用運行時注解,而像Android等對效率性能要求較高的平臺一般使用源碼級別注解來搭建。下一節我們討論怎么玩一玩源碼級注解。
總結
以上是生活随笔為你收集整理的用自定义注解做点什么——自定义注解有什么用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FPGA笔记(1)-逻辑代数与逻辑电路基
- 下一篇: mxnet基础到提高(46)-ndarr