Java中的反射和類裝載器
首先通過一個簡單的例子看一下Java中的反射,如下,是一個Car類:
Car.java
public class Car {
private String brand;
private String color;
private int maxSpeed;
public Car(){System.out.println("init car!!");}
public Car(String brand,String color,int maxSpeed){
this.brand = brand;
this.color = color;
this .maxSpeed = maxSpeed;
}
public void introduce() {
System.out.println("brand:"+brand+";color:"+color+";maxSpeed:"+maxSpeed);
}
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 getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
通常我們可以使用如下方式建立Car的例項:
Car car = new Car();
car.setBrand("紅旗" );
或:
Car car = new Car("紅旗", "黑色");
我們也可以通過Java中的反射機制以更加通用的方式間接的操作目標類:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectTest {
public static Car initByDefaultConst() throws Throwable
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass("com.baobaotao.reflect.Car");
// Class clazz = Class.forName("com.baobaotao.beans.Car1");
Constructor cons = clazz.getDeclaredConstructor((Class[])null);
Car car = (Car)cons.newInstance();
Method setBrand = clazz.getMethod("setBrand",String.class);
setBrand.invoke(car,"紅旗CA72");
Method setColor = clazz.getMethod("setColor",String.class);
setColor.invoke(car,"黑色");
Method setMaxSpeed = clazz.getMethod("setMaxSpeed",int.class);
setMaxSpeed.invoke(car,200);
return car;
}
public static Car initByParamConst() throws Throwable{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass("com.baobaotao.reflect.Car");
Constructor cons = clazz.getDeclaredConstructor(new Class[]{String.class,String.class,int.class});
Car car = (Car)cons.newInstance(new Object[]{"吉利TOPMIX","綠色",120});
return car;
}
public static void main(String[] args) throws Throwable {
Car car1 = initByDefaultConst();
Car car2 = initByParamConst();
car1.introduce();
car2.introduce();
}
}
如上,首先獲取到當前執行緒的ClassLoader,然後通過指定全限定類裝載Car類對應的反射例項,然後通過反射類物件獲取Car的建構函式物件cons,然後通過建構函式物件的newInstance()方法例項化Car物件,後面通過Car的反射類物件的getMethod(String methodName, Class paramClass)方法獲取屬性的Setter方法物件,然後通過invoke(Object obj, Object param)方法呼叫目標類的方法,通過如上方法操作目標類的元資訊。
反射機制的通用性在於,我們可以將這些資訊以配置檔案的方式提供,然後編寫通用的程式碼來進行例項化操作。一些框架就使用到了這些技術。
接下來看一下類裝載器的工作機制:
類裝載器就是尋找類的位元組碼檔案並構造出類在JVM內部表示的物件元件,大致經過如下步驟:
- 裝載:查詢和匯入Class檔案
- 連結:執行教研、準備和解析步驟,解析步驟可選
- 校驗:檢查載入Class檔案資料的正確性
- 準備:給類的靜態變數分配儲存空間
- 解析:講符號引用轉換成直接引用
- 初始化:對類的靜態變數、靜態程式碼塊執行初始化工作。
JVM在執行時會產生三個ClassLoader:根裝載器,ExtClassLoader(擴充套件類裝載器)和AppClassLoader(系統類裝載器)。其中,根裝載器不是ClassLoader的子類,它使用C++編寫,因此在Java中看不到。
根裝載器負責JRE的核心類庫,ExtClassLoader和AppClassLoader都是ClassLoader的子類。其中ExtClassLoader負責裝載JRE擴充套件目錄的ext中的JAR類包;AppClassLoader負責裝載Classpath路徑下的類包。
這三個類裝載器之間存在父子層級關係,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。預設使用AppClassLoader裝載應用程式的類。
在JVM中出於安全考慮,裝載類時使用“全盤負責委託機制”,委託機制是指先委託父裝載器尋找目標類,只有在找不到的情況下才從自己的類路徑中查詢並裝載目標類。
即就是優先查詢系統中已有的類,找不到才會使用使用者自定義的類。
類例項、類描述物件以及類裝載器之間的關係如下:
如上圖,當類檔案被裝載並解析後,在JVM內將擁有一個對應的java.lang.Class類描述物件,該類的例項都擁有指向這個類描述物件的引用,而類描述物件又擁有指向關聯ClassLoader的引用。
通過反射機制還可以訪問private和protected的成員變數和方法(當JVM安全機制允許),如下:
PrivateCar.java
public class PrivateCar {
private String color;
protected void drive(){
System.out.println("drive private car! the color is:"+color);
}
}
color變數和drive()方法都是私有的,通過類例項變數無法在外部訪問,通過反射機制可以繞開這個限制,如下:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class PrivateCarReflect {
public static void main(String[] args) throws Throwable{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass("com.baobaotao.reflect.PrivateCar");
PrivateCar pcar = (PrivateCar)clazz.newInstance();
//Field:類的成員變數的反射類
Field colorFld = clazz.getDeclaredField("color");
//取消Java語言訪問檢查以訪問private變數
colorFld.setAccessible(true);
colorFld.set(pcar,"紅色");
Method driveMtd = clazz.getDeclaredMethod("drive",(Class[])null);
//Method driveMtd = clazz.getDeclaredMethod("drive"); JDK5.0下使用
driveMtd.setAccessible(true);
driveMtd.invoke(pcar,(Object[])null);
}
}
如上,在通過反射機制訪問private、protected成員變數和方法時必須通過setAccessible(boolean access)方法取消Java語言檢查,否則將丟擲IllegalAccessException。如果JVM的安全管理器設定了相應的安全機制,呼叫該方法講排除SecurityException。