摘要:例子首先來看一個例子這里用了目的是告訴編譯器這個方法重寫了父類的方法如果編譯器發(fā)現(xiàn)父類中沒有這個方法就會報錯這個注解的作用大抵是防止手滑寫錯方法同時增強了程序的可讀性這里需要指出一點去掉并不會影響程序的執(zhí)行只是起到標記的作用找到的實現(xiàn)關(guān)注點
1. 例子
首先來看一個例子:
@Override public String toString() { return "xxxxx"; }
這里用了 @Override, 目的是告訴編譯器這個方法重寫了父類的方法, 如果編譯器發(fā)現(xiàn)父類中沒有這個方法就會報錯. 這個注解的作用大抵是防止手滑寫錯方法, 同時增強了程序的可讀性. 這里需要指出一點, @Override 去掉并不會影響程序的執(zhí)行, 只是起到標記的作用
找到 @Override 的實現(xiàn)
package java.lang; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
關(guān)注點有三個: @Target, @Retention, @interface. 從形狀可以看出, 前兩個也是注解. 它們用于描述注解, 稱為 元注解 . @interface 用于定義一個注解, 類似于 public class/interface XXX 中的 class/interface
參照 @Override, 我們可以試著實現(xiàn)自己的注解.
2. 自定義注解@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { }
這個注解與 @Override 類似, 但是把 ElememtType.METHOD 改成了 ElementType.FIELD, 意思是在成員變量上使用. 把 RetentionPolicy.SOURCE 改成了 RetentionPolicy.RUNTIME, 表示注解一直持續(xù)到代碼運行時.
這樣就定義好了一個注解, 可以這樣使用
public class Game { @Player private String A; }
當然這個注解太簡單了, 我們可以加點料, 比如這樣
@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; }
自定義注解大致如上. 給代碼打上注解相當于做了標記, 只有搭配上相應的注解處理流程, 才能算是真正發(fā)揮作用. 接下來介紹如何處理注解
3. 注解處理器這里使用反射獲取注解信息. 只有標注為 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() + " "); } } } 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
這樣粗略地介紹完了創(chuàng)建注解到處理注解的流程. 下面講一下注解中的幾個概念.
4. 概念 1. 元注解 1. 作用描述注解的注解, 在創(chuàng)建新注解的時候使用
2. 分類注解的作用范圍
分類
FIELD : 成員變量, 包括枚舉常量
LOCAL_VARIABLE : 局部變量
METHOD : 方法
PARAMETER : 參數(shù)
TYPE : 類、接口(包括注解類型) 或 enum 聲明
ANNOTATION_TYPE : 注解類型
等等
實現(xiàn)
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
ElementType[] 是枚舉類型的數(shù)組, 定義了上面的分類( FIELD, METHOD 等 ). @Target(ElementType.ANNOTATION_TYPE) 表示 @Target 只能用于修飾注解
注解的生命周期, 即注解到什么時候有效
分類
SOURCE
注解只保留在源文件, 當 Java 文件編譯成 class 文件的時候, 注解被遺棄
CLASS
注解被保留到 class 文件, jvm 加載 class 文件時候被遺棄. 這是默認的生命周期
RUNTIME
注解不僅被保存到 class 文件中, jvm 加載 class 文件之后, 仍然存在, 保存到 class 對象中, 可以通過反射來獲取
實現(xiàn)
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
RetentionPolicy 是枚舉類型( SOURCE, CLASS, RUNTIME )
上述代碼表示 @Retention 是運行時注解, 且只能用于修飾注解
表示注解是否能被 javadoc 處理并保留在文檔中
實現(xiàn)
@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
表示注解能否被繼承, 不常見
實現(xiàn)
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
例子
public class TestInherited { @Inherited @Retention(RetentionPolicy.RUNTIME) // 設(shè)成 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 自帶的注解
1. @Override作用是告訴編譯器這個方法重寫了父類中的方法
2. @Deprecated表明當前的元素已經(jīng)不推薦使用
3. @SuppressWarnings用于讓編譯器忽略警告信息
5. 什么是注解現(xiàn)在對注解的了解應該更深一些了. 下面概括一下什么是注解.
注解的定義如下
注解是一種應用于類、方法、參數(shù)、變量、構(gòu)造器及包聲明中的特殊修飾符。是一種由 JSR-175 標準選擇用來描述元數(shù)據(jù)的一種工具
簡單來說, 注解就是代碼的 元數(shù)據(jù) metadata , 包含了代碼自身的信息, 即 描述代碼的代碼 . 我們可以使用注解給代碼打上"標記", 通過解析 class 文件中相應的標記, 就可以做對應的處理.
需要注意的是, 注解 沒有行為, 只有數(shù)據(jù) , 是一種被動的信息, 不會對代碼的執(zhí)行造成影響, 需要配套的工具進行處理.
我們再來看一下 @Override 的聲明
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
這是一個源碼級別的注解, 不會保留到 class 文件中.
這里有一個問題, @Override 這里并沒有實現(xiàn), 那是怎們實現(xiàn)對方法名稱的檢查的 ?
顯然, 這里能看到注解的只有編譯器, 所以編譯器是這段注解的 "消費者", 也就是編譯器實現(xiàn)了這部分業(yè)務邏輯.
標記, 用于告訴編譯器一些信息
編譯時動態(tài)處理, 如動態(tài)生成代碼
運行時動態(tài)處理, 如得到注解信息
后面兩個主要是用于一些框架中
說到注解的話不得不提到 xml, 許多框架是結(jié)合使用這兩者的.
xml 的優(yōu)點是容易編輯, 配置集中, 方面修改, 缺點是繁瑣==, 配置文件過多的時候難以管理.如果只是簡單地配置參數(shù), xml 比較適合
注解的優(yōu)點是配置信息和代碼放在一起, 增強了程序的內(nèi)聚性, 缺點是分布在各個類中, 不宜維護.
如果要把某個方法聲明為服務, 注解是更優(yōu)的選擇
現(xiàn)在我們知道注解是 元數(shù)據(jù), 也知道注解需要配合處理器使用, 那注解本質(zhì)上是什么呢.
我們回到自定義注解
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { String name() default "PT"; int ATK() default 1; int DEF() default 0; }
編譯后對字節(jié)碼做一些處理: 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
意思已經(jīng)很明顯了, 注解是繼承了 Annotation 類的 接口.
那么通過反射獲得的實例是哪來的呢 ? 通過看源碼可以發(fā)現(xiàn), 在用 XXX.class.getAnnotation(XXX.class) 創(chuàng)建一個注解實例時, 用到了 AnnotationParser.parseAnnotations 方法.
在 openjdk 8 的 sun.reflect.annotation.AnnotationParser.java 文件中, 有方法
public static Annotation annotationForMap(final Class extends Annotation> type, final MapmemberValues) { return AccessController.doPrivileged(new PrivilegedAction () { public Annotation run() { return (Annotation) Proxy.newProxyInstance( type.getClassLoader(), new Class>[] { type }, new AnnotationInvocationHandler(type, memberValues)); }}); }
這里的 AnnotationInvocationHandler 實現(xiàn)了 InvocationHandler 接口, 所以運行期間獲得的實例其實是通過 動態(tài)代理 生成的.
8. 實際項目舉例這里實現(xiàn)一個類似于 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{ 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{ }
測試類
/** * 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);
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/67114.html
摘要:注解是的一個新特性。很重要,生產(chǎn)中我們開發(fā)常用此值表示注解是否可被子元素繼承。類注解方法注解通過反射獲取方法對象此部分內(nèi)容可參考通過反射獲取注解信息注解處理器實戰(zhàn)接下來我通過在公司中的一個實戰(zhàn)改編來演示一下注解處理器的真實使用場景。 前言:Java 注解,對于很多人都不陌生了,但是在公司的實際開發(fā)中,可能讓我們自己去定義注解并應用到生產(chǎn)環(huán)境中的機會比較少,所以會導致一部分人對注解的理解...
摘要:第章元編程與注解反射反射是在運行時獲取類的函數(shù)方法屬性父類接口注解元數(shù)據(jù)泛型信息等類的內(nèi)部信息的機制。本章介紹中的注解與反射編程的相關(guān)內(nèi)容。元編程本質(zhì)上是一種對源代碼本身進行高層次抽象的編碼技術(shù)。反射是促進元編程的一種很有價值的語言特性。 第12章 元編程與注解、反射 反射(Reflection)是在運行時獲取類的函數(shù)(方法)、屬性、父類、接口、注解元數(shù)據(jù)、泛型信息等類的內(nèi)部信息的機...
摘要:如果在中沒有找到該錯誤請通過報告頁建立該編譯器。請在報告中附上您的程序和以下診斷信息。 1. 前言 上一篇 主要介紹了什么是 注解 (Annotation) 以及如何讀取 運行時注解 中的數(shù)據(jù), 同時用注解實現(xiàn)了簡單的 ORM 功能. 這次介紹另一部分: 如何讀取 編譯時注解 ( RetentionPolicy.SOURCE ) 2. 作用 編譯時注解可以用來動態(tài)生成代碼. 使用 S...
摘要:上一篇博客介紹了如何基于配置文件在運行時創(chuàng)建實例對象,這篇博客將介紹基于注解方式怎樣實現(xiàn)對象的創(chuàng)建。方便測試,該類型分別創(chuàng)建兩個單例和多例的類型。注意這種為對象注入屬性值的方式耦合度較高,可根據(jù)情況使用。 上一篇博客介紹了如何基于xml配置文件在運行時創(chuàng)建實例對象,這篇博客將介紹基于注解方式怎樣實現(xiàn)對象的創(chuàng)建。 廢話不多說,直接上代碼。 首先還是創(chuàng)建項目,由于這次不需要使用第三方的AP...
閱讀 984·2019-08-30 15:55
閱讀 1467·2019-08-30 13:55
閱讀 2061·2019-08-29 17:13
閱讀 2894·2019-08-29 15:42
閱讀 1394·2019-08-26 14:04
閱讀 1084·2019-08-26 13:31
閱讀 3336·2019-08-26 11:34
閱讀 902·2019-08-23 18:25