1. 程式人生 > >Java中的反射和類裝載器

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內部表示的物件元件,大致經過如下步驟:

  1. 裝載:查詢和匯入Class檔案
  2. 連結:執行教研、準備和解析步驟,解析步驟可選
    • 校驗:檢查載入Class檔案資料的正確性
    • 準備:給類的靜態變數分配儲存空間
    • 解析:講符號引用轉換成直接引用
  3. 初始化:對類的靜態變數、靜態程式碼塊執行初始化工作。

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。