摘要:相比硬編碼,反射要復雜的多,但其給我們帶來了更大的靈活性。實際上構造函數也是類的靜態方法,因此使用關鍵字創建類的新對象也會被當做對類的靜態引用,從而觸發類加載器對類的加載。基礎基礎主要是為反射提供通用特性的接口或基類。
1. Java類型系統
獲取Java類型系統,主要有兩個方式:一種是傳統的RTTI(Run-Time Type Identification),它假定我們在編譯時已經知道了所有的類型信息;另一種是反射(Reflect),它允許我們在程序運行時獲取并使用類型信息。
假如有一個簡單的繼承體系,讓我們看下在RTTI和Reflect不同情況下如何獲取類型信息。
Animal為接口,定義getType以返回不同動物的類型,Cat、Dog、Elephant等為具體實現類,均實現getType接口。一般情況下,我們會創建一個具體的對象(Cat,Dog,Elephant等),把它向上轉型為Animal,并在程序后面直接使用Animal引用。
具體樣例代碼如下:
/** * 動物 */ public interface Animal { /** * 獲取動物類型 * @return */ String getType(); } /** * 動物具體子類 貓 */ public class Cat implements Animal{ @Override public String getType() { return "貓"; } } /** * 動物具體子類 狗 */ public class Dog implements Animal{ @Override public String getType() { return "狗"; } } /** * 動物具體實現 大象 */ public class Elephant implements Animal{ @Override public String getType() { return "大象"; } }
讓我們看下相同的功能通過硬編碼與反射兩個機制如何實現。
1.1. 硬編碼RTTI假定在編譯期,已經知道了所有的類型信息。在編碼時,可以直接使用具體的類型信息,這是我們最常見的類型用法。
在編譯期,編譯器通過容器、泛型保障類型系統的完整性;在運行時,由類型轉換操作來確保這一點。
硬編碼樣例如下:
public static void main(String... args){ Listanimals = createAnimals(); for (Animal animal : animals){ System.out.println(animal.getType()); } } /** * RTTI假定我們在編譯時已經知道了所有的類型 * @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; }
在這個例子中,我們把Cat、Elephant、Dog等向上轉型為Animal并存放于List
Reflect允許我們在運行時獲取并使用類型信息,它主要用于在編譯階段無法獲得所有的類型信息的場景,如各類框架。
反射樣例如下:
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
反射,可以通過一組特殊的API,在運行時,動態執行所有Java硬編碼完成的功能(如對象創建、方法調用等)。
相比硬編碼,Java反射API要復雜的多,但其給我們帶來了更大的靈活性。
2. Class對象要理解RTTI在Java中的工作原理,首先需要知道類型信息在Java中是如何表示的。這個工作是由稱為Class對象的特殊對象完成的,它包含了與類相關的所有信息。Java使用Class對象來執行RTTI。
類是程序的一部分,每個類都會有一個Class對象。每當編寫并編譯一個新類(動態代理、CGLIB、運行時編譯都能創建新類),就會產生一個Class對象,為了生成這個類的對象,運行這個程序的JVM將使用稱為“類加載器”的子系統。
2.1. Class Loader類加載器子系統,是JVM體系重要的一環,主要完成將class二進制文件加載到JVM中,并將其轉換為Class對象的過程。
類加載器子系統實際上是一條類加載器鏈,但是只有一個原生類加載器,它是JVM實現的一部分。原生類加載器加載的是可信類,包括Java API類,他們通常是從本地加載。在這條鏈中,通常不需要添加額外的類加載器,但是如果有特殊需求,可以掛載新的類加載器(比如Web容器)。
所有的類都是在第一次使用時,動態加載到JVM中的,當程序創建第一次對類的靜態成員引用時,就會加載這個類。實際上構造函數也是類的靜態方法,因此使用new關鍵字創建類的新對象也會被當做對類的靜態引用,從而觸發類加載器對類的加載。
Java程序在它開始運行之前并非被全部加載,各個部分是在需要時按需加載的。類加載器在加載類之前,首先檢查這個類的Class是否已經加載,如果尚未加載,加載器會按照類名查找class文件,并對字節碼進行有效性校驗,一旦Class對象被載入內存,它就用來創建這個類的所有對象。
static初始化塊在類加載時調用,因此可以用于觀察類在什么時候進行加載,樣例如下:
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"); // 構造函數為類的靜態引用,觸發類型加載 new C1(); new C1(); System.out.println("new end"); System.out.println(); System.out.println("Class.forName start"); // Class.forName為Class上的靜態函數,用于強制加載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引用,會觸發Class加載,但是不會觸發初始化 Class c1 = C3.class; Class c2 = C3.class; System.out.println("C3.class end"); System.out.println(); System.out.println("c1.newInstance start"); // 調用class上的方法,觸發初始化邏輯 c1.newInstance(); System.out.println("c1.newInstance end"); }
輸出結果為:
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
看結果,C3.class的調用不會自動的初始化該Class對象(調用static塊)。為了使用Class而做的準備工作主要包括三個步驟:
加載,這個是由類加載器執行。該步驟將查找字節碼文件,并根據字節碼創建一個Class對象。
鏈接,在鏈接階段將驗證類中的字節碼,為靜態域分配存儲空間,如果必要的話,將解析這個類創建的對其他類的引用。
初始化,如果該類有超類,則對其進行初始化,執行靜態初始化器和靜態初始化塊。初始化被延時到對靜態方法或非常數靜態域進行首次訪問時才執行。
2.2. Class 實例獲取Class對象作為Java類型體系的入口,如何獲取實例成為第一個要解決的問題。
Class對象的獲取主要有以下幾種途徑:
ClassName.class,獲取Class對象最簡單最安全的方法,其在編譯時會受到編譯檢測,但上例中已經證實,該方法不會觸發初始化邏輯。
Class.forName,這是反射機制最常用的方法之一,可以在不知具體類型時,通過一個字符串加載所對應的Class對象。
object.getClass,這也是比較常用的方式之一,通過一個對象獲取生成該對象的Class實例。
對于基本數據類型對于的包裝器類,還提供了一個TYPE字段,指向對應的基本類型的Class對象。
基本類型 | 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對象存儲了一個class的所有類型信息,當獲取到Class對象后,便能通過API獲取到所有信息。
在進入Class類型信息之前,需要簡單的了解下幾個反射的基類,以便更好的理解反射實現體系。
2.3.1 ClassAPI 基礎Class API基礎主要是為反射API提供通用特性的接口或基類。由于其通用性,現統一介紹,在具體的API中將對其進行忽略。2.3.1.1 AnnotatedElement
AnnotatedElement為Java1.5新增接口,該接口代表程序中可以接受注解的程序元素,并提供統一的Annotation訪問方式,賦予API通過反射獲取Annotation的能力,當一個Annotation類型被定義為運行時后,該注解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation才會被虛擬機讀取。
AnnotatedElement接口是所有注解元素(Class、Method、Field、Package和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調用該對象的下列方法來訪問Annotation信息:
方法 | 含義 |
---|---|
返回程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null | |
Annotation[] getAnnotations() | 返回該程序元素上存在的所有注解 |
boolean is AnnotationPresent(Class annotationClass) | 判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false |
Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。 |
AnnotatedElement子類涵蓋所有可以出現Annotation的地方,其中包括:
Constructor 構造函數
Method 方法
Class 類型
Field 字段
Package 包
Parameter 參數
AnnotatedParameterizedType 泛型
AnnotatedTypeVariable 變量
AnnotatedArrayType 數組類型
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{ } }
輸出結果如下:
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用于標記反射中簡單元素。
所涉及方法如下:
方法 | 含義 |
---|---|
getDeclaringClass | 元素所在類 |
getName | 元素名稱 |
getModifiers | 元素修飾 |
isSynthetic | 是否為Synthetic,synthetic是由編譯器引入的字段、方法、類或其他結構,主要用于JVM內部使用。 |
其子類主要包括:
Class 類型
Field 字段
Method 方法
Constructor 構造函數
2.3.1.3. AccessibleObjectAccessibleObject可訪問對象,其對元素的可見性進行統一封裝。同時實現AnnotatedElement接口,提供對Annotation元素的訪問。
所涉及方法如下:
方法 | 含義 |
---|---|
isAccessible | 是否可訪問 |
setAccessible | 重新訪問性 |
其中AccessibleObject所涉及的子類主要包括:
Field 字段
Constructor 構造函數
Method 方法
AccessibleObject 對可見性提供了強大的支持,使我們能夠通過反射擴展訪問限制,甚至可以對private成員進行訪問。
樣例代碼如下:
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方法, 不能直接調用 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不能直接調用"); } setId.setAccessible(true); System.out.println("設置可訪問:" + setId.isAccessible()); setId.invoke(testBean, "111"); System.out.println("設置可訪問后,可以繞過private限制,進行調用,結果為:" + testBean.getId()); } }
輸出結果如下:
setId:false private不能直接調用 設置可訪問:true 設置可訪問后,可以繞過private限制,進行調用,結果為:1112.3.1.4. Executable
Executable表示可執行元素的一種封裝,可以獲取方法簽名相關信息。
所涉及方法如下:
方法 | 含義 |
---|---|
getName | 獲取名稱 |
getModifiers | 獲取修飾符 |
getTypeParameters | 獲取類型參數(泛型) |
getParameterTypes | 獲取參數列表 |
getParameterCount | 獲取參數數量 |
getGenericParameterTypes | 獲取參數類型 |
getExceptionTypes | 獲取異常列表 |
getGenericExceptionTypes | 獲取異常列表 |
鎖涉及的子類主要有:
Constructor 構造函數
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()); } } } }
輸出結果為:
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. 方法命名規則
整個反射機制存在著通用的命名規則,了解這些規則,可以大大減少理解方法的阻力。
getXXX和getDeclaredXXX, 兩者主要區別在于獲取元素的可見性不同,一般情況下getXXX返回public類型的元素,而getDeclaredXXX獲取所有的元素,其中包括private、protected、public和package。
2.3.2. 類型信息Class自身信息包括類名、包名、父類以及實現的接口等。
Class類實現AnnotatedElement接口,以提供對注解的支持。除此以外,涉及方法如下:
方法 | 含義 |
---|---|
getName | 獲取類名 |
getCanonicalName | 得到目標類的全名(包名+類名) |
getSimpleName | 等同于getCanonicalName |
getTypeParameters | 獲取類型參數(泛型) |
getSuperclass | 獲取父類 |
getPackage | 獲取包信息 |
getInterfaces | 獲取實現接口 |
getModifiers | 獲取修飾符 |
isAnonymousClass | 是否匿名類 |
isLocalClass | 是否局部類 |
isMemberClass | 是否成員類 |
isEnum | 是否枚舉 |
isInterface | 是否是接口 |
isArray | 是否是數組 |
getComponentType | 獲取數組元素類型 |
isPrimitive | 是否是基本類型 |
isAnnotation | 是否是注解 |
getEnumConstants | 獲取枚舉所有類型 |
getClasses | 獲取定義在該類中的public類型 |
getDeclaredClasses | 獲取定義在該類中的類型 |
實例如下:
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())); } }
輸出結果為:
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類型判斷,實例如下:
public class ClassTypeTest { public static void main(String... args){ Runnable runnable = new Runnable() { @Override public void run() { printClassType(getClass()); } }; System.out.println("匿名內部類"); runnable.run(); class M implements Runnable{ @Override public void run() { printClassType(getClass()); } } System.out.println("方法內部類"); new M().run(); System.out.println("內部類"); new ClassTypeTest().new T().run(); System.out.println("靜態內部類"); new S().run(); System.out.println("枚舉"); printClassType(EnumTest.class); System.out.println("接口"); printClassType(Runnable.class); System.out.println("數組"); 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(); } }
輸出結果如下:
匿名內部類 Class:com.example.reflectdemo.classdetail.ClassTypeTest$1 isAnonymousClass:true isLocalClass:false isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 方法內部類 Class:com.example.reflectdemo.classdetail.ClassTypeTest$1M isAnonymousClass:false isLocalClass:true isMemberClass:false isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 內部類 Class:com.example.reflectdemo.classdetail.ClassTypeTest$T isAnonymousClass:false isLocalClass:false isMemberClass:true isEnum:false isInterface:false isArray:false isPrimitive:false isAnnotation:false 靜態內部類 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 數組 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
內部類型樣例如下:
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{ } }
輸出結果如下:
getClasses com.example.reflectdemo.classdetail.InnerClassTest$B com.example.reflectdemo.classdetail.InnerClassTest$A com.example.reflectdemo.classdetail.InnerClassTest$I2.3.3. 對象實例化
對象實例化,主要通過Constructor實例完成,首先通過相關方法獲取Constructor對象,然后進行實例化操作。
所涉及的方法如下:
方法 | 含義 |
---|---|
newInstance | 使用默認構造函數實例化對象 |
getConstructors | 獲取public構造函數 |
getConstructor(Class>... parameterTypes) | 獲取特定public構造函數 |
getDeclaredConstructors | 獲取所有的構造函數 |
getDeclaredConstructor | 獲取特定構造函數 |
實例化涉及的核心類為Constructor,Constructor繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關功能,其核心方法如下:
方法 | 含義 |
---|---|
newInstance | 調用構造函數,實例化對象 |
樣例如下:
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); } } }
輸出結果為:
newInstance:TestBean{id=1, name="Test"}2.3.4. 屬性信息
對象屬性是類型中最主要的信息之一,主要通過Field表示,首先通過相關方法獲取Field實例,然后進行屬性值操作。
所涉及的方法如下:
方法 | 含義 |
---|---|
getFields | 獲取public字段 |
getField(String name) | 獲取特定public字段 |
getDeclaredFields | 獲取所有的的屬性 |
getDeclaredField | 獲取特定字段 |
Field繼承自AccessibleObject實現Member接口,擁有AccessibleObject、AnnotatedElement、Member相關功能,其核心方法如下:
方法 | 含義 |
---|---|
isEnumConstant | 是否枚舉常量 |
getType | 獲取類型 |
get | 獲取屬性值 |
getBoolean | 獲取boolean值 |
getByte | 獲取byte值 |
getChar | 獲取chat值 |
getShort | 獲取short值 |
getInt | 獲取int值 |
getLong | 獲取long值 |
getFloat | 獲取float值 |
getDouble | 獲取double值 |
set | 設置屬性值 |
setBoolean | 設置boolean值 |
setByte | 設置byte值 |
setChar | 設置char值 |
setShort | 設置short值 |
setInt | 設置int值 |
setLong | 設置long值 |
setFloat | 設置float值 |
setDouble | 設置double值 |
實例如下:
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. 方法信息
類型中的方法通過Method表示,首先通過相關方法獲取Method實現,然后通過反射執行方法。
所涉及的方法如下:
方法 | 含義 |
---|---|
getMethods | 獲取public方法 |
getMethod(String name, Class>... parameterTypes) | 獲取特定public方法 |
getDeclaredMethods | 獲取所有方法 |
getDeclaredMethod | 獲取特定方法 |
Method繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關功能,其核心方法如下:
方法 | 含義 |
---|---|
getReturnType | 獲取方法返回類型 |
invoke | 調用方法 |
isBridge | 是否為橋接方法。橋接方法是 JDK 1.5 引入泛型后,為了使Java的泛型方法生成的字節碼和 1.5 版本前的字節碼相兼容,由編譯器自動生成的方法。我們可以通過Method.isBridge()方法來判斷一個方法是否是橋接方法。 |
isDefault | 是否為默認方法 |
實例如下:
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)); } }
輸出結果:
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對象提供了一些使用方法。
所涉及方法如下:
方法 | 含義 |
---|---|
isInstance | 判斷某對象是否是該類的實例 |
isAssignableFrom | 判定此 Class 對象所表示的類或接口與指定的 Class 參數所表示的類或接口是否相同,或是否是其超類或超接口。如果是則返回 true;否則返回 false。 |
getClassLoader | 獲取加載當前類的ClassLoader |
getResourceAsStream | 根據該ClassLoader加載資源 |
getResource | 根據該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)); } }
輸出結果:
Runnable isInstance Task:true Task isInstance Task:true Task isAssignableFrom Task:true Runnable isAssignableFrom Task :true3. 動態代理
代理是基本的設計模式之一,它是我們為了提供額外的或不同的操作,而插入的用來代替“實際”對象的對象。這些操作通常與“實際”對象通信,因此代理通常充當中間人的角色。
例如,我們已有一個Handler接口,和一個實現類HandlerImpl,現需要對其進行性能統計,使用代理模式,代碼如下:
/** * handler接口 */ public interface Handler { /** * 數據處理 * @param data */ void handle(String data); } /** * Handler 實現 */ 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代理
* 實現Handler接口,記錄耗時情況,并將請求發送給目標對象 */ 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"); }
采用代理模式,比較優雅的解決了該問題,但如果Handler接口存在多個方法,并且需要對所有方法進行性能監控,那HandlerProxy的復雜性將會提高。
Java動態代理比代理更進一步,因為它可以動態的創建代理并動態的處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上。
InvocationHandler 是由動態代理處理器實現的接口,對代理對象的方法調用,會路由到該處理器上進行統一處理。
其只有一個核心方法:
/** * proxy : 代理對象 * method : 調用方法 * args : 調用方法參數 **/ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;3.2. Proxy
Proxy 用于生成代理對象。
其核心方法為:
/** * 獲取代理類3.3. demo
* loader : 類加載器 * interfaces: 類實現的接口 * */ Class> getProxyClass(ClassLoader loader, Class>... interfaces); /* * 生成代理對象
* loader : 類加載器 * interfaces : 類實現的接口 * h : 動態代理回調 */ Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h); /* * 判斷是否為代理類
* * cl : 待判斷類 */ public static boolean isProxyClass(Class> cl); /* * 獲取代理對象的InvocationHandler
* * proxy : 代理對象 */ InvocationHandler getInvocationHandler(Object proxy);
對于之前的性能監控,使用Java動態代理怎么實現?
/** * 定義代理方法回調處理器 */ public class CostInvocationHandler implements InvocationHandler { // 目標對象 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 { // 將請求轉發給目標對象 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內置的一種服務提供發現機制。 目前有不少框架用它來做服務的擴展發現,它是一種動態替換發現的機制。
具體用法是在JAR包的"META-INF/services/"目錄下建立一個文件,文件名是接口的全限定名,文件的內容可以有多行,每行都是該接口對應的具體實現類的全限定名。然后使用 ServiceLoader.load(Interface.class) 對插件進行加載。
假定,現有個場景,需要對消息進行處理,但消息處理器的實現需要放開,及可以動態的對處理器進行加載,當有新消息到達時,依次調用處理器對消息進行處理,讓我們結合SPI和反射構造一個簡單的Plugin系統。
首先我們需要一個插件接口和若干個實現類:
/** * 插件接口 */ public interface Handler { void handle(String msg); } /** * 實現1 */ public class Handler1 implements Handler{ @Override public void handle(String msg) { System.out.println("Handler1:" + msg); } } /** * 實現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
其次,我們實現DispatcherInvocationHandler類繼承自InvocationHandler接口,將方法調用分發給目標對象。
/** * 分發處理器
* 將請求挨個轉發給目標對象 */ public class DispatcherInvocationHandler implements InvocationHandler { // 目標對象集合 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){ // 將請求轉發給目標對象 method.invoke(target, args); } return null; } }
實現主流程,通過SPI加裝插件,將插件作為轉發對象實例化DispatcherInvocationHandler,在通過Proxy構建動態代理對象,最后調用handle方法進行業務處理。
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,以進行分發處理 DispatcherInvocationHandler invocationHandler = new DispatcherInvocationHandler(handlers); // 生成代理對象 Handler proxy = (Handler) Proxy.newProxyInstance(HandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler); // 調用handle方法 proxy.handle("Test"); }
運行結果如下:
Handler1:Test Handler2:Test5. 總結
Java類型系統、反射、動態代理,作為Java的高級應用,大量用于各大框架中。對其的掌握有助于加深對框架的理解。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76818.html
摘要:適配器模式將一個類的接口轉換成客戶希望的另外一個接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。 1、常用設計模式 單例模式:懶漢式、餓漢式、雙重校驗鎖、靜態加載,內部類加載、枚舉類加載。保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。 代理模式:動態代理和靜態代理,什么時候使用...
摘要:動態編程使用場景通過配置生成代碼,減少重復編碼,降低維護成本。動態生成字節碼操作字節碼的工具有,其中有兩個比較流行的,一個是,一個是。 作者簡介 傳恒,一個喜歡攝影和旅游的軟件工程師,先后從事餓了么物流蜂鳥自配送和蜂鳥眾包的開發,現在轉戰 Java,目前負責物流策略組分流相關業務的開發。 什么是動態編程 動態編程是相對于靜態編程而言的,平時我們討論比較多的靜態編程語言例如Java, 與動態...
閱讀 3398·2021-10-11 11:06
閱讀 2182·2019-08-29 11:10
閱讀 1944·2019-08-26 18:18
閱讀 3255·2019-08-26 13:34
閱讀 1559·2019-08-23 16:45
閱讀 1037·2019-08-23 16:29
閱讀 2797·2019-08-23 13:11
閱讀 3226·2019-08-23 12:58