基礎篇:深入解析JAVA反射機制

2020-09-28 12:01:37

反射的概念

  • java的放射機制:在程式執行時,程式有能力獲取一個類的所有方法和屬性;並且對於任意一個物件,可以呼叫它的任意方法或者獲取其屬性
  • 通俗解析:java檔案需要編譯成.class檔案才能被jvm載入使用,物件的.class資料在jvm裡就是Class<T>;我們如果能拿到這個Class<T>物件,就能獲取該Class<T>對應的物件型別,及在該型別宣告的方法和屬性值;還可以根據Class<T>建立相應的型別物件,通過Field,Method反過來操作物件
  • java相關類介紹
類名描述
Class<T>代表類的實體,在執行的Java應用程式中表示類或者介面
Field類的成員變數(成員變數也稱為類的屬性)
Method類的方法
Constructor<T>類的構造方法

獲取Class的三種方法及根據Class建立物件的兩種方法

  • 1通過已知的型別獲取class
// 根據Example 獲取Class =》Example.class
public Class<Example> getExample(){
    Class<Example> clazz = Example.class;
    return clazz;
}
  • 2通過範例物件獲取class
public Class<Example> getExampleByInstance(){
    Example example = new Example();
    // getClass是Object類裡面的方法;《?》 是萬用字元
    Class<?> clazz = example.getClass();
    return (Class<Example>)clazz;
}
  • 3通過Class.forName獲取全路徑指定類名的class
/** forName0 本地方法,C++實現,jvm呼叫
 *	1 className 是個類名  2 initialize 是否延遲載入  3 loader 載入器
 */
private static native Class<?> forName0(String className, boolean initialize,
				ClassLoader loader, Class<?> caller) throws ClassNotFoundException;

public static Class<?> forName(String className) throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
// 兩個forName方法最終都會呼叫forName0方法去載入class   
public static Class<?> forName(String name,
		boolean initialize, ClassLoader loader) throws ClassNotFoundException {
        ....
        return forName0(name, initialize, loader, caller);
    }
// 範例:通過java.lang.Integer 
public Class<Integer> getInteger()throws ClassNotFoundException{
    Class<?> clazz = Class.forName("java.lang.Integer");
    return (Class<Integer>)clazz;
}

JAVA反射API

  • Class常用操作方法
//獲取所有的構造方法 / private public
public Constructor<?>[] getDeclaredConstructors()
//獲取特定的構造方法 / private public
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)    
//獲取類的父類別
public native Class<? super T> getSuperclass()    
//獲取類實現的介面
private Class<?>[] getInterfaces(boolean cloneArray)  
//獲取在類內定義的內部類或介面
public Class<?>[] getDeclaredClasses()
//獲取所有的方法
public Method[] getDeclaredMethods() throws SecurityException
//根據方法名和引數獲得特定的方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)  
//獲取型別的定義的所有屬性
public Field[] getFields() throws SecurityException
// 根據屬性命名獲得特定的Field
public Field getField(String name) 
  • Method常用的操作方法
//獲得方法的放回型別
public Class<?> getReturnType()   
//獲得方法的傳入引數型別
public Class<?>[] getParameterTypes()   
//obj是範例物件,args是方法,反過來由Method控制物件的方法呼叫
public Object invoke(Object obj, Object... args)
  • Field常用的操作方法
//屬性與obj相等則返回true
public boolean equals(Object obj)
//獲得obj中對應的屬性值
public Object get(Object obj)
//設定obj中對應屬性值
public void set(Object obj, Object value) 
  • Constructor
//根據傳遞的引數建立類的物件:initargs 構造方法引數
public T newInstance(Object... initargs) 
  • 1根據class建立物件
//方式一 clazz.newInstance()
Class<Example> clazz = Example.class;
Example example = clazz.newInstance();
//方式二 先獲取再由Constructor:clazz.getConstructors()/getConstructor(...) 
//再由Constructor.newInstance 方法構造物件
-----------------------------------------
public class Example {
    private int value;
    public Example(){ } // 如果只宣告有參建構函式,clazz.newInstance()會報錯
    public Example(Integer value){  this.value  = value;  }
    static public void main(String[] args) throws Exception{
        Class<Example> clazz = Example.class;
        //根據指定建構函式引數獲取Constructor
        Constructor<Example> constructor = clazz.getConstructor(Integer.class);
        Example example = constructor.newInstance(100);
        System.out.println(example.value);
    }
}    
  • 2由class獲取Field,並操作範例的屬性
public class Example {
    private int value , count;
    static public void main(String[] args) throws Exception{
        Class<Example> clazz = Example.class;
        //獲取所有的屬性,getField只能獲取public的屬性
		Field[] fs = clazz.getDeclaredFields();
        //根據名稱獲取指定 Field
        Field value = clazz.getDeclaredField("value");
        Example example = clazz.newInstance();
        //使用反射機制可以打破封裝性,導致了java物件的屬性不安全  
        value.setAccessible(true); //setAccessible(true)讓private的引數可賦值操作
        //由Field反過去設定example的值
        value.set(example,100);
        System.out.println(example.value);
    }
}
  • 3由class獲取Method,並反射呼叫實體方法
public class Example {
    public static void main(String[] args) throws Exception {
        Class<Example> clazz = Example.class;
        Example example = clazz.newInstance();
        Method[] methods = clazz.getDeclaredMethods();
        //getDeclaredMethod和getMethod是:getMethod只能返回public的方法
        Method method = clazz.getDeclaredMethod("hello", String.class);
        method.setAccessible(true);
        method.invoke(example, "cscw");
    }
    private void hello(String name) { System.out.println(name + " Hello!"); }
}
-----
cscw Hello!

反射機制應用的場景

  • 1 動態拓展:假設有同一組類是實現相同的介面,並且類的載入方式不限制。當我們需要那種具體類實現的功能時,只需載入.class檔案,並獲取對應的Class<T>物件。可以由Class或者Constructor範例化物件instance;根據介面定義,可以獲取Class<T>裡的某一方法Method,並配合instance呼叫功能方法
  • 2 Spring的IOC就是基於反射機制實現
  • 3 JDK的動態代理

反射和JDK動態代理

  • 在Java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler介面。通過這個類和介面可以生成JDK動態代理類或動態代理物件
public interface InvocationHandler {
	//所有方法都會呼叫此代理方法
    Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}	 
public class Proxy implements Serializable...
    //根據interfaces和InvocationHandler生成代理物件
    public static Object newProxyInstance(ClassLoader loader,
    		Class<?>[] interfaces, InvocationHandler h) 
    ...
  • JDK的動態代理由Proxy和InvocationHandler實現;而被代理物件必須實現一個介面。代理物件由Proxy生成,可轉為介面interface的實現類物件OBJ。當呼叫OBJ的方法時,則會觸發InvocationHandler.invoke,引數依次為代理物件Method物件,和方法Method所需的引數。在invoke方法可以加入拓展的邏輯,如紀錄檔記錄操作;並可以在invoke裡利用反射的技術呼叫 被代理物件方法
  • 範例
public class ExampleFactory<T> implements InvocationHandler{
    private T target;
    public T bind(T obj){
        target = obj;
        return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
        			obj.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        //增強邏輯
        System.out.println("log start");
        //反射呼叫被代理物件方法
        Object result = method.invoke(target,objects);
        System.out.println("log end");
        return result;
    }
}
-----------
public interface Face {
    void hello(String name);
}
---------
//被代理物件必須實現一個介面,並由介面方法對方提供功能
public class Example implements Face {
	public void hello(String name) {
        System.out.println(name + " Hello!");
    }
    public static void main(String[] args)  {
    	//ExampleFactory<Face> 相當於一箇中介人
        ExampleFactory<Face> factory = new ExampleFactory<>();
        //example 是代理物件
        Face example = exampleProxy.bind(new Example());
        example.hello("思婷");
    }
}
-----
log start
思婷 Hello!
log end

歡迎指正文中錯誤

關注公眾號,一起交流

參考文章