JavaSE:注解與反射(Annotation & Reflection)
注解和架構是所有架構的底層,如Mybatis,spring。架構的底層實作機制就是注解和反射。注解相比于注釋,除了能較為直接的表示出這部分子產品的功能,也能實作一定的具體功能。
01 初識注解
1.1 什麼是注解
- Annotation是從JDK5.0引入的新技術
- Annotation的作用:
- 不是程式本身,但可以對程式做出解釋。(這一點和注釋comment沒什麼差別)
- 可以被其他程式(比如:編譯器等)讀取。
- Annotation的格式:
- 注解是以“@注釋名”在代碼中存在的,還可以添加一定參數值,如
@Override
@SuppressWarnings(Value="unchecked")
- Annotation在哪裡使用?可以附加在package,class,method,field等上面,相當于給他們添加了額外的輔助資訊,我們可以通過反射機制程式設計實作對這些中繼資料的通路。
1.2 内置注解
- @Override 定義在java.lang.Overide中,此注釋隻适用于修辭方法,表明一個方法聲明打算重寫超類中的另一個方法申明。
- @Deprecated 定義在java.lang.Deprecated中,此注釋可适用于修辭方法,屬性,類,表示不鼓勵程式員使用這樣的元素,通常因為該方法比較危險或者存在更好的方法
- @SupperWarnings
- 定義在java.lang.SupperWarnings中,用來抑制編譯時的告警資訊。
- 但與前兩個注解不同的是,這個注解需要參數,具體如何設定參看JDK說明文檔
- @SupperWarnings("all")
- @SupperWarnings("unchecked")
- @SupperWarning("unchecked","deprecation")
- 等等....
1.3 元注解
- 元注解的作用就是負責注解其他注解,Java定義了4個标準的meta-annotation類型。它們被用來提供對其他annotation類型做說明。
- 這些類型和它們所支援的類在java.kang.annotation包中可以找到(@Target,@Retention,@Documented,@inherited)
- @Target:用于描述注解的使用範圍(即:被描述的注解可以用在什麼地方)
- @Retention:表示需要在什麼級别儲存該注釋資訊,用于描述注解的生命周期
- (SOURCE(源代碼)<CLASS(Javac編譯檔案)<RUNTIME(軟體運作))
- @Documented:說明該注解将被包含在javadoc中
- @Inherited:說明子類可以繼承父類中的注解
1.4 自定義注解
- 使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation接口
- 分析:
- @interface用來聲明一個注解,格式:public @interface 注解名
- 需要注意,如果該檔案中已有public,則注解定義需去掉public,一個檔案中隻能有一個public方法
- 其中的每一個方法實際上是聲明了一個配置參數
- 方法的名稱就是參數的名稱
- 傳回值類型就是參數的類型(傳回值隻能是基本類型,Class,String,enum)
- 可以通過添加default來聲明參數的預設值
- 如果隻有一個參數成員,一般參數名為value
- 如果參數名為value,則可以在指派時省略"value = ",直接寫指派内容即可
- 注解元素必須要有值,我們定義注解元素時,經常使用空字元串,0作為預設值
02 反射
2.1 反射概述
2.1.1 靜态 vs 動态語言
- 動态語言
- 是一類在運作時可以改變其結構的語言,例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被删除或是其他結構上的變化。通俗點說就是在運作時代碼可根據某些條件改變自身結構。
- 主要動态語言:Object-C、C#、JavaScript、PHP、Python等
- 靜态語言
- 與動态語言相對應,運作時結構不可變的語言就是靜态語言。如Java、C、C++。
- Java不是動态語言,但Java可以稱之為“準動态語言”。即Java有一定的動态性,我們可以利用反射機制獲得類似動态語言的特性。Java的動态性讓程式設計的時候更加靈活!
2.1.2 Java Reflection
- Reflection(反射)是Java被視為“準動态語言的”關鍵,反射機制允許程式在執行期借助Reflection API取得任何類的内部資訊,并能直接操作任意對象的内部屬性及方法。
- Class c = Class.forName("java.lang.String")
- 加載完類之後,在堆記憶體的方法區中就産生了一個Class類型的對象(一個類隻有一個Class對象),這個對象就包含了完整的類的結構資訊。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構。是以,人們形象稱之為反射
2.1.3 Java反射機制研究及應用
- Java反射機制提供的功能:
- 在運作時判斷任意一個對象所屬的類
- 在運作時構造任意一個類的對象
- 在運作時判斷任意一個類所具有的的成員變量和方法
- 在運作時擷取泛型資訊
- 在運作時調用任意一個對象的成員變量和方法
- 在運作時處理注解
- 生成動态代理
- Java反射的優點和缺點
- 優點:
- 可以實作動态建立對象和編譯,展現出很大的靈活性
- 缺點:
- 對性能會有影響。使用反射基本上是一個解釋操作,我們可以告訴JVM,我們希望做什麼并且讓它滿足我們的需求。這類操作總是慢于直接執行相同的操作。
- 反射相關的主要API
- java.lang.Class:代表一個類
- java.lang.reflect.Method:代表類的方法
- java.lang.reflect.Field:代表類的成員變量
- java.lang.reflect.Constructor:代表類的構造器
- Class類
- 在Object類中定義了如下的方法,此方法将被所有子類繼承
- public final Class getClass()
- 以上的傳回值類型是一個Class類,此類是Java反射的源頭,實際上所謂反射從程式的運作結果來看也很好了解,即:可以通過對象反射求出類的名稱。
- 對象照鏡子後可以得到的資訊包括:某個類的屬性、方法和構造器、某個類到底實作了哪些接口。對于每個類而言,JRE都為其保留一個不變的Class類型的對象。一個Class對象包含了特定的某個結構(class/interface/enum/annotation/primitive type/void/[])的有關資訊
- Class本身也是一個類
- Class對象隻能由系統建立對象
- 哪些類型可以有Class對象?
- class:外部類,成員(成員内部類,靜态内部類),局部内部類、匿名内部類
- interface:接口
- []:數組
- enum:枚舉
- annotation:注解@interface
- primitive type:基本資料類型
- void
2.2 Java記憶體分析
2.2.1 Java記憶體配置設定
2.2.2 類的加載過程
當程式主動使用某個類時,如果該類還未被加載到記憶體中,則系統會通過如下三個步驟來對該類進行初始化。
2.2.3 類加載器
- 類加載器的作用:将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,然後在堆中生成一個代表這個類的java.lan.Class對象,作為方法區中類資料的通路入口。
- 類緩存:标準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它将維持加載(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象。
- JVM規範定義了如下類型的類的加載器:
- 引導類加載器:用C++編寫的,是JVM自帶的類加載器,負責Java平台核心庫,用來裝載核心類庫。需注意此庫無法直接擷取。
- 擴充類加載器:負責jre/lib/ext目錄下的jar包或-D java.ext.dirs指定目錄下的jar包裝入工作庫
- 系統類加載器:負責java -classpath或-D java.class.path所指的目錄下的類與jar包裝入工作,是最常用的加載器。
- 雙親委派機制:是防止同名包、類與 jdk 中的相沖突,實際上加載類的時候,先通知 appLoader,看 appLoader 是否已經緩存,沒有的話,appLoader 又委派給他的父類加載器(extLoader)詢問,看他是不是能已經緩存加載,沒有的話,extLoader 又委派他的父類加載器(bootstrapLoader)詢問,BootstrapLoader看是不是自己已緩存或者能加載的,有就加載,沒有再傳回 extLoader,extLoader 能加載就加載,不能的話再傳回給 appLoader 加載,再傳回的路中,誰能加載,加載的同時也加緩存裡。正是由于不停的找自己父級,是以才有 Parents 加載機制,翻譯過來叫 雙親委派機制
2.2.4 類的加載與ClassLoader的了解
- 加載:将class檔案位元組碼加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,然後生成一個代表這個類的java.lang.Class對象
- 連結:将Java類的二進制代碼合并到JVM的運作狀态之中的過程
- 驗證:確定加載的類的資訊符合JVM規範,沒有安全方面的問題
- 準備:正式為類變量(static)配置設定記憶體并設定類變量預設初始值的階段,這些記憶體都将在方法區中進行配置設定。
- 解析:虛拟機常量池内的符号引用(常量名)替換為直接引用(位址)的過程
- 初始化:
- 執行類構造器()方法。類構造器()方法是由編譯器自動收集類中所有類變量的指派動作和靜态代碼塊中的語句合并産生的。(類構造器是構造類資訊的,不是構造該類對象的構造器)
- 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
- 虛拟機會保證一個類的()方法在多線程環境中被正确加鎖和同步
2.2.5 擷取運作時類的完整結構
- 通過反射
- 包括如下資訊:Field、Method、Constructors、Superclass、Interface、Annotation
- 實作的全部接口
- 所繼承的父類
- 全部的構造器
- 全部的方法
- 全部的Field
- 注解···
小結
- 在實際的操作中,取得類的資訊的操作代碼,并不會經常開發
- 一定要熟悉java.lang.reflect包的作用和反射機制
- 如何取得屬性、方法、構造器的名稱,修飾等。
2.3 動态建立對象執行方法
- 建立類的對象:調用Class對象的newInstance()方法
- 1)類必須有一個無參數的構造器。(無參構造器必須有)
- 2)類的構造器的通路權限需要足夠
- 除了調用無參構造器建立對象外,也可以
- 1)通過Class類的getDeclaredConstructor(Class...parameterTypes)取得本類的制定形參類型的構造器
- 2)向構造器的形參中傳遞一個對象數組進去,裡面包含了構造器中所需的各個參數
- 3)通過Constructor執行個體化對象
- 利用反射調用指定的方法
- 通過反射,調用類中的方法,通過Method類完成。
- 通過Class類的getMethod(String name,Class...parameterTypes)方法取得一個Method對象,并設定此方法操作時所需要的參數類型(防止出現方法重寫,利用參數類型和方法名确定具體的方法)
- 之後使用Object invoke(Object obj , Object[] args)進行調用,并向方法中傳遞要設定的obj對象的參數資訊。
- Object invoke(Object obj,Object...args)
- Object對應原方法的傳回值。若原方法無傳回值,此時傳回null
- 原方法若為靜态方法,此時形參Object obj可為null
- 若原方法形參清單為空,則Object[] args為null
- 若原方聲明為private,則需要在調用次invoke()方法前,顯式調用方法對象的setAccessible(true),将可通路private的方法
- SetAccessible
- Method和Field、Constructor對象都有setAccesible()方法。
- setAccessible作用是啟動和禁用通路安全檢查的開關。
- 參數值為true則訓示反射的對象在使用時應當取消Java語言通路檢查。
- 提高反射的效率。如果代碼中必須用反射,而該句代碼需要頻繁被調用,那麼設定為true。
- 使得原本無法通路的私有成員也可以通路。
- 參數值為false則訓示反射的對象應該實施Java語言通路檢查
2.4 反射操作泛型
- Java采用泛型擦除機制來引入泛型。Java中的泛型僅僅是給編譯器javac使用的,確定資料的安全性和免去強制類型轉換的問題,但是,一旦編譯完成,所有和泛型有關的類型全部擦除
- 為了通過反射操作這些類型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種類型來代表不能被歸一到Class類中的類型但是又和原始類型齊名的類型
- ParameterizedType:表示一種參數化類型,如Collection
- GenericArrayType:表示一種元素類型是參數化類型或者類型變量的數組類型
- TypeVariable:是各種類型變量的公共父接口
- WildcardType:代表一種通配符類型表達式
2.5 反射操作注解
- 這部分很重要,開始和後面架構出現結合(熟知注解對于後期架構學習很重要)
- 放一個案例在這裡說明相關功能
- 日後将在架構學習筆記中進一步闡述用反射操作注解的重要性
- 先定義一個類注解(關注Target裡面參數設定)
//類名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeTong{
String value();
}
- 再定義一個屬性的注解(需要注意自定義的兩個注解都有參數)
//屬性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldTong{
String colunmName();
String type();
int length();
}
- 此後定義一個student類,用于後面的反射
@TypeTong(value = "db_student")
class student2{
@FieldTong(colunmName = "db_name", type = "varchar",length = 3 )
private String name;
@FieldTong(colunmName = "db_id", type = "int",length = 10 )
private int id;
@FieldTong(colunmName = "db_age", type = "int",length = 10 )
private int age;
public student2() {
}
public student2(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "student2{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
- main函數編制
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class s1 = Class.forName("AnnotationandReflection.Demo03.student2");
//通過反射獲得注解
Annotation[] annotations = s1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//通過反射獲得指定注解屬性值
System.out.println("==========================");
TypeTong typeTong = (TypeTong)s1.getAnnotation(TypeTong.class);
String value = typeTong.value();
System.out.println(value);
//獲得類指定的注解
System.out.println("==========================");
Field f = s1.getDeclaredField("id");
FieldTong annotation = f.getAnnotation(FieldTong.class);
System.out.println(annotation.colunmName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
- 程式運作後結果如下: