摘要:相比硬編碼,反射要復(fù)雜的多,但其給我們帶來(lái)了更大的靈活性。實(shí)際上構(gòu)造函數(shù)也是類的靜態(tài)方法,因此使用關(guān)鍵字創(chuàng)建類的新對(duì)象也會(huì)被當(dāng)做對(duì)類的靜態(tài)引用,從而觸發(fā)類加載器對(duì)類的加載?;A(chǔ)基礎(chǔ)主要是為反射提供通用特性的接口或基類。
1. Java類型系統(tǒng)
獲取Java類型系統(tǒng),主要有兩個(gè)方式:一種是傳統(tǒng)的RTTI(Run-Time Type Identification),它假定我們?cè)诰幾g時(shí)已經(jīng)知道了所有的類型信息;另一種是反射(Reflect),它允許我們?cè)诔绦蜻\(yùn)行時(shí)獲取并使用類型信息。
假如有一個(gè)簡(jiǎn)單的繼承體系,讓我們看下在RTTI和Reflect不同情況下如何獲取類型信息。
Animal為接口,定義getType以返回不同動(dòng)物的類型,Cat、Dog、Elephant等為具體實(shí)現(xiàn)類,均實(shí)現(xiàn)getType接口。一般情況下,我們會(huì)創(chuàng)建一個(gè)具體的對(duì)象(Cat,Dog,Elephant等),把它向上轉(zhuǎn)型為Animal,并在程序后面直接使用Animal引用。
具體樣例代碼如下:
/** * 動(dòng)物 */ public interface Animal { /** * 獲取動(dòng)物類型 * @return */ String getType(); } /** * 動(dòng)物具體子類 貓 */ public class Cat implements Animal{ @Override public String getType() { return "貓"; } } /** * 動(dòng)物具體子類 狗 */ public class Dog implements Animal{ @Override public String getType() { return "狗"; } } /** * 動(dòng)物具體實(shí)現(xiàn) 大象 */ public class Elephant implements Animal{ @Override public String getType() { return "大象"; } }
讓我們看下相同的功能通過(guò)硬編碼與反射兩個(gè)機(jī)制如何實(shí)現(xiàn)。
1.1. 硬編碼RTTI假定在編譯期,已經(jīng)知道了所有的類型信息。在編碼時(shí),可以直接使用具體的類型信息,這是我們最常見的類型用法。
在編譯期,編譯器通過(guò)容器、泛型保障類型系統(tǒng)的完整性;在運(yùn)行時(shí),由類型轉(zhuǎn)換操作來(lái)確保這一點(diǎn)。
硬編碼樣例如下:
public static void main(String... args){ Listanimals = createAnimals(); for (Animal animal : animals){ System.out.println(animal.getType()); } } /** * RTTI假定我們?cè)诰幾g時(shí)已經(jīng)知道了所有的類型 * @return */ private static List createAnimals() { List animals = new ArrayList<>(); animals.add(new Cat()); // 已知類型Cat animals.add(new Elephant()); // 已知類型Elephant animals.add(new Dog()); // 已知類型 Dog return animals; }
在這個(gè)例子中,我們把Cat、Elephant、Dog等向上轉(zhuǎn)型為Animal并存放于List
Reflect允許我們?cè)谶\(yùn)行時(shí)獲取并使用類型信息,它主要用于在編譯階段無(wú)法獲得所有的類型信息的場(chǎng)景,如各類框架。
反射樣例如下:
private static final String[] ANIMAL_TYPES = new String[]{ "com.example.reflectdemo.base.Cat", "com.example.reflectdemo.base.Elephant", "com.example.reflectdemo.base.Dog" }; public static void main(String... args){ List
反射,可以通過(guò)一組特殊的API,在運(yùn)行時(shí),動(dòng)態(tài)執(zhí)行所有Java硬編碼完成的功能(如對(duì)象創(chuàng)建、方法調(diào)用等)。
相比硬編碼,Java反射API要復(fù)雜的多,但其給我們帶來(lái)了更大的靈活性。
2. Class對(duì)象要理解RTTI在Java中的工作原理,首先需要知道類型信息在Java中是如何表示的。這個(gè)工作是由稱為Class對(duì)象的特殊對(duì)象完成的,它包含了與類相關(guān)的所有信息。Java使用Class對(duì)象來(lái)執(zhí)行RTTI。
類是程序的一部分,每個(gè)類都會(huì)有一個(gè)Class對(duì)象。每當(dāng)編寫并編譯一個(gè)新類(動(dòng)態(tài)代理、CGLIB、運(yùn)行時(shí)編譯都能創(chuàng)建新類),就會(huì)產(chǎn)生一個(gè)Class對(duì)象,為了生成這個(gè)類的對(duì)象,運(yùn)行這個(gè)程序的JVM將使用稱為“類加載器”的子系統(tǒng)。
2.1. Class Loader類加載器子系統(tǒng),是JVM體系重要的一環(huán),主要完成將class二進(jìn)制文件加載到JVM中,并將其轉(zhuǎn)換為Class對(duì)象的過(guò)程。
類加載器子系統(tǒng)實(shí)際上是一條類加載器鏈,但是只有一個(gè)原生類加載器,它是JVM實(shí)現(xiàn)的一部分。原生類加載器加載的是可信類,包括Java API類,他們通常是從本地加載。在這條鏈中,通常不需要添加額外的類加載器,但是如果有特殊需求,可以掛載新的類加載器(比如Web容器)。
所有的類都是在第一次使用時(shí),動(dòng)態(tài)加載到JVM中的,當(dāng)程序創(chuàng)建第一次對(duì)類的靜態(tài)成員引用時(shí),就會(huì)加載這個(gè)類。實(shí)際上構(gòu)造函數(shù)也是類的靜態(tài)方法,因此使用new關(guān)鍵字創(chuàng)建類的新對(duì)象也會(huì)被當(dāng)做對(duì)類的靜態(tài)引用,從而觸發(fā)類加載器對(duì)類的加載。
Java程序在它開始運(yùn)行之前并非被全部加載,各個(gè)部分是在需要時(shí)按需加載的。類加載器在加載類之前,首先檢查這個(gè)類的Class是否已經(jīng)加載,如果尚未加載,加載器會(huì)按照類名查找class文件,并對(duì)字節(jié)碼進(jìn)行有效性校驗(yàn),一旦Class對(duì)象被載入內(nèi)存,它就用來(lái)創(chuàng)建這個(gè)類的所有對(duì)象。
static初始化塊在類加載時(shí)調(diào)用,因此可以用于觀察類在什么時(shí)候進(jìn)行加載,樣例如下:
static class C1{ static { System.out.println("C1"); } } static class C2{ static { System.out.println("C2"); } } static class C3{ static { System.out.println("C3"); } } public static void main(String... args) throws Exception{ System.out.println("new start"); // 構(gòu)造函數(shù)為類的靜態(tài)引用,觸發(fā)類型加載 new C1(); new C1(); System.out.println("new end"); System.out.println(); System.out.println("Class.forName start"); // Class.forName為Class上的靜態(tài)函數(shù),用于強(qiáng)制加載Class Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2"); Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2"); System.out.println("Class.forName end"); System.out.println(); System.out.println("C3.class start"); // Class引用,會(huì)觸發(fā)Class加載,但是不會(huì)觸發(fā)初始化 Class c1 = C3.class; Class c2 = C3.class; System.out.println("C3.class end"); System.out.println(); System.out.println("c1.newInstance start"); // 調(diào)用class上的方法,觸發(fā)初始化邏輯 c1.newInstance(); System.out.println("c1.newInstance end"); }
輸出結(jié)果為:
new start C1 new end Class.forName start C2 Class.forName end C3.class start C3.class end c1.newInstance start C3 c1.newInstance end
看結(jié)果,C3.class的調(diào)用不會(huì)自動(dòng)的初始化該Class對(duì)象(調(diào)用static塊)。為了使用Class而做的準(zhǔn)備工作主要包括三個(gè)步驟:
加載,這個(gè)是由類加載器執(zhí)行。該步驟將查找字節(jié)碼文件,并根據(jù)字節(jié)碼創(chuàng)建一個(gè)Class對(duì)象。
鏈接,在鏈接階段將驗(yàn)證類中的字節(jié)碼,為靜態(tài)域分配存儲(chǔ)空間,如果必要的話,將解析這個(gè)類創(chuàng)建的對(duì)其他類的引用。
初始化,如果該類有超類,則對(duì)其進(jìn)行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊。初始化被延時(shí)到對(duì)靜態(tài)方法或非常數(shù)靜態(tài)域進(jìn)行首次訪問時(shí)才執(zhí)行。
2.2. Class 實(shí)例獲取Class對(duì)象作為Java類型體系的入口,如何獲取實(shí)例成為第一個(gè)要解決的問題。
Class對(duì)象的獲取主要有以下幾種途徑:
ClassName.class,獲取Class對(duì)象最簡(jiǎn)單最安全的方法,其在編譯時(shí)會(huì)受到編譯檢測(cè),但上例中已經(jīng)證實(shí),該方法不會(huì)觸發(fā)初始化邏輯。
Class.forName,這是反射機(jī)制最常用的方法之一,可以在不知具體類型時(shí),通過(guò)一個(gè)字符串加載所對(duì)應(yīng)的Class對(duì)象。
object.getClass,這也是比較常用的方式之一,通過(guò)一個(gè)對(duì)象獲取生成該對(duì)象的Class實(shí)例。
對(duì)于基本數(shù)據(jù)類型對(duì)于的包裝器類,還提供了一個(gè)TYPE字段,指向?qū)?yīng)的基本類型的Class對(duì)象。
基本類型 | TYPE類型 |
---|---|
boolean.class | Boolean.TYPE |
char.class | Char.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
Class對(duì)象存儲(chǔ)了一個(gè)class的所有類型信息,當(dāng)獲取到Class對(duì)象后,便能通過(guò)API獲取到所有信息。
在進(jìn)入Class類型信息之前,需要簡(jiǎn)單的了解下幾個(gè)反射的基類,以便更好的理解反射實(shí)現(xiàn)體系。
2.3.1 ClassAPI 基礎(chǔ)Class API基礎(chǔ)主要是為反射API提供通用特性的接口或基類。由于其通用性,現(xiàn)統(tǒng)一介紹,在具體的API中將對(duì)其進(jìn)行忽略。2.3.1.1 AnnotatedElement
AnnotatedElement為Java1.5新增接口,該接口代表程序中可以接受注解的程序元素,并提供統(tǒng)一的Annotation訪問方式,賦予API通過(guò)反射獲取Annotation的能力,當(dāng)一個(gè)Annotation類型被定義為運(yùn)行時(shí)后,該注解才能是運(yùn)行時(shí)可見,當(dāng)class文件被裝載時(shí)被保存在class文件中的Annotation才會(huì)被虛擬機(jī)讀取。
AnnotatedElement接口是所有注解元素(Class、Method、Field、Package和Constructor)的父接口,所以程序通過(guò)反射獲取了某個(gè)類的AnnotatedElement對(duì)象之后,程序就可以調(diào)用該對(duì)象的下列方法來(lái)訪問Annotation信息:
方法 | 含義 |
---|---|
返回程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null | |
Annotation[] getAnnotations() | 返回該程序元素上存在的所有注解 |
boolean is AnnotationPresent(Class annotationClass) | 判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false |
Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長(zhǎng)度為零的一個(gè)數(shù)組)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會(huì)對(duì)其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。 |
AnnotatedElement子類涵蓋所有可以出現(xiàn)Annotation的地方,其中包括:
Constructor 構(gòu)造函數(shù)
Method 方法
Class 類型
Field 字段
Package 包
Parameter 參數(shù)
AnnotatedParameterizedType 泛型
AnnotatedTypeVariable 變量
AnnotatedArrayType 數(shù)組類型
AnnotatedWildcardType
樣例如下:
public class AnnotatedElementTest { public static void main(String... args){ System.out.println("getAnnotations:"); for (Annotation annotation : A.class.getAnnotations()){ System.out.println(annotation); } System.out.println(); System.out.println("getAnnotation:" + A.class.getAnnotation(TestAnn1.class)); System.out.println(); System.out.println("isAnnotationPresent:" + A.class.isAnnotationPresent(TestAnn1.class)); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnn1{ } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnn2{ } @TestAnn1 @TestAnn2 public class A{ } }
輸出結(jié)果如下:
getAnnotations: @com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1() @com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn2() getAnnotation:@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1() isAnnotationPresent:true2.3.1.2. Member
Member用于標(biāo)記反射中簡(jiǎn)單元素。
所涉及方法如下:
方法 | 含義 |
---|---|
getDeclaringClass | 元素所在類 |
getName | 元素名稱 |
getModifiers | 元素修飾 |
isSynthetic | 是否為Synthetic,synthetic是由編譯器引入的字段、方法、類或其他結(jié)構(gòu),主要用于JVM內(nèi)部使用。 |
其子類主要包括:
Class 類型
Field 字段
Method 方法
Constructor 構(gòu)造函數(shù)
2.3.1.3. AccessibleObjectAccessibleObject可訪問對(duì)象,其對(duì)元素的可見性進(jìn)行統(tǒng)一封裝。同時(shí)實(shí)現(xiàn)AnnotatedElement接口,提供對(duì)Annotation元素的訪問。
所涉及方法如下:
方法 | 含義 |
---|---|
isAccessible | 是否可訪問 |
setAccessible | 重新訪問性 |
其中AccessibleObject所涉及的子類主要包括:
Field 字段
Constructor 構(gòu)造函數(shù)
Method 方法
AccessibleObject 對(duì)可見性提供了強(qiáng)大的支持,使我們能夠通過(guò)反射擴(kuò)展訪問限制,甚至可以對(duì)private成員進(jìn)行訪問。
樣例代碼如下:
public class TestBean { private String id; public String getId() { return id; } private void setId(String id) { this.id = id; } } public class AccessibleObjectBase { public static void main(String... args) throws Exception{ TestBean testBean = new TestBean(); // private方法, 不能直接調(diào)用 Method setId = TestBean.class.getDeclaredMethod("setId", String.class); System.out.println("setId:" + setId.isAccessible()); try { setId.invoke(testBean, "111"); }catch (Exception e){ System.out.println("private不能直接調(diào)用"); } setId.setAccessible(true); System.out.println("設(shè)置可訪問:" + setId.isAccessible()); setId.invoke(testBean, "111"); System.out.println("設(shè)置可訪問后,可以繞過(guò)private限制,進(jìn)行調(diào)用,結(jié)果為:" + testBean.getId()); } }
輸出結(jié)果如下:
setId:false private不能直接調(diào)用 設(shè)置可訪問:true 設(shè)置可訪問后,可以繞過(guò)private限制,進(jìn)行調(diào)用,結(jié)果為:1112.3.1.4. Executable
Executable表示可執(zhí)行元素的一種封裝,可以獲取方法簽名相關(guān)信息。
所涉及方法如下:
方法 | 含義 |
---|---|
getName | 獲取名稱 |
getModifiers | 獲取修飾符 |
getTypeParameters | 獲取類型參數(shù)(泛型) |
getParameterTypes | 獲取參數(shù)列表 |
getParameterCount | 獲取參數(shù)數(shù)量 |
getGenericParameterTypes | 獲取參數(shù)類型 |
getExceptionTypes | 獲取異常列表 |
getGenericExceptionTypes | 獲取異常列表 |
鎖涉及的子類主要有:
Constructor 構(gòu)造函數(shù)
Method 方法
樣例代碼如下:
public class TestBean { private String id; publicTestBean(String id) throws IllegalArgumentException, NotImplementedException { this.id = id; } public String getId() { return id; } private void setId(String id) { this.id = id; } } public class ExecutableTest { public static void main(String... args) throws Exception{ for (Constructor constructor : TestBean.class.getConstructors()){ System.out.println("getName: " + constructor.getName()); System.out.println(); System.out.println("getModifiers: " + Modifier.toString(constructor.getModifiers())); System.out.println(); System.out.println("getTypeParameters:"); for (TypeVariable t : constructor.getTypeParameters()){ System.out.println("type var:" + t.getName()); } System.out.println(); System.out.println("getParameterCount:" + constructor.getParameterCount()); System.out.println(); System.out.println("getParameterTypes:"); for (Class cls : constructor.getParameterTypes()){ System.out.println(cls.getName()); } System.out.println(); System.out.println("getExceptionTypes:"); for (Class cls : constructor.getExceptionTypes()){ System.out.println(cls.getName()); } } } }
輸出結(jié)果為:
getName: com.example.reflectdemo.reflectbase.TestBean getModifiers: public getTypeParameters: type var:T type var:R getParameterCount:1 getParameterTypes: java.lang.String getExceptionTypes: java.lang.IllegalArgumentException sun.reflect.generics.reflectiveObjects.NotImplementedException2.3.1.5. 方法命名規(guī)則
整個(gè)反射機(jī)制存在著通用的命名規(guī)則,了解這些規(guī)則,可以大大減少理解方法的阻力。
getXXX和getDeclaredXXX, 兩者主要區(qū)別在于獲取元素的可見性不同,一般情況下getXXX返回public類型的元素,而getDeclaredXXX獲取所有的元素,其中包括private、protected、public和package。
2.3.2. 類型信息Class自身信息包括類名、包名、父類以及實(shí)現(xiàn)的接口等。
Class類實(shí)現(xiàn)AnnotatedElement接口,以提供對(duì)注解的支持。除此以外,涉及方法如下:
方法 | 含義 |
---|---|
getName | 獲取類名 |
getCanonicalName | 得到目標(biāo)類的全名(包名+類名) |
getSimpleName | 等同于getCanonicalName |
getTypeParameters | 獲取類型參數(shù)(泛型) |
getSuperclass | 獲取父類 |
getPackage | 獲取包信息 |
getInterfaces | 獲取實(shí)現(xiàn)接口 |
getModifiers | 獲取修飾符 |
isAnonymousClass | 是否匿名類 |
isLocalClass | 是否局部類 |
isMemberClass | 是否成員類 |
isEnum | 是否枚舉 |
isInterface | 是否是接口 |
isArray | 是否是數(shù)組 |
getComponentType | 獲取數(shù)組元素類型 |
isPrimitive | 是否是基本類型 |
isAnnotation | 是否是注解 |
getEnumConstants | 獲取枚舉所有類型 |
getClasses | 獲取定義在該類中的public類型 |
getDeclaredClasses | 獲取定義在該類中的類型 |
實(shí)例如下:
class Baseimplements Callable { @Override public T call() throws Exception { return null; } } public final class BaseClassInfo extends Base implements Runnable, Serializable { @Override public void run() { } public static void main(String... args){ Class cls = BaseClassInfo.class; System.out.println("getName:" + cls.getName()); System.out.println(); System.out.println("getCanonicalName:" + cls.getCanonicalName()); System.out.println(); System.out.println("getSimpleName:" + cls.getSimpleName()); System.out.println(); System.out.println("getSuperclass:" + cls.getSuperclass()); System.out.println(); System.out.println("getPackage:" + cls.getPackage()); System.out.println(); for (Class c : cls.getInterfaces()){ System.out.println("interface : " + c.getSimpleName()); } System.out.println(); for (TypeVariable > typeVariable : cls.getTypeParameters()){ System.out.println("type var : " + typeVariable.getTypeName()); } System.out.println(); System.out.println("getModifiers:" + Modifier.toString(cls.getModifiers())); } }
輸出結(jié)果為:
getName:com.example.reflectdemo.classdetail.BaseClassInfo getCanonicalName:com.example.reflectdemo.classdetail.BaseClassInfo getSimpleName:BaseClassInfo getSuperclass:class com.example.reflectdemo.classdetail.Base getPackage:package com.example.reflectdemo.classdetail interface : Runnable interface : Serializable type var : T type var : R getModifiers:public final
Class類型判斷,實(shí)例如下:
public class ClassTypeTest { public static void main(String... args){ Runnable runnable = new Runnable() { @Override public void run() { printClassType(getClass()); } }; System.out.println("匿名內(nèi)部類"); runnable.run(); class M implements Runnable{ @Override public void run() { printClassType(getClass()); } } System.out.println("方法內(nèi)部類"); new M().run(); System.out.println("內(nèi)部類"); new ClassTypeTest().new T().run(); System.out.println("靜態(tài)內(nèi)部類"); new S().run(); System.out.println("枚舉"); printClassType(EnumTest.class); System.out.println("接口"); printClassType(Runnable.class); System.out.println("數(shù)組"); printClassType(int[].class); System.out.println("int"); printClassType(int.class); System.out.println("注解"); printClassType(AnnTest.class); } class T implements Runnable{ @Override public void run() { printClassType(getClass()); } } static class S implements Runnable{ @Override public void run() { printClassType(getClass()); } } enum EnumTest{ A, B, C } @interface AnnTest{ } private static void printClassType(Class cls){ System.out.println("Class:" + cls.getName()); System.out.println("isAnonymousClass:" + cls.isAnonymousClass()); System.out.println("isLocalClass:" + cls.isLocalClass()); System.out.println("isMemberClass:" + cls.isMemberClass()); System.out.println("isEnum:" + cls.isEnum()); System.out.println("isInterface:" + cls.isInterface()); System.out.println("isArray:" + cls.isArray()); System.out.println("isPrimitive:" + cls.isPrimitive()); System.out.println("isAnnotation:" + cls.isAnnotation()); if (cls.isEnum()){ System.out.println("getEnumConstants:"); for (Object o : cls.getEnumConstants()){ System.out.println(o); } } if (cls.isArray()){ System.out.println("getComponentType:" + cls.getComponentType()); } System.out.println(); } }
輸出結(jié)果如下:
匿名內(nèi)部類 Class:com.example.reflectdemo.classdetail.ClassTypeTest$1 isAnonymousClass:true isLocalClass:false isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 方法內(nèi)部類 Class:com.example.reflectdemo.classdetail.ClassTypeTest$1M isAnonymousClass:false isLocalClass:true isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 內(nèi)部類 Class:com.example.reflectdemo.classdetail.ClassTypeTest$T isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 靜態(tài)內(nèi)部類 Class:com.example.reflectdemo.classdetail.ClassTypeTest$S isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 枚舉 Class:com.example.reflectdemo.classdetail.ClassTypeTest$EnumTest isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:true isInterface:false isArray:false isPrimitive:false isAnnotation:false getEnumConstants: A B C 接口 Class:java.lang.Runnable isAnonymousClass:false isLocalClass:false isMemberClass:false isEnum:false isInterface:true isArray:false isPrimitive:false isAnnotation:false 數(shù)組 Class:[I isAnonymousClass:false isLocalClass:false isMemberClass:false isEnum:false isInterface:false isArray:true isPrimitive:false isAnnotation:false getComponentType:int int Class:int isAnonymousClass:false isLocalClass:false isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:true isAnnotation:false 注解 Class:com.example.reflectdemo.classdetail.ClassTypeTest$AnnTest isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:false isInterface:true isArray:false isPrimitive:false isAnnotation:true
內(nèi)部類型樣例如下:
public class InnerClassTest { public static void main(String... args){ System.out.println("getClasses"); for (Class cls : InnerClassTest.class.getClasses()){ System.out.println(cls.getName()); } } public interface I{ } public class A implements I{ } public class B implements I{ } }
輸出結(jié)果如下:
getClasses com.example.reflectdemo.classdetail.InnerClassTest$B com.example.reflectdemo.classdetail.InnerClassTest$A com.example.reflectdemo.classdetail.InnerClassTest$I2.3.3. 對(duì)象實(shí)例化
對(duì)象實(shí)例化,主要通過(guò)Constructor實(shí)例完成,首先通過(guò)相關(guān)方法獲取Constructor對(duì)象,然后進(jìn)行實(shí)例化操作。
所涉及的方法如下:
方法 | 含義 |
---|---|
newInstance | 使用默認(rèn)構(gòu)造函數(shù)實(shí)例化對(duì)象 |
getConstructors | 獲取public構(gòu)造函數(shù) |
getConstructor(Class>... parameterTypes) | 獲取特定public構(gòu)造函數(shù) |
getDeclaredConstructors | 獲取所有的構(gòu)造函數(shù) |
getDeclaredConstructor | 獲取特定構(gòu)造函數(shù) |
實(shí)例化涉及的核心類為Constructor,Constructor繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關(guān)功能,其核心方法如下:
方法 | 含義 |
---|---|
newInstance | 調(diào)用構(gòu)造函數(shù),實(shí)例化對(duì)象 |
樣例如下:
public class TestBean { private final Integer id; private final String name; publicTestBean(Integer id, String name) throws IllegalArgumentException, NotImplementedException { this.id = id; this.name = name; } @Override public String toString() { return "TestBean{" + "id=" + id + ", name="" + name + """ + "}"; } } public class ConstructorTest { public static void main(String... args) throws Exception{ for (Constructor constructor : TestBean.class.getConstructors()){ TestBean bean = (TestBean) constructor.newInstance(1, "Test"); System.out.println("newInstance:" + bean); } } }
輸出結(jié)果為:
newInstance:TestBean{id=1, name="Test"}2.3.4. 屬性信息
對(duì)象屬性是類型中最主要的信息之一,主要通過(guò)Field表示,首先通過(guò)相關(guān)方法獲取Field實(shí)例,然后進(jìn)行屬性值操作。
所涉及的方法如下:
方法 | 含義 |
---|---|
getFields | 獲取public字段 |
getField(String name) | 獲取特定public字段 |
getDeclaredFields | 獲取所有的的屬性 |
getDeclaredField | 獲取特定字段 |
Field繼承自AccessibleObject實(shí)現(xiàn)Member接口,擁有AccessibleObject、AnnotatedElement、Member相關(guān)功能,其核心方法如下:
方法 | 含義 |
---|---|
isEnumConstant | 是否枚舉常量 |
getType | 獲取類型 |
get | 獲取屬性值 |
getBoolean | 獲取boolean值 |
getByte | 獲取byte值 |
getChar | 獲取chat值 |
getShort | 獲取short值 |
getInt | 獲取int值 |
getLong | 獲取long值 |
getFloat | 獲取float值 |
getDouble | 獲取double值 |
set | 設(shè)置屬性值 |
setBoolean | 設(shè)置boolean值 |
setByte | 設(shè)置byte值 |
setChar | 設(shè)置char值 |
setShort | 設(shè)置short值 |
setInt | 設(shè)置int值 |
setLong | 設(shè)置long值 |
setFloat | 設(shè)置float值 |
setDouble | 設(shè)置double值 |
實(shí)例如下:
public enum EnumTest { A } public class FieldBean { private EnumTest aEnum; private String aString; private boolean aBoolean; private byte aByte; private char aChar; private short aShort; private int anInt; private long aLong; private float aFloat; private double aDouble; } public class FieldTest { public static void main(String... args) throws NoSuchFieldException, IllegalAccessException { FieldBean fieldBean = new FieldBean(); Field aEnum = getByName("aEnum"); Field aString = getByName("aString"); Field aBoolean = getByName("aBoolean"); Field aByte = getByName("aByte"); Field aChar = getByName("aChar"); Field aShort = getByName("aShort"); Field anInt = getByName("anInt"); Field aLong = getByName("aLong"); Field aFloat = getByName("aFloat"); Field aDouble = getByName("aDouble"); aEnum.set(fieldBean, EnumTest.A); System.out.println("isEnumConstant: " + aEnum.isEnumConstant()); System.out.println("set and get enum : " + aEnum.get(fieldBean)); aString.set(fieldBean, "Test"); System.out.println("set and get String : " + aString.get(fieldBean)); aBoolean.setBoolean(fieldBean, true); System.out.println("set and get Boolean : " + aBoolean.getBoolean(fieldBean)); aByte.setByte(fieldBean, (byte) 1); System.out.println("set and get Byte : " + aByte.getByte(fieldBean)); aChar.setChar(fieldBean, "a"); System.out.println("set and get Char : " + aChar.getChar(fieldBean)); aShort.setShort(fieldBean, (short) 1); System.out.println("set and get Short : " + aShort.getShort(fieldBean)); anInt.setInt(fieldBean, 1); System.out.println("set and get Int : " + anInt.getInt(fieldBean)); aLong.setLong(fieldBean, 1L); System.out.println("set and get Long : " + aLong.getLong(fieldBean)); aFloat.setFloat(fieldBean, 1f); System.out.println("set and get Float : " + aLong.getFloat(fieldBean)); aDouble.setDouble(fieldBean, 1.1); System.out.println("set and get Double : " + aLong.getDouble(fieldBean)); } private static Field getByName(String name) throws NoSuchFieldException { Field field = FieldBean.class.getDeclaredField(name); field.setAccessible(true); return field; } }2.3.5. 方法信息
類型中的方法通過(guò)Method表示,首先通過(guò)相關(guān)方法獲取Method實(shí)現(xiàn),然后通過(guò)反射執(zhí)行方法。
所涉及的方法如下:
方法 | 含義 |
---|---|
getMethods | 獲取public方法 |
getMethod(String name, Class>... parameterTypes) | 獲取特定public方法 |
getDeclaredMethods | 獲取所有方法 |
getDeclaredMethod | 獲取特定方法 |
Method繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關(guān)功能,其核心方法如下:
方法 | 含義 |
---|---|
getReturnType | 獲取方法返回類型 |
invoke | 調(diào)用方法 |
isBridge | 是否為橋接方法。橋接方法是 JDK 1.5 引入泛型后,為了使Java的泛型方法生成的字節(jié)碼和 1.5 版本前的字節(jié)碼相兼容,由編譯器自動(dòng)生成的方法。我們可以通過(guò)Method.isBridge()方法來(lái)判斷一個(gè)方法是否是橋接方法。 |
isDefault | 是否為默認(rèn)方法 |
實(shí)例如下:
public interface SayHi { String get(); default void hi(){ System.out.println("Hi " + get()); } } public class MethodBean implements Function, SayHi { private final String name; public MethodBean(String name) { this.name = name; } @Override public String get() { return "Hi " + name; } @Override public String apply(String s) { return s + name; } } public class MethodTest { public static void main(String... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method strMethod = MethodBean.class.getDeclaredMethod("apply", String.class); Method objMethod = MethodBean.class.getDeclaredMethod("apply", Object.class); Method hiMethod = SayHi.class.getDeclaredMethod("hi"); MethodBean methodBean = new MethodBean("張三"); System.out.println("Return Type:"); System.out.println("getMethod(String):" + strMethod.getReturnType()); System.out.println("getMethod(Object):" + objMethod.getReturnType()); System.out.println("hi():" + hiMethod.getReturnType()); System.out.println(); System.out.println("isBridge:"); System.out.println("getMethod(String):" + strMethod.isBridge()); System.out.println("getMethod(Object):" + objMethod.isBridge()); System.out.println("hi():" + hiMethod.isBridge()); System.out.println(); System.out.println("isDefault:"); System.out.println("getMethod(String):" + strMethod.isDefault()); System.out.println("getMethod(Object):" + objMethod.isDefault()); System.out.println("hi():" + hiMethod.isDefault()); System.out.println(); System.out.println("invoke:"); System.out.println("invoke(String):" + strMethod.invoke(methodBean, "Test")); System.out.println("invoke(Object):" + objMethod.invoke(methodBean, "Test")); System.out.println("hi():" + hiMethod.invoke(methodBean)); } }
輸出結(jié)果:
Return Type: getMethod(String):class java.lang.String getMethod(Object):class java.lang.Object hi():void isBridge: getMethod(String):false getMethod(Object):true hi():false isDefault: getMethod(String):false getMethod(Object):false hi():true invoke: invoke(String):Test張三 invoke(Object):Test張三 Hi Hi 張三 hi():null2.3.6. 其他
除上述核心方法外,Class對(duì)象提供了一些使用方法。
所涉及方法如下:
方法 | 含義 |
---|---|
isInstance | 判斷某對(duì)象是否是該類的實(shí)例 |
isAssignableFrom | 判定此 Class 對(duì)象所表示的類或接口與指定的 Class 參數(shù)所表示的類或接口是否相同,或是否是其超類或超接口。如果是則返回 true;否則返回 false。 |
getClassLoader | 獲取加載當(dāng)前類的ClassLoader |
getResourceAsStream | 根據(jù)該ClassLoader加載資源 |
getResource | 根據(jù)該ClassLoader加載資源 |
public class Task implements Runnable{ @Override public void run() { } } public class OtherTest { public static void main(String...args){ Task task = new Task(); System.out.println("Runnable isInstance Task:" + Runnable.class.isInstance(task)); System.out.println("Task isInstance Task:" + Task.class.isInstance(task)); System.out.println("Task isAssignableFrom Task:" + Task.class.isAssignableFrom(Task.class)); System.out.println("Runnable isAssignableFrom Task :" + Runnable.class.isAssignableFrom(Task.class)); } }
輸出結(jié)果:
Runnable isInstance Task:true Task isInstance Task:true Task isAssignableFrom Task:true Runnable isAssignableFrom Task :true3. 動(dòng)態(tài)代理
代理是基本的設(shè)計(jì)模式之一,它是我們?yōu)榱颂峁╊~外的或不同的操作,而插入的用來(lái)代替“實(shí)際”對(duì)象的對(duì)象。這些操作通常與“實(shí)際”對(duì)象通信,因此代理通常充當(dāng)中間人的角色。
例如,我們已有一個(gè)Handler接口,和一個(gè)實(shí)現(xiàn)類HandlerImpl,現(xiàn)需要對(duì)其進(jìn)行性能統(tǒng)計(jì),使用代理模式,代碼如下:
/** * handler接口 */ public interface Handler { /** * 數(shù)據(jù)處理 * @param data */ void handle(String data); } /** * Handler 實(shí)現(xiàn) */ public class HandlerImpl implements Handler{ @Override public void handle(String data) { try { TimeUnit.MILLISECONDS.sleep(100); System.out.println(data); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * Handler代理
* 實(shí)現(xiàn)Handler接口,記錄耗時(shí)情況,并將請(qǐng)求發(fā)送給目標(biāo)對(duì)象 */ public class HandlerProxy implements Handler{ private final Handler handler; public HandlerProxy(Handler handler) { this.handler = handler; } @Override public void handle(String data) { long start = System.currentTimeMillis(); this.handler.handle(data); long end = System.currentTimeMillis(); System.out.println("cost " + (end - start) + " ms"); } } public static void main(String... args){ Handler handler = new HandlerImpl(); Handler proxy = new HandlerProxy(handler); proxy.handle("Test"); }
采用代理模式,比較優(yōu)雅的解決了該問題,但如果Handler接口存在多個(gè)方法,并且需要對(duì)所有方法進(jìn)行性能監(jiān)控,那HandlerProxy的復(fù)雜性將會(huì)提高。
Java動(dòng)態(tài)代理比代理更進(jìn)一步,因?yàn)樗梢詣?dòng)態(tài)的創(chuàng)建代理并動(dòng)態(tài)的處理對(duì)所代理方法的調(diào)用。在動(dòng)態(tài)代理上所做的所有調(diào)用都會(huì)被重定向到單一的調(diào)用處理器上。
InvocationHandler 是由動(dòng)態(tài)代理處理器實(shí)現(xiàn)的接口,對(duì)代理對(duì)象的方法調(diào)用,會(huì)路由到該處理器上進(jìn)行統(tǒng)一處理。
其只有一個(gè)核心方法:
/** * proxy : 代理對(duì)象 * method : 調(diào)用方法 * args : 調(diào)用方法參數(shù) **/ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;3.2. Proxy
Proxy 用于生成代理對(duì)象。
其核心方法為:
/** * 獲取代理類3.3. demo
* loader : 類加載器 * interfaces: 類實(shí)現(xiàn)的接口 * */ Class> getProxyClass(ClassLoader loader, Class>... interfaces); /* * 生成代理對(duì)象
* loader : 類加載器 * interfaces : 類實(shí)現(xiàn)的接口 * h : 動(dòng)態(tài)代理回調(diào) */ Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h); /* * 判斷是否為代理類
* * cl : 待判斷類 */ public static boolean isProxyClass(Class> cl); /* * 獲取代理對(duì)象的InvocationHandler
* * proxy : 代理對(duì)象 */ InvocationHandler getInvocationHandler(Object proxy);
對(duì)于之前的性能監(jiān)控,使用Java動(dòng)態(tài)代理怎么實(shí)現(xiàn)?
/** * 定義代理方法回調(diào)處理器 */ public class CostInvocationHandler implements InvocationHandler { // 目標(biāo)對(duì)象 private final Object target; public CostInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("call method " + method + " ,args " + args); long start = System.currentTimeMillis(); try { // 將請(qǐng)求轉(zhuǎn)發(fā)給目標(biāo)對(duì)象 return method.invoke(this.target, args); }finally { long end = System.currentTimeMillis(); System.out.println("cost " + (end - start) + "ms"); } } } public static void main(String... args){ Handler handler = new HandlerImpl(); CostInvocationHandler invocationHandler = new CostInvocationHandler(handler); Class cls = Proxy.getProxyClass(DHandlerMain.class.getClassLoader(), Handler.class); Handler proxy = (Handler) Proxy.newProxyInstance(DHandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler); System.out.println("invoke method"); proxy.handle("Test"); System.out.println("isProxyClass: " + Proxy.isProxyClass(cls)); System.out.println("getInvocationHandler: " + (invocationHandler == Proxy.getInvocationHandler(proxy))); }4. 基于SPI的Plugin
SPI 全稱為 (Service Provider Interface) ,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制。 目前有不少框架用它來(lái)做服務(wù)的擴(kuò)展發(fā)現(xiàn),它是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制。
具體用法是在JAR包的"META-INF/services/"目錄下建立一個(gè)文件,文件名是接口的全限定名,文件的內(nèi)容可以有多行,每行都是該接口對(duì)應(yīng)的具體實(shí)現(xiàn)類的全限定名。然后使用 ServiceLoader.load(Interface.class) 對(duì)插件進(jìn)行加載。
假定,現(xiàn)有個(gè)場(chǎng)景,需要對(duì)消息進(jìn)行處理,但消息處理器的實(shí)現(xiàn)需要放開,及可以動(dòng)態(tài)的對(duì)處理器進(jìn)行加載,當(dāng)有新消息到達(dá)時(shí),依次調(diào)用處理器對(duì)消息進(jìn)行處理,讓我們結(jié)合SPI和反射構(gòu)造一個(gè)簡(jiǎn)單的Plugin系統(tǒng)。
首先我們需要一個(gè)插件接口和若干個(gè)實(shí)現(xiàn)類:
/** * 插件接口 */ public interface Handler { void handle(String msg); } /** * 實(shí)現(xiàn)1 */ public class Handler1 implements Handler{ @Override public void handle(String msg) { System.out.println("Handler1:" + msg); } } /** * 實(shí)現(xiàn)2 */ public class Handler2 implements Handler{ @Override public void handle(String msg) { System.out.println("Handler2:" + msg); } }
然后,我們添加SPI配置,及在META-INF/services/com.example.reflectdemo.plugin.Handler添加配置信息:
com.example.reflectdemo.plugin.Handler1 com.example.reflectdemo.plugin.Handler2
其次,我們實(shí)現(xiàn)DispatcherInvocationHandler類繼承自InvocationHandler接口,將方法調(diào)用分發(fā)給目標(biāo)對(duì)象。
/** * 分發(fā)處理器
* 將請(qǐng)求挨個(gè)轉(zhuǎn)發(fā)給目標(biāo)對(duì)象 */ public class DispatcherInvocationHandler implements InvocationHandler { // 目標(biāo)對(duì)象集合 private final Listtargets; public DispatcherInvocationHandler(List targets) { this.targets = targets; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { for (Object target : targets){ // 將請(qǐng)求轉(zhuǎn)發(fā)給目標(biāo)對(duì)象 method.invoke(target, args); } return null; } }
實(shí)現(xiàn)主流程,通過(guò)SPI加裝插件,將插件作為轉(zhuǎn)發(fā)對(duì)象實(shí)例化DispatcherInvocationHandler,在通過(guò)Proxy構(gòu)建動(dòng)態(tài)代理對(duì)象,最后調(diào)用handle方法進(jìn)行業(yè)務(wù)處理。
public static void main(String... args){ // 使用SPI加載插件 ServiceLoaderserviceLoader = ServiceLoader.load(Handler.class); List handlers = new ArrayList<>(); Iterator handlerIterator = serviceLoader.iterator(); while (handlerIterator.hasNext()){ Handler handler = handlerIterator.next(); handlers.add(handler); } // 將加載的插件組裝成InvocationHandler,以進(jìn)行分發(fā)處理 DispatcherInvocationHandler invocationHandler = new DispatcherInvocationHandler(handlers); // 生成代理對(duì)象 Handler proxy = (Handler) Proxy.newProxyInstance(HandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler); // 調(diào)用handle方法 proxy.handle("Test"); }
運(yùn)行結(jié)果如下:
Handler1:Test Handler2:Test5. 總結(jié)
Java類型系統(tǒng)、反射、動(dòng)態(tài)代理,作為Java的高級(jí)應(yīng)用,大量用于各大框架中。對(duì)其的掌握有助于加深對(duì)框架的理解。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76818.html
摘要:本章主要介紹的是的基礎(chǔ)應(yīng)用和源碼涉及的相關(guān)等,主要包含的內(nèi)容有的簡(jiǎn)介反射動(dòng)態(tài)代理包含代理和代理使用和代碼生成器等。組件生命周期,如圖測(cè)試代碼生成器代碼生成器,又稱逆向工程。 本章主要介紹的是MyBatis的基礎(chǔ)應(yīng)用和源碼涉及的相關(guān)等,主要包含的內(nèi)容有MyBatis的簡(jiǎn)介、反射、動(dòng)態(tài)代理(包含JDK代理和cglib代理)、MyBatis使用和代碼生成器等。 1.1 MyBatis簡(jiǎn)介 M...
摘要:適配器模式將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。這個(gè)主題對(duì)象在狀態(tài)發(fā)生變化時(shí),會(huì)通知所有觀察者對(duì)象,使它們能夠自動(dòng)更新自己。 1、常用設(shè)計(jì)模式 單例模式:懶漢式、餓漢式、雙重校驗(yàn)鎖、靜態(tài)加載,內(nèi)部類加載、枚舉類加載。保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。 代理模式:動(dòng)態(tài)代理和靜態(tài)代理,什么時(shí)候使用...
摘要:動(dòng)態(tài)編程使用場(chǎng)景通過(guò)配置生成代碼,減少重復(fù)編碼,降低維護(hù)成本。動(dòng)態(tài)生成字節(jié)碼操作字節(jié)碼的工具有,其中有兩個(gè)比較流行的,一個(gè)是,一個(gè)是。 作者簡(jiǎn)介 傳恒,一個(gè)喜歡攝影和旅游的軟件工程師,先后從事餓了么物流蜂鳥自配送和蜂鳥眾包的開發(fā),現(xiàn)在轉(zhuǎn)戰(zhàn) Java,目前負(fù)責(zé)物流策略組分流相關(guān)業(yè)務(wù)的開發(fā)。 什么是動(dòng)態(tài)編程 動(dòng)態(tài)編程是相對(duì)于靜態(tài)編程而言的,平時(shí)我們討論比較多的靜態(tài)編程語(yǔ)言例如Java, 與動(dòng)態(tài)...
閱讀 3415·2021-10-11 11:06
閱讀 2196·2019-08-29 11:10
閱讀 1957·2019-08-26 18:18
閱讀 3264·2019-08-26 13:34
閱讀 1572·2019-08-23 16:45
閱讀 1047·2019-08-23 16:29
閱讀 2810·2019-08-23 13:11
閱讀 3244·2019-08-23 12:58