Java 反射機(jī)制教程
Java 反射機(jī)制可以讓我們?cè)诰幾g期(Compile Time)之外的運(yùn)行期(Runtime)檢查類(lèi),接口埃碱,變量以及方法的信息划咐。反射還可以讓我們?cè)谶\(yùn)行期實(shí)例化對(duì)象,調(diào)用方法宠漩,通過(guò)調(diào)用 get/set 方法獲取變量的值柜蜈。
具體的理論細(xì)節(jié)可以通過(guò)某度進(jìn)行搜索. 可以查詢(xún)到更多理論性的解答..今天我們就以代碼的形式進(jìn)行科普
我們先進(jìn)行一個(gè)簡(jiǎn)單的例子介紹
Method[] methods = MyClass.class.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}
在這個(gè)例子中通過(guò)調(diào)用 MyClass 類(lèi)的 class 屬性獲取對(duì)應(yīng)的 Class 類(lèi)的對(duì)象仗谆,通過(guò)這個(gè) Class 類(lèi)的對(duì)象獲取 MyObject 類(lèi)中的方法集合指巡。迭代這個(gè)方法的集合并且打印每個(gè)方法的名字。
java 類(lèi)
使用 Java 反射機(jī)制可以在運(yùn)行時(shí)期檢查 Java 類(lèi)的信息隶垮,檢查 Java 類(lèi)的信息往往是你在使用 Java 反射機(jī)制的時(shí)候所做的第一件事情厌处,通過(guò)獲取類(lèi)的信息你可以獲取以下相關(guān)的內(nèi)容:
- Class 對(duì)象
- 類(lèi)名
- 修飾符
- 包信息
- 父類(lèi)
- 實(shí)現(xiàn)的接口
- 構(gòu)造器
- 方法
- 變量
- 注解
除了上述這些內(nèi)容,還有很多的信息你可以通過(guò)反射機(jī)制獲得,請(qǐng)參考 官方文檔.
Class 對(duì)象
在你想檢查一個(gè)類(lèi)的信息之前岁疼,你首先需要獲取類(lèi)的 Class 對(duì)象。Java 中的所有類(lèi)型包括基本類(lèi)型(int, long, float等等)缆娃,即使是數(shù)組都有與之關(guān)聯(lián)的 Class 類(lèi)的對(duì)象捷绒。如果你在編譯期知道一個(gè)類(lèi)的名字的話(huà),那么你可以使用如下的方式獲取一個(gè)類(lèi)的 Class 對(duì)象贯要。
Class myClass = MyClass.class;
如果你在編譯期不知道類(lèi)的名字暖侨,但是你可以在運(yùn)行期獲得到類(lèi)名的字符串,那么你則可以這么做來(lái)獲取 Class 對(duì)象:
String className = ... ;//在運(yùn)行期獲取的類(lèi)名字符串
Class class = Class.forName(className);
在使用 Class.forName() 方法時(shí),你必須提供一個(gè)類(lèi)的全名崇渗,這個(gè)全名包括類(lèi)所在的包的名字字逗。例如 MyClass 類(lèi)位于 com.enoch.driver 包,那么他的全名就是 com.enoch.driver.MyClass宅广。 如果在調(diào)用Class.forName()方法時(shí)葫掉,沒(méi)有在編譯路徑下(classpath)找到對(duì)應(yīng)的類(lèi),那么將會(huì)拋出ClassNotFoundException跟狱。
大家看到Class.forName()的同學(xué)應(yīng)該都會(huì)想到JDBC連接數(shù)據(jù)庫(kù)時(shí),驅(qū)動(dòng)的調(diào)用就是通過(guò)此方法
類(lèi)名
你可以從 Class 對(duì)象中獲取兩個(gè)版本的類(lèi)名俭厚。
通過(guò) getName() 方法返回類(lèi)的全限定類(lèi)名(包含包名)
Class aClass = ... //獲取Class對(duì)象,具體方式可見(jiàn)Class對(duì)象小節(jié)
String className = aClass.getName();
如果你僅僅只是想獲取類(lèi)的名字(不包含包名)驶臊,那么你可以使用 getSimpleName()方法:
Class aClass = ... //獲取Class對(duì)象挪挤,具體方式可見(jiàn)Class對(duì)象小節(jié)
String simpleClassName = aClass.getSimpleName();
修飾符
可以通過(guò) Class 對(duì)象來(lái)訪問(wèn)一個(gè)類(lèi)的修飾符, 即public,private,static 等等的關(guān)鍵字关翎,你可以使用如下方法來(lái)獲取類(lèi)的修飾符:
Class aClass = ... //獲取Class對(duì)象扛门,具體方式可見(jiàn)Class對(duì)象小節(jié)
int modifiers = aClass.getModifiers();
修飾符都被包裝成一個(gè)int類(lèi)型的數(shù)字,這樣每個(gè)修飾符都是一個(gè)位標(biāo)識(shí)(flag bit)纵寝,這個(gè)位標(biāo)識(shí)可以設(shè)置和清除修飾符的類(lèi)型论寨。 可以使用 java.lang.reflect.Modifier 類(lèi)中的方法來(lái)檢查修飾符的類(lèi)型
Modifier.isAbstract(int modifiers); Modifier.isFinal(int modifiers); Modifier.isInterface(int modifiers); Modifier.isNative(int modifiers); Modifier.isPrivate(int modifiers); Modifier.isProtected(int modifiers); Modifier.isPublic(int modifiers); Modifier.isStatic(int modifiers); Modifier.isStrict(int modifiers); Modifier.isSynchronized(int modifiers); Modifier.isTransient(int modifiers); Modifier.isVolatile(int modifiers);
包信息
可以使用 Class 對(duì)象通過(guò)如下的方式獲取包信息:
Class aClass = ... //獲取Class對(duì)象,具體方式可見(jiàn)Class對(duì)象小節(jié)
Package package = aClass.getPackage();
通過(guò) Package 對(duì)象你可以獲取包的相關(guān)信息店雅,比如包名政基,你也可以通過(guò) Manifest 文件訪問(wèn)位于編譯路徑下 jar 包的指定信息,比如你可以在 Manifest 文件中指定包的版本編號(hào)闹啦。更多的 Package 類(lèi)信息可以閱讀 java.lang.Package沮明。
父類(lèi)
通過(guò) Class 對(duì)象你可以訪問(wèn)類(lèi)的父類(lèi),如下例:
Class superclass = aClass.getSuperclass();
可以看到 superclass 對(duì)象其實(shí)就是一個(gè) Class 類(lèi)的實(shí)例窍奋,所以你可以繼續(xù)在這個(gè)對(duì)象上進(jìn)行反射操作荐健。
實(shí)現(xiàn)的接口
可以通過(guò)如下方式獲取指定類(lèi)所實(shí)現(xiàn)的接口集合:
Class aClass = ... //獲取Class對(duì)象酱畅,具體方式可見(jiàn)Class對(duì)象小節(jié)
Class[] interfaces = aClass.getInterfaces();
由于一個(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口,因此 getInterfaces(); 方法返回一個(gè) Class 數(shù)組江场,在 Java 中接口同樣有對(duì)應(yīng)的 Class 對(duì)象纺酸。 注意:getInterfaces() 方法僅僅只返回當(dāng)前類(lèi)所實(shí)現(xiàn)的接口。當(dāng)前類(lèi)的父類(lèi)如果實(shí)現(xiàn)了接口址否,這些接口是不會(huì)在返回的 Class 集合中的餐蔬,盡管實(shí)際上當(dāng)前類(lèi)其實(shí)已經(jīng)實(shí)現(xiàn)了父類(lèi)接口。
構(gòu)造器
你可以通過(guò)如下方式訪問(wèn)一個(gè)類(lèi)的構(gòu)造方法:
Constructor[] constructors = aClass.getConstructors();
方法
你可以通過(guò)如下方式訪問(wèn)一個(gè)類(lèi)的所有方法:
Method[] method = aClass.getMethods();
變量
你可以通過(guò)如下方式訪問(wèn)一個(gè)類(lèi)的成員變量:
Field[] method = aClass.getFields();
注解
你可以通過(guò)如下方式訪問(wèn)一個(gè)類(lèi)的注解:
Annotation[] annotations = aClass.getAnnotations();
獲取 Constructor 對(duì)象
我們可以通 過(guò) Class 對(duì)象來(lái)獲取 Constructor 類(lèi)的實(shí)例:
Class aClass = ...//獲取Class對(duì)象
Constructor[] constructors = aClass.getConstructors();
返回的 Constructor 數(shù)組包含每一個(gè)聲明為公有的(Public)構(gòu)造方法佑附。 如果你知道你要訪問(wèn)的構(gòu)造方法的方法參數(shù)類(lèi)型樊诺,你可以用下面的方法獲取指定的構(gòu)造方法,這例子返回的構(gòu)造方法的方法參數(shù)為 String 類(lèi)型:
Class aClass = ...//獲取Class對(duì)象
Constructor constructor = aClass.getConstructor(new Class[]{String.class});
構(gòu)造方法參數(shù)
你可以通過(guò)如下方式獲取指定構(gòu)造方法的方法參數(shù)信息:
Constructor constructor = ... //獲取Constructor對(duì)象
Class[] parameterTypes = constructor.getParameterTypes();
利用 Constructor 對(duì)象實(shí)例化一個(gè)類(lèi)
你可以通過(guò)如下方法實(shí)例化一個(gè)類(lèi):
Constructor constructor = MyClass.class.getConstructor(String.class);
MyClass myObject = (MyClass)
constructor.newInstance("一個(gè)構(gòu)造函數(shù)");
constructor.newInstance()方法的方法參數(shù)是一個(gè)可變參數(shù)列表音同,但是當(dāng)你調(diào)用構(gòu)造方法的時(shí)候你必須提供精確的參數(shù)词爬,即形參與實(shí)參必須一一對(duì)應(yīng)。在這個(gè)例子中構(gòu)造方法需要一個(gè) String 類(lèi)型的參數(shù)权均,那我們?cè)谡{(diào)用 newInstance 方法的時(shí)候就必須傳入一個(gè) String 類(lèi)型的參數(shù)顿膨。
內(nèi)容索引
- 獲取 Field 對(duì)象
- 變量名稱(chēng)
- 變量類(lèi)型
- 獲取或設(shè)置(get/set)變量值
獲取 Field 對(duì)象
可以通過(guò) Class 對(duì)象獲取 Field 對(duì)象,如下例:
Class aClass = ...//獲取Class對(duì)象
Field[] methods = aClass.getFields();
返回的 Field 對(duì)象數(shù)組包含了指定類(lèi)中聲明為公有的(public)的所有變量集合叽赊。 如果你知道你要訪問(wèn)的變量名稱(chēng)恋沃,你可以通過(guò)如下的方式獲取指定的變量:
Class aClass = MyClass.class
Field field = aClass.getField("someField");
上面的例子返回的Field類(lèi)的實(shí)例對(duì)應(yīng)的就是在 MyClass 類(lèi)中聲明的名為 someField 的成員變量,就是這樣:
public class MyClass{
public String someField = null;
}
在調(diào)用 getField()方法時(shí)必指,如果根據(jù)給定的方法參數(shù)沒(méi)有找到對(duì)應(yīng)的變量芽唇,那么就會(huì)拋出 NoSuchFieldException。
變量名稱(chēng)
一旦你獲取了 Field 實(shí)例取劫,你可以通過(guò)調(diào)用 Field.getName()方法獲取他的變量名稱(chēng)匆笤,如下例:
Field field = ... //獲取Field對(duì)象
String fieldName = field.getName();
變量類(lèi)型
Field field = aClass.getField("someField");
Object fieldType = field.getType();
獲取或設(shè)置(get/set)變量值
一旦你獲得了一個(gè) Field 的引用,你就可以通過(guò)調(diào)用 Field.get()或 Field.set()方法谱邪,獲取或者設(shè)置變量的值炮捧,如下例:
Class aClass = MyClass.class
Field field = aClass.getField("someField");
MyClass objectInstance = new MyClass();
Object value = field.get(objectInstance);
field.set(objetInstance, value);
傳入 Field.get()/Field.set()方法的參數(shù) objetInstance 應(yīng)該是擁有指定變量的類(lèi)的實(shí)例。在上述的例子中傳入的參數(shù)是 MyObjec t類(lèi)的實(shí)例惦银,是因?yàn)?someField 是 MyObject 類(lèi)的實(shí)例咆课。 如果變量是靜態(tài)變量的話(huà)(public static)那么在調(diào)用 Field.get()/Field.set()方法的時(shí)候傳入 null 做為參數(shù)而不用傳遞擁有該變量的類(lèi)的實(shí)例。(譯者注:你如果傳入擁有該變量的類(lèi)的實(shí)例也可以得到相同的結(jié)果)
內(nèi)容索引
- 獲取 Method 對(duì)象
- 方法參數(shù)以及返回類(lèi)型
- 通過(guò) Method 對(duì)象調(diào)用方法
使用 Java 反射你可以在運(yùn)行期檢查一個(gè)方法的信息以及在運(yùn)行期調(diào)用這個(gè)方法扯俱,通過(guò)使用 java.lang.reflect.Method 類(lèi)就可以實(shí)現(xiàn)上述功能书蚪。在本節(jié)會(huì)帶你深入了解 Method 對(duì)象的信息。
獲取 Method 對(duì)象
可以通過(guò) Class 對(duì)象獲取 Method 對(duì)象迅栅,如下例:
Class aClass = ...//獲取Class對(duì)象
Method[] methods = aClass.getMethods();
返回的 Method 對(duì)象數(shù)組包含了指定類(lèi)中聲明為公有的(public)的所有變量集合殊校。
如果你知道你要調(diào)用方法的具體參數(shù)類(lèi)型,你就可以直接通過(guò)參數(shù)類(lèi)型來(lái)獲取指定的方法读存,下面這個(gè)例子中返回方法對(duì)象名稱(chēng)是“doSomething”为流,他的方法參數(shù)是 String 類(lèi)型:
Class aClass = ...//獲取Class對(duì)象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});
如果根據(jù)給定的方法名稱(chēng)以及參數(shù)類(lèi)型無(wú)法匹配到相應(yīng)的方法呕屎,則會(huì)拋出 NoSuchMethodException。 如果你想要獲取的方法沒(méi)有參數(shù)敬察,那么在調(diào)用 getMethod()方法時(shí)第二個(gè)參數(shù)傳入 null 即可秀睛,就像這樣:
Class aClass = ...//獲取Class對(duì)象
Method method = aClass.getMethod("doSomething", null);
方法參數(shù)以及返回類(lèi)型
你可以獲取指定方法的方法參數(shù)是哪些:
Method method = ... //獲取Class對(duì)象
Class[] parameterTypes = method.getParameterTypes();
你可以獲取指定方法的返回類(lèi)型:
Method method = ... //獲取Class對(duì)象
Class returnType = method.getReturnType();
通過(guò) Method 對(duì)象調(diào)用方法
你可以通過(guò)如下方式來(lái)調(diào)用一個(gè)方法:
//獲取一個(gè)方法名為doSomesthing,參數(shù)類(lèi)型為String的方法
Method method = MyClass.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
傳入的 null 參數(shù)是你要調(diào)用方法的對(duì)象莲祸,如果是一個(gè)靜態(tài)方法調(diào)用的話(huà)則可以用 null 代替指定對(duì)象作為 invoke()的參數(shù)蹂安,在上面這個(gè)例子中,如果 doSomething 不是靜態(tài)方法的話(huà)锐帜,你就要傳入有效的 MyClass 實(shí)例而不是 null藤抡。 Method.invoke(Object target, Object … parameters)方法的第二個(gè)參數(shù)是一個(gè)可變參數(shù)列表,但是你必須要傳入與你要調(diào)用方法的形參一一對(duì)應(yīng)的實(shí)參抹估。就像上個(gè)例子那樣,方法需要 String 類(lèi)型的參數(shù)弄兜,那我們必須要傳入一個(gè)字符串药蜻。
Java 注解
內(nèi)容索引
- 什么是注解
- 類(lèi)注解
- 方法注解
- 參數(shù)注解
- 變量注解
利用 Java 反射機(jī)制可以在運(yùn)行期獲取 Java 類(lèi)的注解信息。
什么是注解
注解是 Java 5 的一個(gè)新特性替饿。注解是插入你代碼中的一種注釋或者說(shuō)是一種元數(shù)據(jù)(meta data)语泽。這些注解信息可以在編譯期使用預(yù)編譯工具進(jìn)行處理(pre-compiler tools),也可以在運(yùn)行期使用 Java 反射機(jī)制進(jìn)行處理视卢。下面是一個(gè)類(lèi)注解的例子:
@MyAnnotation(name="someName", value = "Hello World")
public class TheClass {
}
在 TheClass 類(lèi)定義的上面有一個(gè)@MyAnnotation 的注解踱卵。注解的定義與接口的定義相似,下面是MyAnnotation注解的定義:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
public String name();
public String value();
}
在 interface 前面的@符號(hào)表名這是一個(gè)注解据过,一旦你定義了一個(gè)注解之后你就可以將其應(yīng)用到你的代碼中惋砂,就像之前我們的那個(gè)例子那樣。 在注解定義中的兩個(gè)指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE)绳锅,說(shuō)明了這個(gè)注解該如何使用西饵。 @Retention(RetentionPolicy.RUNTIME)表示這個(gè)注解可以在運(yùn)行期通過(guò)反射訪問(wèn)。如果你沒(méi)有在注解定義的時(shí)候使用這個(gè)指示那么這個(gè)注解的信息不會(huì)保留到運(yùn)行期鳞芙,這樣反射就無(wú)法獲取它的信息眷柔。 @Target(ElementType.TYPE) 表示這個(gè)注解只能用在類(lèi)型上面(比如類(lèi)跟接口)。你同樣可以把Type改為Field或者M(jìn)ethod原朝,或者你可以不用這個(gè)指示驯嘱,這樣的話(huà)你的注解在類(lèi),方法和變量上就都可以使用了喳坠。 關(guān)于 Java 注解更詳細(xì)的講解可以訪問(wèn) Java Annotations tutorial鞠评。
類(lèi)注解
你可以在運(yùn)行期訪問(wèn)類(lèi),方法或者變量的注解信息壕鹉,下是一個(gè)訪問(wèn)類(lèi)注解的例子:
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
你還可以像下面這樣指定訪問(wèn)一個(gè)類(lèi)的注解:
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
方法注解
下面是一個(gè)方法注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}
你可以像這樣訪問(wèn)方法注解:
Method method = ... //獲取方法對(duì)象
Annotation[] annotations = method.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
你可以像這樣訪問(wèn)指定的方法注解:
Method method = ... // 獲取方法對(duì)象
Annotation annotation = method.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
參數(shù)注解
方法參數(shù)也可以添加注解谢澈,就像下面這樣:
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
你可以通過(guò) Method對(duì)象來(lái)訪問(wèn)方法參數(shù)注解:
Method method = ... //獲取方法對(duì)象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();
int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}
需要注意的是 Method.getParameterAnnotations()方法返回一個(gè)注解類(lèi)型的二維數(shù)組煌贴,每一個(gè)方法的參數(shù)包含一個(gè)注解數(shù)組。
變量注解
下面是一個(gè)變量注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}
你可以像這樣來(lái)訪問(wèn)變量的注解:
Field field = ... //獲取方法對(duì)象
Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
你可以像這樣訪問(wèn)指定的變量注解:
Field field = ...//獲取方法對(duì)象
Annotation annotation = field.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
Java 泛型
內(nèi)容索引
- 運(yùn)用泛型反射的經(jīng)驗(yàn)法則
- 泛型方法返回類(lèi)型
- 泛型方法參數(shù)類(lèi)型
- 泛型變量類(lèi)型
泛型方法返回類(lèi)型
public class MyClass {
protected List<String> stringList = ...;
public List<String> getStringList(){
return this.stringList;
}
}
我們可以獲取 getStringList()方法的泛型返回類(lèi)型锥忿,換句話(huà)說(shuō)牛郑,我們可以檢測(cè)到 getStringList()方法返回的是 List 而不僅僅只是一個(gè) List。如下例:
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
這段代碼會(huì)打印出 “typeArgClass = java.lang.String”敬鬓,Type[]數(shù)組typeArguments 只有一個(gè)結(jié)果 – 一個(gè)代表 java.lang.String 的 Class 類(lèi)的實(shí)例淹朋。Class 類(lèi)實(shí)現(xiàn)了 Type 接口。
泛型方法參數(shù)類(lèi)型
你同樣可以通過(guò)反射來(lái)獲取方法參數(shù)的泛型類(lèi)型钉答,下面這個(gè)例子定義了一個(gè)類(lèi)础芍,這個(gè)類(lèi)中的方法的參數(shù)是一個(gè)被參數(shù)化的 List:
public class MyClass {
protected List<String> stringList = ...;
public void setStringList(List<String> list){
this.stringList = list;
}
}
你可以像這樣來(lái)獲取方法的泛型參數(shù):
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
這段代碼會(huì)打印出”parameterArgType = java.lang.String”。Type[]數(shù)組 parameterArgTypes 只有一個(gè)結(jié)果 – 一個(gè)代表 java.lang.String 的 Class 類(lèi)的實(shí)例数尿。Class 類(lèi)實(shí)現(xiàn)了Type接口仑性。
泛型變量類(lèi)型
同樣可以通過(guò)反射來(lái)訪問(wèn)公有(Public)變量的泛型類(lèi)型,無(wú)論這個(gè)變量是一個(gè)類(lèi)的靜態(tài)成員變量或是實(shí)例成員變量右蹦。你可以在“Java Reflection: Fields”中閱讀到有關(guān)如何獲取 Field 對(duì)象的相關(guān)內(nèi)容诊杆。這是之前的一個(gè)例子,一個(gè)定義了一個(gè)名為 stringList 的成員變量的類(lèi)何陆。
public class MyClass {
public List<String> stringList = ...;
}
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
這段代碼會(huì)打印出”fieldArgClass = java.lang.String”晨汹。Type[]數(shù)組 fieldArgClass 只有一個(gè)結(jié)果 – 一個(gè)代表 java.lang.String 的 Class 類(lèi)的實(shí)例。Class 類(lèi)實(shí)現(xiàn)了 Type 接口贷盲。
Java 動(dòng)態(tài)類(lèi)加載與重載
- 類(lèi)加載器
- 類(lèi)加載體系
- 類(lèi)加載
- 動(dòng)態(tài)類(lèi)加載
- 動(dòng)態(tài)類(lèi)重載
- 自定義類(lèi)重載
- 類(lèi)加載/重載示例
Java 允許你在運(yùn)行期動(dòng)態(tài)加載和重載類(lèi)淘这,但是這個(gè)功能并沒(méi)有像人們希望的那么簡(jiǎn)單直接。這篇文章將闡述在 Java 中如何加載以及重載類(lèi)巩剖。 你可能會(huì)質(zhì)疑為什么 Java 動(dòng)態(tài)類(lèi)加載特性是 Java 反射機(jī)制的一部分而不是 Java 核心平臺(tái)的一部分铝穷。不管怎樣,這篇文章被放到了 Java 反射系列里面而且也沒(méi)有更好的系列來(lái)包含它了佳魔。
類(lèi)加載器
所有 Java 應(yīng)用中的類(lèi)都是被 java.lang.ClassLoader 類(lèi)的一系列子類(lèi)加載的氧骤。因此要想動(dòng)態(tài)加載類(lèi)的話(huà)也必須使用 java.lang.ClassLoader 的子類(lèi)。
一個(gè)類(lèi)一旦被加載時(shí)吃引,這個(gè)類(lèi)引用的所有類(lèi)也同時(shí)會(huì)被加載筹陵。類(lèi)加載過(guò)程是一個(gè)遞歸的模式,所有相關(guān)的類(lèi)都會(huì)被加載镊尺。但并不一定是一個(gè)應(yīng)用里面所有類(lèi)都會(huì)被加載朦佩,與這個(gè)被加載類(lèi)的引用鏈無(wú)關(guān)的類(lèi)是不會(huì)被加載的,直到有引用關(guān)系的時(shí)候它們才會(huì)被加載庐氮。
類(lèi)加載體系
在 Java 中類(lèi)加載是一個(gè)有序的體系语稠。當(dāng)你新創(chuàng)建一個(gè)標(biāo)準(zhǔn)的 Java 類(lèi)加載器時(shí)你必須提供它的父加載器。當(dāng)一個(gè)類(lèi)加載器被調(diào)用來(lái)加載一個(gè)類(lèi)的時(shí)候,首先會(huì)調(diào)用這個(gè)加載器的父加載器來(lái)加載仙畦。如果父加載器無(wú)法找到這個(gè)類(lèi)输涕,這時(shí)候這個(gè)加載器才會(huì)嘗試去加載這個(gè)類(lèi)。
類(lèi)加載
類(lèi)加載器加載類(lèi)的順序如下: 1慨畸、檢查這個(gè)類(lèi)是否已經(jīng)被加載莱坎。 2、如果沒(méi)有被加載寸士,則首先調(diào)用父加載器加載檐什。 3、如果父加載器不能加載這個(gè)類(lèi)弱卡,則嘗試加載這個(gè)類(lèi)乃正。
當(dāng)你實(shí)現(xiàn)一個(gè)有重載類(lèi)功能的類(lèi)加載器,它的順序與上述會(huì)有些不同婶博。類(lèi)重載不會(huì)請(qǐng)求的他的父加載器來(lái)進(jìn)行加載瓮具。在后面的段落會(huì)進(jìn)行講解。
動(dòng)態(tài)類(lèi)加載
動(dòng)態(tài)加載一個(gè)類(lèi)十分簡(jiǎn)單凡人。你要做的就是獲取一個(gè)類(lèi)加載器然后調(diào)用它的 loadClass()方法名党。下面是個(gè)例子:
public static void main(String[] args){
ClassLoader classLoader = MainClass.class.getClassLoader();
try {
Class aClass = classLoader.loadClass("com.enoch.MyClass");
System.out.println("aClass.getName() = " + aClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
動(dòng)態(tài)類(lèi)重載
動(dòng)態(tài)類(lèi)重載有一點(diǎn)復(fù)雜。Java 內(nèi)置的類(lèi)加載器在加載一個(gè)類(lèi)之前會(huì)檢查它是否已經(jīng)被加載划栓。因此重載一個(gè)類(lèi)是無(wú)法使用 Java 內(nèi)置的類(lèi)加載器的,如果想要重載一個(gè)類(lèi)你需要手動(dòng)繼承 ClassLoader条获。
在你定制 ClassLoader 的子類(lèi)之后忠荞,你還有一些事需要做。所有被加載的類(lèi)都需要被鏈接帅掘。這個(gè)過(guò)程是通過(guò) ClassLoader.resolve()方法來(lái)完成的委煤。由于這是一個(gè) final 方法,因此這個(gè)方法在 ClassLoader 的子類(lèi)中是無(wú)法被重寫(xiě)的修档。resolve()方法是不會(huì)允許給定的 ClassLoader 實(shí)例鏈接一個(gè)類(lèi)兩次碧绞。所以每當(dāng)你想要重載一個(gè)類(lèi)的時(shí)候你都需要使用一個(gè)新的 ClassLoader 的子類(lèi)。你在設(shè)計(jì)類(lèi)重載功能的時(shí)候這是必要的條件吱窝。
自定義類(lèi)重載
在前面已經(jīng)說(shuō)過(guò)你不能使用已經(jīng)加載過(guò)類(lèi)的類(lèi)加載器來(lái)重載一個(gè)類(lèi)讥邻。因此你需要其他的 ClassLoader 實(shí)例來(lái)重載這個(gè)類(lèi)。但是這又帶來(lái)了一些新的挑戰(zhàn)院峡。
所有被加載到 Java 應(yīng)用中的類(lèi)都以類(lèi)的全名(包名 + 類(lèi)名)作為一個(gè)唯一標(biāo)識(shí)來(lái)讓 ClassLoader 實(shí)例來(lái)加載兴使。這意味著,類(lèi) MyObject 被類(lèi)加載器 A 加載照激,如果類(lèi)加載器 B 又加載了 MyObject 類(lèi)发魄,那么兩個(gè)加載器加載出來(lái)的類(lèi)是不同的。看看下面的代碼:
MyClass object = (MyClass)
myClassReloadingFactory.newInstance("com.enoch.MyClass");
MyObject 類(lèi)在上面那段代碼中被引用励幼,它的變量名是 object汰寓。這就導(dǎo)致了 MyObject 這個(gè)類(lèi)會(huì)被這段代碼所在類(lèi)的類(lèi)加載器所加載。
如果 myClassReloadingFactory 工廠對(duì)象使用不同的類(lèi)加載器重載 MyClass 類(lèi)苹粟,你不能把重載的 MyClass t類(lèi)的實(shí)例轉(zhuǎn)換(cast)到類(lèi)型為 MyClass 的對(duì)象變量有滑。一旦 MyClass 類(lèi)分別被兩個(gè)類(lèi)加載器加載,那么它就會(huì)被認(rèn)為是兩個(gè)不同的類(lèi)六水,盡管它們的類(lèi)的全名是完全一樣的俺孙。你如果嘗試把這兩個(gè)類(lèi)的實(shí)例進(jìn)行轉(zhuǎn)換就會(huì)報(bào) ClassCastException。 你可以解決這個(gè)限制掷贾,不過(guò)你需要從以下兩個(gè)方面修改你的代碼: 1睛榄、標(biāo)記這個(gè)變量類(lèi)型為一個(gè)接口,然后只重載這個(gè)接口的實(shí)現(xiàn)類(lèi)想帅。 2场靴、標(biāo)記這個(gè)變量類(lèi)型為一個(gè)超類(lèi),然后只重載這個(gè)超類(lèi)的子類(lèi)港准。
請(qǐng)看下面這兩個(gè)例子:
yObjectInterface object = (MyObjectInterface)
myClassReloadingFactory.newInstance("com.enoch.MyClass");
MyObjectSuperclass object = (MyObjectSuperclass)
myClassReloadingFactory.newInstance("com.enoch.MyClass");
只要保證變量的類(lèi)型是超類(lèi)或者接口旨剥,這兩個(gè)方法就可以正常運(yùn)行,當(dāng)它們的子類(lèi)或是實(shí)現(xiàn)類(lèi)被重載的時(shí)候超類(lèi)跟接口是不會(huì)被重載的浅缸。
為了保證這種方式可以運(yùn)行你需要手動(dòng)實(shí)現(xiàn)類(lèi)加載器然后使得這些接口或超類(lèi)可以被它的父加載器加載轨帜。當(dāng)你的類(lèi)加載器加載 MyObject 類(lèi)時(shí),超類(lèi) MyObjectSuperclass 或者接口 MyObjectSuperclass 也會(huì)被加載衩椒,因?yàn)樗鼈兪?MyObject 的依賴(lài)蚌父。你的類(lèi)加載器必須要代理這些類(lèi)的加載到同一個(gè)類(lèi)加載器,這個(gè)類(lèi)加載器加載這個(gè)包括接口或者超類(lèi)的類(lèi)毛萌。
Java 動(dòng)態(tài)代理
- 創(chuàng)建代理
- InvocationHandler 接口
常見(jiàn)用例
- 數(shù)據(jù)庫(kù)連接以及事物管理
- 單元測(cè)試中的動(dòng)態(tài) Mock 對(duì)象
- 自定義工廠與依賴(lài)注入(DI)容器之間的適配器
- 類(lèi)似 AOP 的方法攔截器
利用Java反射機(jī)制你可以在運(yùn)行期動(dòng)態(tài)的創(chuàng)建接口的實(shí)現(xiàn)苟弛。 java.lang.reflect.Proxy 類(lèi)就可以實(shí)現(xiàn)這一功能。這個(gè)類(lèi)的名字(譯者注:Proxy 意思為代理)就是為什么把動(dòng)態(tài)接口實(shí)現(xiàn)叫做動(dòng)態(tài)代理阁将。動(dòng)態(tài)的代理的用途十分廣泛膏秫,比如數(shù)據(jù)庫(kù)連接和事物管理(transaction management)還有單元測(cè)試時(shí)用到的動(dòng)態(tài) mock 對(duì)象以及 AOP 中的方法攔截功能等等都使用到了動(dòng)態(tài)代理。
創(chuàng)建代理
你可以通過(guò)使用 Proxy.newProxyInstance()方法創(chuàng)建動(dòng)態(tài)代理做盅。 newProxyInstance()方法有三個(gè)參數(shù): 1缤削、類(lèi)加載器(ClassLoader)用來(lái)加載動(dòng)態(tài)代理類(lèi)。 2吹榴、一個(gè)要實(shí)現(xiàn)的接口的數(shù)組僻他。 3、一個(gè) InvocationHandler 把所有方法的調(diào)用都轉(zhuǎn)到代理上腊尚。 如下例:
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
在執(zhí)行完這段代碼之后吨拗,變量 proxy 包含一個(gè) MyInterface 接口的的動(dòng)態(tài)實(shí)現(xiàn)。所有對(duì) proxy 的調(diào)用都被轉(zhuǎn)向到實(shí)現(xiàn)了 InvocationHandler 接口的 handler 上。有關(guān) InvocationHandler 的內(nèi)容會(huì)在下一段介紹劝篷。
InvocationHandler 接口
在前面提到了當(dāng)你調(diào)用 Proxy.newProxyInstance()方法時(shí)哨鸭,你必須要傳入一個(gè) InvocationHandler 接口的實(shí)現(xiàn)。所有對(duì)動(dòng)態(tài)代理對(duì)象的方法調(diào)用都會(huì)被轉(zhuǎn)向到 InvocationHandler 接口的實(shí)現(xiàn)上娇妓,下面是 InvocationHandler 接口的定義:
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
下面是它的實(shí)現(xiàn)類(lèi)的定義:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}
傳入 invoke()方法中的 proxy 參數(shù)是實(shí)現(xiàn)要代理接口的動(dòng)態(tài)代理對(duì)象像鸡。通常你是不需要他的。
invoke()方法中的 Method 對(duì)象參數(shù)代表了被動(dòng)態(tài)代理的接口中要調(diào)用的方法哈恰,從這個(gè) method 對(duì)象中你可以獲取到這個(gè)方法名字只估,方法的參數(shù),參數(shù)類(lèi)型等等信息着绷。關(guān)于這部分內(nèi)容可以查閱之前有關(guān) Method 的文章蛔钙。
Object 數(shù)組參數(shù)包含了被動(dòng)態(tài)代理的方法需要的方法參數(shù)。注意:原生數(shù)據(jù)類(lèi)型(如int荠医,long等等)方法參數(shù)傳入等價(jià)的包裝對(duì)象(如Integer吁脱, Long等等)。
數(shù)據(jù)庫(kù)連接以及事物管理
Spring 框架中有一個(gè)事物代理可以讓你提交/回滾一個(gè)事物彬向。它的具體原理在 Advanced Connection and Transaction Demarcation and Propagation一文中有詳細(xì)描述兼贡,所以在這里我就簡(jiǎn)短的描述一下,方法調(diào)用序列如下:
web controller --> proxy.execute(...);
proxy --> connection.setAutoCommit(false);
proxy --> realAction.execute();
realAction does database work
proxy --> connection.commit();
自定義工廠與依賴(lài)注入(DI)容器之間的適配器
依賴(lài)注入容器 Butterfly Container 有一個(gè)非常強(qiáng)大的特性可以讓你把整個(gè)容器注入到這個(gè)容器生成的 bean 中娃胆。但是遍希,如果你不想依賴(lài)這個(gè)容器的接口,這個(gè)容器可以適配你自己定義的工廠接口里烦。你僅僅需要這個(gè)接口而不是接口的實(shí)現(xiàn)凿蒜,這樣這個(gè)工廠接口和你的類(lèi)看起來(lái)就像這樣:
public interface IMyFactory {
Bean bean1();
Person person();
...
}
public class MyAction{
protected IMyFactory myFactory= null;
public MyAction(IMyFactory factory){
this.myFactory = factory;
}
public void execute(){
Bean bean = this.myFactory.bean();
Person person = this.myFactory.person();
}
}
當(dāng) MyAction 類(lèi)調(diào)用通過(guò)容器注入到構(gòu)造方法中的 IMyFactory 實(shí)例的方法時(shí),這個(gè)方法調(diào)用實(shí)際先調(diào)用了 IContainer.instance()方法招驴,這個(gè)方法可以讓你從容器中獲取實(shí)例篙程。這樣這個(gè)對(duì)象可以把 Butterfly Container 容器在運(yùn)行期當(dāng)成一個(gè)工廠使用枷畏,比起在創(chuàng)建這個(gè)類(lèi)的時(shí)候進(jìn)行注入别厘,這種方式顯然更好。而且這種方法沒(méi)有依賴(lài)到 Butterfly Container 中的任何接口拥诡。
類(lèi)似 AOP 的方法攔截器
Spring 框架可以攔截指定 bean 的方法調(diào)用触趴,你只需提供這個(gè) bean 繼承的接口。Spring 使用動(dòng)態(tài)代理來(lái)包裝 bean渴肉。所有對(duì) bean 中方法的調(diào)用都會(huì)被代理攔截冗懦。代理可以判斷在調(diào)用實(shí)際方法之前是否需要調(diào)用其他方法或者調(diào)用其他對(duì)象的方法,還可以在 bean 的方法調(diào)用完畢之后再調(diào)用其他的代理方法仇祭。