深刻理解Java反射+動態代理

答:   反射機制的定義: html

是在運行狀態中,對於任意的一個類,都可以知道這個類的全部屬性和方法,對任意一個對象都可以經過反射機制調用一個類的任意方法,這種動態獲取類信息及動態調用類對象方法的功能稱爲java的反射機制。java

反射的做用:數組

一、動態地建立類的實例,將類綁定到現有的對象中,或從現有的對象中獲取類型。網絡

二、應用程序須要在運行時從某個特定的程序集中載入一個特定的類jvm

詳見:java反射機制原理ide

反射 通常使用 Class.forName()方法;函數

動態代理就是實現InvocationHandler 接口;工具

要想理解反射的原理,首先要了解什麼是類型信息。Java讓咱們在運行時識別對象和類的信息,主要有2種方式:一種是傳統的RTTI,它假定咱們在編譯時已經知道了全部的類型信息;另外一種是反射機制,它容許咱們在運行時發現和使用類的信息。post

一、Class對象

  理解RTTI在Java中的工做原理,首先須要知道類型信息在運行時是如何表示的,這是由Class對象來完成的,它包含了與類有關的信息。Class對象就是用來建立全部「常規」對象的,Java使用Class對象來執行RTTI,即便你正在執行的是相似類型轉換這樣的操做。學習

  每一個類都會產生一個對應的Class對象,也就是保存在.class文件。全部類都是在對其第一次使用時,動態加載到JVM的,當程序建立一個對類的靜態成員的引用時,就會加載這個類。Class對象僅在須要的時候纔會加載,static初始化是在類加載時進行的。 

public class TestMain {
    public static void main(String[] args) {
        System.out.println(XYZ.name);
    }
}

class XYZ {
    public static String name = "luoxn28";

    static {
        System.out.println("xyz靜態塊");
    }

    public XYZ() {
        System.out.println("xyz構造了");
    }
}

 

輸出結果爲:

  類加載器首先會檢查這個類的Class對象是否已被加載過,若是還沒有加載,默認的類加載器就會根據類名查找對應的.class文件。

  想在運行時使用類型信息,必須獲取對象(好比類Base對象)的Class對象的引用,使用功能Class.forName(「Base」)能夠實現該目的,或者使用base.class。注意,有一點頗有趣,使用功能」.class」來建立Class對象的引用時,不會自動初始化該Class對象,使用forName()會自動初始化該Class對象。爲了使用類而作的準備工做通常有如下3個步驟:

  • 加載:由類加載器完成,找到對應的字節碼,建立一個Class對象
  • 連接:驗證類中的字節碼,爲靜態域分配空間
  • 初始化:若是該類有超類,則對其初始化,執行靜態初始化器和靜態初始化塊 
public class Base {
    static int num = 1;
    
    static {
        System.out.println("Base " + num);
    }
}
public class Main {
    public static void main(String[] args) {
        // 不會初始化靜態塊
        Class clazz1 = Base.class;
        System.out.println("------");
        // 會初始化
        Class clazz2 = Class.forName("zzz.Base");
    }
}

 

二、類型轉換前先作檢查

  編譯器將檢查類型向下轉型是否合法,若是不合法將拋出異常。向下轉換類型前,可使用instanceof判斷。 

class Base { }
class Derived extends Base { }

public class Main {
    public static void main(String[] args) {
        Base base = new Derived();
        if (base instanceof Derived) {
            // 這裏能夠向下轉換了
            System.out.println("ok");
        }
        else {
            System.out.println("not ok");
        }
    }
}

 

三、反射:運行時類信息

  若是不知道某個對象的確切類型,RTTI能夠告訴你,可是有一個前提:這個類型在編譯時必須已知,這樣才能使用RTTI來識別它。Class類與java.lang.reflect類庫一塊兒對反射進行了支持,該類庫包含Field、Method和Constructor類,這些類的對象由JVM在啓動時建立,用以表示未知類裏對應的成員。這樣的話就可使用Contructor建立新的對象,用get()和set()方法獲取和修改類中與Field對象關聯的字段,用invoke()方法調用與Method對象關聯的方法。另外,還能夠調用getFields()、getMethods()和getConstructors()等許多便利的方法,以返回表示字段、方法、以及構造器對象的數組,這樣,對象信息能夠在運行時被徹底肯定下來,而在編譯時不須要知道關於類的任何事情。

  反射機制並無什麼神奇之處,當經過反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,看它屬於哪一個特定的類。所以,那個類的.class對於JVM來講必須是可獲取的,要麼在本地機器上,要麼從網絡獲取。因此對於RTTI和反射之間的真正區別只在於:

  • RTTI,編譯器在編譯時打開和檢查.class文件
  • 反射,運行時打開和檢查.class文件 
public class Person implements Serializable {

    private String name;
    private int age;
// get/set方法
}
public static void main(String[] args) {
    Person person = new Person("luoxn28", 23);
    Class clazz = person.getClass();

    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        String key = field.getName();
        PropertyDescriptor descriptor = new PropertyDescriptor(key, clazz);
        Method method = descriptor.getReadMethod();
        Object value = method.invoke(person);

        System.out.println(key + ":" + value);

    }
}

 

  以上經過getReadMethod()方法調用類的get函數,能夠經過getWriteMethod()方法來調用類的set方法。一般來講,咱們不須要使用反射工具,可是它們在建立動態代碼會更有用,反射在Java中用來支持其餘特性的,例如對象的序列化和JavaBean等。

四、動態代理

  代理模式是爲了提供額外或不一樣的操做,而插入的用來替代」實際」對象的對象,這些操做涉及到與」實際」對象的通訊,所以代理一般充當中間人角色。Java的動態代理比代理的思想更前進了一步,它能夠動態地建立並代理並動態地處理對所代理方法的調用。在動態代理上所作的全部調用都會被重定向到單一的調用處理器上,它的工做是揭示調用的類型並肯定相應的策略。 

 

學習Spring的時候,咱們知道Spring主要有兩大思想,一個是IoC,另外一個就是AOP,對於IoC,它利用的是反射機制,依賴注入就不用多說了,而對於Spring的核心AOP來講,使用了動態代理,其實底層也是反射。咱們不但要知道怎麼經過AOP來知足的咱們的功能,咱們更須要學習的是其底層是怎麼樣的一個原理,而AOP的原理就是java的動態代理機制,因此本篇隨筆就是對java的動態機制進行一個回顧。

在java的動態代理機制中,有兩個重要的類或接口,一個是 InvocationHandler(Interface)、另外一個則是 Proxy(Class),這一個類和接口是實現咱們動態代理所必須用到的。首先咱們先來看看java的API幫助文檔是怎麼樣對這兩個類進行描述的:

InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每個動態代理類都必需要實現InvocationHandler這個接口,而且每一個代理類的實例都關聯到了一個handler,當咱們經過代理對象調用一個方法的時候,這個方法的調用就會被轉發爲由InvocationHandler這個接口的 invoke 方法來進行調用。咱們來看看InvocationHandler這個接口的惟一一個方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

咱們看到這個方法一共接受三個參數,那麼這三個參數分別表明什麼呢?

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代咱們所代理的那個真實對象
method:  指代的是咱們所要調用真實對象的某個方法的Method對象
args:  指代的是調用真實對象某個方法時接受的參數

若是不是很明白,等下經過一個實例會對這幾個參數進行更深的講解。

接下來咱們來看看Proxy這個類:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

Proxy這個類的做用就是用來動態建立一個代理對象的類,它提供了許多的方法,可是咱們用的最多的就是 newProxyInstance 這個方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

這個方法的做用就是獲得一個動態的代理對象,其接收三個參數,咱們來看看這三個參數所表明的含義: 

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一個ClassLoader對象,定義了由哪一個ClassLoader對象來對生成的代理對象進行加載

interfaces:  一個Interface對象的數組,表示的是我將要給我須要代理的對象提供一組什麼接口,若是我提供了一組接口給它,那麼這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了

h:  一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪個InvocationHandler對象上

 

好了,在介紹完這兩個接口(類)之後,咱們來經過一個實例來看看咱們的動態代理模式是什麼樣的:

首先咱們定義了一個Subject類型的接口,爲其聲明瞭兩個方法:

public interface Subject
{
    public void rent();
    
    public void hello(String str);
}

接着,定義了一個類來實現這個接口,這個類就是咱們的真實對象,RealSubject類: 

public class RealSubject implements Subject
{
    @Override
    public void rent()
    {
        System.out.println("I want to rent my house");
    }
    
    @Override
    public void hello(String str)
    {
        System.out.println("hello: " + str);
    }
}

 

下一步,咱們就要定義一個動態代理類了,前面說個,每個動態代理類都必需要實現 InvocationHandler 這個接口,所以咱們這個動態代理類也不例外: 

public class DynamicProxy implements InvocationHandler
{
    // 這個就是咱們要代理的真實對象
    private Object subject;
    
    //    構造方法,給咱們要代理的真實對象賦初值
    public DynamicProxy(Object subject)
    {
        this.subject = subject;
    }
    
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真實對象前咱們能夠添加一些本身的操做
        System.out.println("before invoke");
        
        System.out.println("Method:" + method);
        
        //    當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
        method.invoke(subject, args);
        
        //  在代理真實對象後咱們也能夠添加一些本身的操做
        System.out.println("after invoke");
        
        return null;
    }

}

 

最後,來看看咱們的Client類: 

public class Client
{
    public static void main(String[] args)
    {
        //    咱們要代理的真實對象
        Subject realSubject = new RealSubject();

        //    咱們要代理哪一個真實對象,就將該對象傳進去,最後是經過該真實對象來調用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 經過Proxy的newProxyInstance方法來建立咱們的代理對象,咱們來看看其三個參數
         * 第一個參數 handler.getClass().getClassLoader() ,咱們這裏使用handler這個類的ClassLoader對象來加載咱們的代理對象
         * 第二個參數realSubject.getClass().getInterfaces(),咱們這裏爲代理對象提供的接口是真實對象所實行的接口,表示我要代理的是該真實對象,這樣我就能調用這組接口中的方法了
         * 第三個參數handler, 咱們這裏將這個代理對象關聯到了上方的 InvocationHandler 這個對象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);
        
        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}

 

咱們先來看看控制檯的輸出: 

$Proxy0

before invoke
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
I want to rent my house
after invoke

before invoke
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after invoke

 

 

當調試的時候,若是把鼠標放在subject 上面,會自動的執行toString()方法,也就是

before invoke
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.toString()
 
after invoke

 

咱們首先來看看 $Proxy0 這東西,咱們看到,這個東西是由 System.out.println(subject.getClass().getName()); 這條語句打印出來的,那麼爲何咱們返回的這個代理對象的類名是這樣的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我覺得返回的這個代理對象會是Subject類型的對象,或者是InvocationHandler的對象,結果卻不是,首先咱們解釋一下爲何咱們這裏能夠將其轉化爲Subject類型的對象?緣由就是在newProxyInstance這個方法的第二個參數上,咱們給這個代理對象提供了一組什麼接口,那麼我這個代理對象就會實現了這組接口,這個時候咱們固然能夠將這個代理對象強制類型轉化爲這組接口中的任意一個,由於這裏的接口是Subject類型,因此就能夠將其轉化爲Subject類型了

同時咱們必定要記住,通過 Proxy.newProxyInstance 建立的代理對象是在jvm運行時動態生成的一個對象,它並非咱們的InvocationHandler類型,也不是咱們定義的那組接口的類型,而是在運行是動態生成的一個對象,而且命名方式都是這樣的形式,以$開頭,proxy爲中,最後一個數字表示對象的標號

接着咱們來看看這兩句 

subject.rent();
subject.hello("world");

這裏是經過代理對象來調用實現的那種接口中的方法,這個時候程序就會跳轉到由這個代理對象關聯到的 handler 中的invoke方法去執行,而咱們的這個 handler 對象又接受了一個 RealSubject類型的參數,表示我要代理的就是這個真實對象,因此此時就會調用 handler 中的invoke方法去執行: 

public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真實對象前咱們能夠添加一些本身的操做
        System.out.println("before invoke");
        
        System.out.println("Method:" + method);
        
        //    當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
        method.invoke(subject, args);
        
        //  在代理真實對象後咱們也能夠添加一些本身的操做
        System.out.println("after invoke");
        
        return null;
    }

 

咱們看到,在真正經過代理對象來調用真實對象的方法的時候,咱們能夠在該方法先後添加本身的一些操做,同時咱們看到咱們的這個 method 對象是這樣的:

public abstract void com.xiaoluo.dynamicproxy.Subject.rent()

public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)

正好就是咱們的Subject接口中的兩個方法,這也就證實了當我經過代理對象來調用方法的時候,起實際就是委託由其關聯到的 handler 對象的invoke方法中來調用,並非本身來真實調用,而是經過代理的方式來調用的。

這就是咱們的java動態代理機制

 

 參考:深刻理解Java反射

 參考:java的動態代理機制詳解

相關文章
相關標籤/搜索