首先來看一個例子:java
@Override public String toString() { return "xxxxx"; }
這裏用了 @Override
, 目的是告訴編譯器這個方法重寫了父類的方法, 若是編譯器發現父類中沒有這個方法就會報錯. 這個註解的做用大抵是防止手滑寫錯方法, 同時加強了程序的可讀性. 這裏須要指出一點, @Override
去掉並不會影響程序的執行, 只是起到標記的做用sql
找到 @Override
的實現數組
package java.lang; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
關注點有三個: @Target
, @Retention
, @interface
. 從形狀能夠看出, 前兩個也是註解. 它們用於描述註解, 稱爲 元註解 . @interface
用於定義一個註解, 相似於 public class/interface XXX
中的 class/interface
app
參照 @Override
, 咱們能夠試着實現本身的註解.框架
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { }
這個註解與 @Override
相似, 可是把 ElememtType.METHOD
改爲了 ElementType.FIELD
, 意思是在成員變量上使用. 把 RetentionPolicy.SOURCE
改爲了 RetentionPolicy.RUNTIME
, 表示註解一直持續到代碼運行時.
這樣就定義好了一個註解, 能夠這樣使用jvm
public class Game { @Player private String A; }
固然這個註解太簡單了, 咱們能夠加點料, 好比這樣ide
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { String name() default "PT"; int ATK() default 1; int DEF() default 0; }
使用的時候就能夠定義一些值了工具
public class Game { @Player(name = "CC", ATK = 2, DEF = 1) private String A; @Player(DEF = 2) private String B; }
註解元素必須有特定的值, 不指定值的話就會使用默認的 default 值.
註解裏有一個相對特殊的方法 value()
, 使用註解時只給 value
賦值時, 能夠只寫值. 例子以下測試
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { String name() default "PT"; int ATK() default 1; int DEF() default 0; double value(); }
public class Game { @Player(1.0) private String A; @Player(value = 3.0, DEF = 0) private String B; }
自定義註解大體如上. 給代碼打上註解至關於作了標記, 只有搭配上相應的註解處理流程, 才能算是真正發揮做用. 接下來介紹如何處理註解ui
這裏使用反射獲取註解信息. 只有標註爲 RetentionPolicy.RUNTIME
的註解能夠這麼提取信息.
/** * 註解處理器 */ public class PlayerProcessor { public static void process() { // 獲取成員變量 Field[] fields = Game.class.getDeclaredFields(); for (Field field : fields) { // 判斷是不是 Player 註解 if (field.isAnnotationPresent(Player.class)) { Player annotation = field.getAnnotation(Player.class); System.out.println("name = " + annotation.name()); System.out.println("attack = " + annotation.ATK()); System.out.println("defence = " + annotation.DEF()); System.out.println("type = "+ annotation.annotationType() + "\n"); } } } public static void main(String[] args) { process(); } }
輸出以下
name = CC attack = 2 defence = 2 type = interface Player name = PT attack = 1 defence = 10 type = interface Player
這樣粗略地介紹完了建立註解處處理註解的流程. 下面講一下註解中的幾個概念.
描述註解的註解, 在建立新註解的時候使用
註解的做用範圍
分類
FIELD
: 成員變量, 包括枚舉常量
LOCAL_VARIABLE
: 局部變量
METHOD
: 方法
PARAMETER
: 參數
TYPE
: 類、接口(包括註解類型) 或 enum 聲明
ANNOTATION_TYPE
: 註解類型
等等
實現
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
ElementType[]
是枚舉類型的數組, 定義了上面的分類( FIELD, METHOD 等 ). @Target(ElementType.ANNOTATION_TYPE)
表示 @Target
只能用於修飾註解
註解的生命週期, 即註解到何時有效
分類
SOURCE
註解只保留在源文件, 當 Java 文件編譯成 class 文件的時候, 註解被遺棄
CLASS
註解被保留到 class 文件, jvm 加載 class 文件時候被遺棄. 這是默認的生命週期
RUNTIME
註解不只被保存到 class 文件中, jvm 加載 class 文件以後, 仍然存在, 保存到 class 對象中, 能夠經過反射來獲取
實現
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
RetentionPolicy
是枚舉類型( SOURCE, CLASS, RUNTIME )
上述代碼表示 @Retention
是運行時註解, 且只能用於修飾註解
表示註解是否能被 javadoc 處理並保留在文檔中
實現
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
代表它自身也會被文檔化, 是運行時註解, 且只能用於修飾註解類型
例子
註解類沒有加 @Document
public @interface Doc { }
被文檔化的類
public class DocExample { @Doc public void doc() {} }
生成的 javadoc
加上 @Document
後
@Document public @interface Doc { }
生成的 javadoc
表示註解可否被繼承, 不常見
實現
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
例子
public class TestInherited { @Inherited @Retention(RetentionPolicy.RUNTIME) // 設成 RUNTIME 才能輸出信息 @interface Yes {} @Retention(RetentionPolicy.RUNTIME) @interface No {} @Yes @No class Father {} class Son extends Father {} public static void main(String[] args) { System.out.println(Arrays.toString(Son.class.getAnnotations())); } }
輸出: [@TestInherited$Yes()]
說明註解被繼承了, 也就是用反射的時候子類能夠獲得父類的註解的信息
就是 jdk 自帶的註解
做用是告訴編譯器這個方法重寫了父類中的方法
代表當前的元素已經不推薦使用
用於讓編譯器忽略警告信息
如今對註解的瞭解應該更深一些了. 下面歸納一下什麼是註解.
註解的定義以下
註解是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。是一種由 JSR-175 標準選擇用來描述元數據的一種工具
簡單來講, 註解就是代碼的 元數據 metadata
, 包含了代碼自身的信息, 即 描述代碼的代碼 . 咱們可使用註解給代碼打上"標記", 經過解析 class
文件中相應的標記, 就能夠作對應的處理.
須要注意的是, 註解 沒有行爲, 只有數據 , 是一種被動的信息, 不會對代碼的執行形成影響, 須要配套的工具進行處理.
咱們再來看一下 @Override
的聲明
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
這是一個源碼級別的註解, 不會保留到 class
文件中.
這裏有一個問題, @Override
這裏並無實現, 那是怎們實現對方法名稱的檢查的 ?
顯然, 這裏能看到註解的只有編譯器, 因此編譯器是這段註解的 "消費者", 也就是編譯器實現了這部分業務邏輯.
標記, 用於告訴編譯器一些信息
編譯時動態處理, 如動態生成代碼
運行時動態處理, 如獲得註解信息
後面兩個主要是用於一些框架中
說到註解的話不得不提到 xml
, 許多框架是結合使用這二者的.xml
的優勢是容易編輯, 配置集中, 方面修改, 缺點是繁瑣==, 配置文件過多的時候難以管理.若是隻是簡單地配置參數, xml
比較適合
註解的優勢是配置信息和代碼放在一塊兒, 加強了程序的內聚性, 缺點是分佈在各個類中, 不宜維護.
若是要把某個方法聲明爲服務, 註解是更優的選擇
如今咱們知道註解是 元數據
, 也知道註解須要配合處理器使用, 那註解本質上是什麼呢.
咱們回到自定義註解
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { String name() default "PT"; int ATK() default 1; int DEF() default 0; }
編譯後對字節碼作一些處理: javap -verbose Player.class
能夠看到
Last modified 2017-5-26; size 498 bytes MD5 checksum 4ca03164249758f784827b6d103358de Compiled from "Player.java" public interface Player extends java.lang.annotation.Annotation minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool: #1 = Class #23 // Player #2 = Class #24 // java/lang/Object #3 = Class #25 // java/lang/annotation/Annotation #4 = Utf8 name #5 = Utf8 ()Ljava/lang/String; #6 = Utf8 AnnotationDefault #7 = Utf8 PT #8 = Utf8 ATK #9 = Utf8 ()I #10 = Integer 1 #11 = Utf8 DEF #12 = Integer 0 #13 = Utf8 SourceFile #14 = Utf8 Player.java #15 = Utf8 RuntimeVisibleAnnotations #16 = Utf8 Ljava/lang/annotation/Target; #17 = Utf8 value #18 = Utf8 Ljava/lang/annotation/ElementType; #19 = Utf8 FIELD #20 = Utf8 Ljava/lang/annotation/Retention; #21 = Utf8 Ljava/lang/annotation/RetentionPolicy; #22 = Utf8 RUNTIME #23 = Utf8 Player #24 = Utf8 java/lang/Object #25 = Utf8 java/lang/annotation/Annotation { public abstract java.lang.String name(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: s#7 public abstract int ATK(); descriptor: ()I flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: I#10 public abstract int DEF(); descriptor: ()I flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: I#12} SourceFile: "Player.java" RuntimeVisibleAnnotations: 0: #16(#17=[e#18.#19]) 1: #20(#17=e#21.#22)
這裏須要注意的是這個public interface Player extends java.lang.annotation.Annotation
意思已經很明顯了, 註解是繼承了 Annotation
類的 接口.
那麼經過反射得到的實例是哪來的呢 ? 經過看源碼能夠發現, 在用 XXX.class.getAnnotation(XXX.class)
建立一個註解實例時, 用到了 AnnotationParser.parseAnnotations
方法.
在 openjdk 8 的 sun.reflect.annotation.AnnotationParser.java
文件中, 有方法
public static Annotation annotationForMap(final Class<? extends Annotation> type, final Map<String, Object> memberValues) { return AccessController.doPrivileged(new PrivilegedAction<Annotation>() { public Annotation run() { return (Annotation) Proxy.newProxyInstance( type.getClassLoader(), new Class<?>[] { type }, new AnnotationInvocationHandler(type, memberValues)); }}); }
這裏的 AnnotationInvocationHandler
實現了 InvocationHandler
接口, 因此運行期間得到的實例實際上是經過 動態代理 生成的.
這裏實現一個相似於 ORM 的功能, 加深一下對運行時註解的理解
註解類
表
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String name(); }
列
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String name(); }
實體類
/** * Created by away on 2017/5/27. */ @Table(name = "city") public class City { @Column(name = "city_id") private int id; @Column(name = "city_name") private String name; // getset }
SQL 方法類
/** * Created by away on 2017/5/27. */ public class ORMSupport<T> { public void save(T entity) { StringBuffer sql = new StringBuffer(30); sql.append("insert into "); processTable(entity, sql); processField(entity, sql); System.out.println(sql); } private void processTable(T entity, StringBuffer sql) { Table table = entity.getClass().getDeclaredAnnotation(Table.class); if (table != null) { sql.append(table.name()).append(" ("); } } private void processField(T entity, StringBuffer sql) { Field[] fields = entity.getClass().getDeclaredFields(); StringBuilder val = new StringBuilder(); val.append("("); String comma = ""; for (Field field : fields) { if (field.isAnnotationPresent(Column.class)) { String name = field.getAnnotation(Column.class).name(); sql.append(comma).append(name); val.append(comma).append(getColumnVal(entity, field.getName())); } comma = ", "; } sql.append(") values ") .append(val) .append(");"); } private Object getColumnVal(T entity, String field) { StringBuilder methodName = new StringBuilder(); String firstLetter = (field.charAt(0) + "").toUpperCase(); methodName.append("get") .append(firstLetter) .append(field.substring(1)); try { Method method = entity.getClass().getMethod(methodName.toString()); return method.invoke(entity); } catch (Exception e) { e.printStackTrace(); return ""; } } }
DAO
/** * Created by away on 2017/5/27. */ public class CityRepository extends ORMSupport<City> { }
測試類
/** * Created by away on 2017/5/27. */ public class ORMTest { public static void main(String[] args) { City city = new City(); city.setId(1); city.setName("nanjing"); CityRepository cityRepository = new CityRepository(); cityRepository.save(city); } }
輸出
insert into city (city_id, city_name) values (1, nanjing);