JavaSE:註解與反射(Annotation & Reflection) 註解和框架是所有框架的底層,如Mybatis,spring。框架的底層實現機制就是註解和反射。註解相比於註釋,除了能較為直接的表示出這部分模塊的功能,也能實現一定的具體功能。 01 初識註解 1.1 什麼是註解 Anno ...
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());
}
- 程式運行後結果如下: