Java-Java反射
- Java反射概述
- 示例
- Code
- 分析
- 類裝載器ClassLoader
- 工作機(jī)制
- ClassLoader分類
- 全盤負(fù)責(zé)委托機(jī)制
- 重要方法
- loadClassString name
- defineClassString name byte b int off int len
- findSystemClassString name
- findLoadedClassString name
- getParent
- 總結(jié)
- Java反射機(jī)制
- 三個主要的反射類
- Constructor
- Method
- Field
- 三個主要的反射類
Java反射概述
Java語言允許通過程序化的方式間接對Class進(jìn)行操作。
Class文件由類裝載器裝載后,在JVM中形成一份描述Class結(jié)構(gòu)的元信息對象,通過該元對象可以獲知Class的結(jié)構(gòu)信息,如構(gòu)造函數(shù)、屬性和方法等。
Java允許用戶借由這個與Class相關(guān)的元信息對象間接調(diào)用Class對象的功能, 這就為使用程序化方式操作Class對象開辟了途徑。
使用反射不同于常規(guī)的Java編程,其中它與 元數(shù)據(jù)–描述其它數(shù)據(jù)的數(shù)據(jù)協(xié)作。Java語言反射接入的特殊類型的原數(shù)據(jù)是JVM中類和對象的描述。
Java反射機(jī)制可以讓我們在編譯期(Compile Time)之外的運(yùn)行期(Runtime)獲得任何一個類的字節(jié)碼。包括接口、變量、方法等信息。還可以讓我們在運(yùn)行期實(shí)例化對象,通過調(diào)用get/set方法獲取變量的值。
示例
Code
我們將用下面這個例子來了解Java反射機(jī)制。
package com.xgj.master.ioc.reflect;public class Car {private String brand ;private String color;private int speed;public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public int getSpeed() {return speed;}public void setSpeed(int speed) {this.speed = speed;}/*** * @Title:Car* @Description:默認(rèn)構(gòu)造函數(shù)*/public Car(){}/*** * @Title:Car* @Description:帶參構(gòu)造函數(shù)* @param brand* @param color* @param speed*/public Car(String brand ,String color ,int speed){this.brand = brand;this.color = color;this.speed = speed;}public void introduceCar(){System.out.println("Car : " + this.brand + " , " + this.color + " , " + this.speed);}}通常情況下,我們實(shí)例化類,調(diào)用類中的方法如下:
Car car = new Car("BMW","Black",180);car.introduceCar();輸出: Car : BMW , Black , 180
這是使用傳統(tǒng)的方式來直接調(diào)用目標(biāo)類的方法。
如果使用Java的反射機(jī)制 該如何控制目標(biāo)類呢?
來看代碼
package com.xgj.master.ioc.reflect;import java.lang.reflect.Constructor; import java.lang.reflect.Method;public class ReflectTest {public static Car initCarByDefaultConstrut() throws Exception {// (1)通過類裝載器獲取Car類對象ClassLoader loader = Thread.currentThread().getContextClassLoader();Class claz = loader.loadClass("com.xgj.master.ioc.reflect.Car");// (2)獲取類的默認(rèn)構(gòu)造函數(shù),并通過它實(shí)例化CarConstructor constructor = claz.getDeclaredConstructor(null);Car car = (Car) constructor.newInstance();// (3)通過反射方法設(shè)置屬性Method method = claz.getMethod("setBrand", String.class);method.invoke(car, "BMW");Method method2 = claz.getMethod("setColor", String.class);method2.invoke(car, "black");Method method3 = claz.getMethod("setSpeed", int.class);method3.invoke(car, 100);return car;}public static void main(String[] args) throws Exception {initCarByDefaultConstrut().introduceCar();}}運(yùn)行結(jié)果: Car : BMW , black , 100
分析
我們完全可以通過編程方式調(diào)用Class的各項(xiàng)功能,與通過構(gòu)造函數(shù)和方法直接調(diào)用類的功能的效果是一致的,只不過是間接調(diào)用罷了。
幾個重要的反射類
- ClassLoader
- Class
- Constructor
- Method .
通過這些反射類我們就可以間接的調(diào)用目標(biāo)Class的各項(xiàng)功能。
在(1)處,我們獲取當(dāng)前線程的ClassLoader, 然后通過指全限定類名com.xgj.master.ioc.reflect.Car 來裝載Car類對應(yīng)的反射實(shí)例。
在(2)處,我們通過Car的反射類對象獲取Car的默認(rèn)構(gòu)造函數(shù)對象,通過構(gòu)造函數(shù)對象的newInstance()方法實(shí)例化Car對象,等同于 new Car()
在(3)處,我們又通過Car的反射類對象的getMethod(String methodName, Class paramsClass)獲取屬性的Setter方法對象,其中第一個參數(shù)是目標(biāo)Class的方法名,第二個參數(shù)是方法入?yún)⒌膶ο箢愋汀?
在獲取到方法反射對象后,就可以通過invoke(Object ob, Object param)方法調(diào)用目標(biāo)類的方法了。 該方法的第一個禪師是操作的目標(biāo)類對象實(shí)例,第二個參數(shù)目標(biāo)方法的入?yún)ⅰ?/font>
類裝載器ClassLoader
工作機(jī)制
類裝載器就是尋找類的字節(jié)碼文件并構(gòu)造類在JVM內(nèi)部表示對象的組件。
類裝載器把一個類裝入JVM中,步驟如下:
其中第二步操作包括:
(1). 檢驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性
(2). 準(zhǔn)備:給類的靜態(tài)變量分配存儲空間
(3). 解析:將符號引用轉(zhuǎn)換為直接引用
類裝載工作由ClassLoader及其子類負(fù)責(zé),負(fù)責(zé)在運(yùn)行時查找和裝入Class直接碼文件。
ClassLoader分類
JVM運(yùn)行期間會產(chǎn)生3個ClassLoader
- 根裝載器
- ExtClassLoader(擴(kuò)展類裝載器)
AppClassLoader(應(yīng)用類裝載器)
其中 ExtClassLoader和AppClassLoader 是 ClassLoader的子類
根裝載器不是ClassLoader的子類,它是C++編寫。根裝載器負(fù)責(zé)裝載JRE的核心類庫,比如JRE目標(biāo)下的JAR
ExtClassLoader負(fù)責(zé)裝載JRE擴(kuò)展目錄ext中的JAR包
- AppClassLoader負(fù)責(zé)裝載應(yīng)用Classpath路徑下的類包
三者關(guān)系: 根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。 默認(rèn)情況下,使用AppClassLoader來裝載應(yīng)用程序的類。
驗(yàn)證下:
package com.xgj.master.ioc.classloader;public class ClassLoaderTest {public static void main(String[] args) {// TODO Auto-generated method stubClassLoader classLoader = Thread.currentThread().getContextClassLoader();System.out.println("current loader:" + classLoader);System.out.println("parent laoder:" + classLoader.getParent());System.out.println("grandparent laoder:" + classLoader.getParent().getParent());}}輸出:
current loader:sun.misc.Launcher$AppClassLoader@8391b0c parent laoder:sun.misc.Launcher$ExtClassLoader@5d1eb50b grandparent laoder:null根裝載器在Java中無法獲取到它的句柄,因此返回null .
全盤負(fù)責(zé)委托機(jī)制
JVM裝載類時使用“全盤負(fù)責(zé)委托機(jī)制”。
全盤負(fù)責(zé):是指當(dāng)一個ClassLoader裝載一個類時,除非顯示地使用另外一個ClassLoader,該類所依賴以及引用的類也由這個ClassLoader載入。
委托機(jī)制:是指先委托父類裝載器尋找目標(biāo)類,只有在找不到的情況下才從自己的類路徑中查找并裝載目標(biāo)類。
這一點(diǎn)是從安全角度考慮,舉個例子,比如有人惡意編寫了一個基礎(chǔ)類如java.lang.String 并裝載到JVM中,如果沒有委托機(jī)制,jvm就會讀取了這個惡意基礎(chǔ)類,全盤負(fù)責(zé)委托機(jī)制保證了java.lang.String永遠(yuǎn)由根裝載器來裝載,避免了安全隱患的發(fā)生。
如何查看JVM從哪個JAR包中加載指定類呢?
請看 Java-查看JVM從哪個JAR包中加載指定類
重要方法
loadClass(String name)
public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);} protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}name參數(shù)指定類裝載器需要裝載的類的名字,必須使用全限定類名。
該方法有一個重載方法 loadClass(String name ,boolean resolve) .resolve參數(shù)告訴類裝載器是否需要解析該類, 如果JVM只需要知道該類是否存在或者找出該類的超類,那么就不需要進(jìn)行解析。
defineClass(String name, byte[] b, int off, int len)
將類文件的字節(jié)數(shù)組轉(zhuǎn)換成JVM內(nèi)部的java.lang.Class對象。 參數(shù)name為字節(jié)數(shù)組對應(yīng)的全限定類名。
findSystemClass(String name)
protected final Class<?> findSystemClass(String name)throws ClassNotFoundException{ClassLoader system = getSystemClassLoader();if (system == null) {if (!checkName(name))throw new ClassNotFoundException(name);Class cls = findBootstrapClass(name);if (cls == null) {throw new ClassNotFoundException(name);}return cls;}return system.loadClass(name);}從本地文件系統(tǒng)裝載Class文件,不存在則拋出ClassNotFoundException。 該方法為AJVM默認(rèn)使用的裝載機(jī)制。
findLoadedClass(String name)
調(diào)用該方法查看ClassLoader是否已經(jīng)載入某個類,如果載入,返回java.lang.Class對象,否則返回null.
如果強(qiáng)行裝載已經(jīng)存在的類,將拋出鏈接錯誤。
getParent()
@CallerSensitivepublic final ClassLoader getParent() {if (parent == null)return null;SecurityManager sm = System.getSecurityManager();if (sm != null) {checkClassLoaderPermission(parent, Reflection.getCallerClass());}return parent;}獲取類裝載器的父裝載器。 除了根裝載器外,所有的類裝載器都有且有且只有一個父裝載器。 ExtClassLoader的父裝載器是根裝載器。 因?yàn)楦b載器非Java語言編寫,因此無法獲得,返回null.
總結(jié)
除了JVM默認(rèn)的3個ClassLoader外,用戶也可以編寫自己的第三方類裝載器,以實(shí)現(xiàn)一些特殊的需求。
類文件被裝載并解析后,在JVM內(nèi)將擁有一個對應(yīng)的java.lang.Class類描述對象,該類的實(shí)例都擁有指向這個類描述對象的引用,而類描述對象又擁有指向關(guān)聯(lián)ClassLoader的引用。
如下圖《類實(shí)例、類描述對象及裝載器的關(guān)系》所示
每個類在JVM中都有一個對應(yīng)的java.lang.Class對象。它提供了類結(jié)構(gòu)信息的描述。
Class沒有public的構(gòu)造方法,Class對象是在裝載類時由JVM通過調(diào)用類裝載器中的defineClass()方法自動構(gòu)造的。
Java反射機(jī)制
Class反射對象描述類定義結(jié)構(gòu),可以從Class對象中獲取構(gòu)造函數(shù)、成員變量、方法類等類元素的反射對象,并以編程的方式通過這些反射對象對目標(biāo)類對象進(jìn)行操作。
這些反射對象定義在java.lang.reflect包中。
三個主要的反射類
Constructor
類的構(gòu)造函數(shù)反射類。
通過Class#getConstructors()方法可以獲取類的所有構(gòu)造函數(shù)反射對象數(shù)組。
在Java5.0中,還可以通過getConstructor(Class...parameterTypes)獲取擁有特定入?yún)⒌臉?gòu)造函數(shù)反射對象。
Constructor的一個主要方法是newInstance(Object[] initargs),通過該方法可以創(chuàng)建一個對象類的實(shí)例。相當(dāng)于new關(guān)鍵字。
在Java5.0中,該方法演化為更為靈活的形式:newInstance(Object...initargs)
Method
類方法的反射類。
通過Class#getDeclaredMethods()方法可以獲取類的所有方法反射類對象數(shù)組Method[].
在Java5.0中,可以通過getDeclaredMethod(String name,Class...parameterTypes)獲取特定簽名的方法。其中name為方法名,Class...為方法入?yún)㈩愋土斜怼?/font>
Method最主要的方法是invoke(Object obj , Object[] args) , 其中obj表示操作的目標(biāo)對象;args為方法入?yún)ⅰ?/font>
在Java5.0中,該方法調(diào)整為invoke(Object obj, Object...args) .
此外,其他比較常用的方法:
- Class getReturnType():獲取方法的返回值烈性
- Class[] getParamaterTypes():獲取方法的入?yún)㈩愋蛿?shù)組
- Class[] getExceptionTypes() 獲取該方法的異常類型數(shù)組
- Annotation[][] getParameterAnnotations() 獲取方法的注解洗洗,是Java5.0中新增的方法。
Field
類的成員變量的反射類。
通過Class#getDeclaredFields()方法可以獲取類的成員變量反射對象數(shù)組,
通過Class#getDeclaredField(String name)則可以獲取某個特定名稱的成員變量反射對象。
Field類的主要方法是set(Object obj , Object value) 其中obj表示操作的目標(biāo)對象,通過value為目標(biāo)對象的成員變量設(shè)置值。
如果成員變量為基礎(chǔ)類型,則可以使用Field類中提供的帶類型名的值設(shè)置方法,比如setBoolean(Object obj , Object value)、setInt(Object obj , Object value)等。
此外Java還未包提供了Package反射類,在Java5.0中還未注解提供了AnnotatedElement反射類。
對于private或者procted成員變量和方法,只要JVM的安全機(jī)制允許,也可以通過反射調(diào)用。比如:
package com.xgj.master.ioc.reflect;public class PrivateCar {private String brand;protected void introduce() {System.out.println("brand:" + brand);}} package com.xgj.master.ioc.reflect;import java.lang.reflect.Field; import java.lang.reflect.Method;public class PrivateCarTest {public static void main(String[] args) throws Exception {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();Class claz = classLoader.loadClass("com.xgj.master.ioc.reflect.PrivateCar");PrivateCar pcar = (PrivateCar) claz.newInstance();Field field = claz.getDeclaredField("brand");// 取消Java語言訪問檢查以便訪問private變量field.setAccessible(true);field.set(pcar, "BMW");Method method = claz.getDeclaredMethod("introduce", (Class[]) null);// 取消Java語言訪問檢查以便訪問protected方法method.setAccessible(true);method.invoke(pcar, (Object[]) null);}}在訪問private 或者 protected成員變量和方法時,必須通過setAccessible(boolean access)方法取消Java語言檢查,否則將拋出IllegalAccessException. 如果JVM的安全管理器(SecurityManager)設(shè)置了相應(yīng)的安全機(jī)制,那么調(diào)用該方法會拋出SecurityException
總結(jié)
以上是生活随笔為你收集整理的Java-Java反射的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring-IoC容器
- 下一篇: Java-查看JVM从哪个JAR包中加载