Java提升班(六)反射和動態代理(JDK Proxy和Cglib)

反射和動態代理放有必定的相關性,但單純的說動態代理是由反射機制實現的,實際上是不夠全面不許確的,動態代理是一種功能行爲,而它的實現方法有不少。要怎麼理解以上這句話,請看下文。html

1、反射

反射機制是 Java 語言提供的一種基礎功能,賦予程序在運行時自省(introspect,官方用語)的能力。經過反射咱們能夠直接操做類或者對象,好比獲取某個對象的類定義,獲取類聲明的屬性和方法,調用方法或者構造對象,甚至能夠運行時修改類定義。java

一、獲取類(Class)對象

獲取類對象有三種方法:git

  • 經過forName() -> 示例:Class.forName("PeopleImpl")
  • 經過getClass() -> 示例:new PeopleImpl().getClass()
  • 直接獲取.class -> 示例:PeopleImpl.class

二、類的經常使用方法

  • getName():獲取類完整方法;
  • getSuperclass():獲取類的父類;
  • newInstance():建立實例對象;
  • getFields():獲取當前類和父類的public修飾的全部屬性;
  • getDeclaredFields():獲取當前類(不包含父類)的聲明的全部屬性;
  • getMethod():獲取當前類和父類的public修飾的全部方法;
  • getDeclaredMethods():獲取當前類(不包含父類)的聲明的全部方法;

更多方法:http://icdn.apigo.cn/blog/class-all-method.pnggithub

三、類方法調用

反射要調用類中的方法,須要經過關鍵方法「invoke()」實現的,方法調用也分爲三種:編程

  • 靜態(static)方法調用
  • 普通方法調用
  • 私有方法調用

如下會分別演示,各類調用的實現代碼,各類調用的公共代碼部分,以下:設計模式

// 此段代碼爲公共代碼
interface People {
    int parentAge = 18;
    public void sayHi(String name);
}
class PeopleImpl implements People {
    private String privSex = "男";
    public String race = "漢族";
    @Override
    public void sayHi(String name) {
        System.out.println("hello," + name);
    }
    private void prvSayHi() {
        System.out.println("prvSayHi~");
    }
    public static void getSex() {
        System.out.println("18歲");
    }
}

3.1 靜態方法調用

// 核心代碼(省略了拋出異常的聲明)
public static void main(String[] args) {
    Class myClass = Class.forName("example.PeopleImpl");
    // 調用靜態(static)方法
    Method getSex = myClass.getMethod("getSex");
    getSex.invoke(myClass);
}

靜態方法的調用比較簡單,使用 getMethod(xx) 獲取到對應的方法,直接使用 invoke(xx)就能夠了。api

3.2 普通方法調用

普通非靜態方法調用,須要先獲取類示例,經過「newInstance()」方法獲取,核心代碼以下:框架

Class myClass = Class.forName("example.PeopleImpl");
Object object = myClass.newInstance();
Method method = myClass.getMethod("sayHi",String.class);
method.invoke(object,"老王");

getMethod 獲取方法,能夠聲明須要傳遞的參數的類型。ide

3.3 調用私有方法

調用私有方法,必須使用「getDeclaredMethod(xx)」獲取本類全部什麼的方法,代碼以下:性能

Class myClass = Class.forName("example.PeopleImpl");
Object object = myClass.newInstance();
Method privSayHi = myClass.getDeclaredMethod("privSayHi");
privSayHi.setAccessible(true); // 修改訪問限制
privSayHi.invoke(object);

除了「getDeclaredMethod(xx)」能夠看出,調用私有方法的關鍵是設置 setAccessible(true) 屬性,修改訪問限制,這樣設置以後就能夠進行調用了。

四、總結

1.在反射中核心的方法是 newInstance() 獲取類實例,getMethod(..) 獲取方法,使用 invoke(..) 進行方法調用,經過 setAccessible 修改私有變量/方法的訪問限制。

2.獲取屬性/方法的時候有無「Declared」的區別是,帶有 Declared 修飾的方法或屬性,能夠獲取本類的全部方法或屬性(private 到 public),但不能獲取到父類的任何信息;非 Declared 修飾的方法或屬性,只能獲取 public 修飾的方法或屬性,並能夠獲取到父類的信息,好比 getMethod(..)和getDeclaredMethod(..)。

2、動態代理

動態代理是一種方便運行時動態構建代理、動態處理代理方法調用的機制,不少場景都是利用相似機制作到的,好比用來包裝 RPC 調用、面向切面的編程(AOP)。

實現動態代理的方式不少,好比 JDK 自身提供的動態代理,就是主要利用了上面提到的反射機制。還有其餘的實現方式,好比利用傳說中更高性能的字節碼操做機制,相似 ASM、cglib(基於 ASM)等。

動態代理解決的問題?

首先,它是一個代理機制。若是熟悉設計模式中的代理模式,咱們會知道,代理能夠看做是對調用目標的一個包裝,這樣咱們對目標代碼的調用不是直接發生的,而是經過代理完成。經過代理可讓調用者與實現者之間解耦。好比進行 RPC 調用,經過代理,能夠提供更加友善的界面。還能夠經過代理,能夠作一個全局的攔截器。

一、JDK Proxy 動態代理

JDK Proxy 是經過實現 InvocationHandler 接口來實現的,代碼以下:

interface Animal {
    void eat();
}
class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("The dog is eating");
    }
}
class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("The cat is eating");
    }
}

// JDK 代理類
class AnimalProxy implements InvocationHandler {
    private Object target; // 代理對象
    public Object getInstance(Object target) {
        this.target = target;
        // 取得代理對象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("調用前");
        Object result = method.invoke(target, args); // 方法調用
        System.out.println("調用後");
        return result;
    }
}

public static void main(String[] args) {
    // JDK 動態代理調用
    AnimalProxy proxy = new AnimalProxy();
    Animal dogProxy = (Animal) proxy.getInstance(new Dog());
    dogProxy.eat();
}

如上代碼,咱們實現了經過動態代理,在全部請求以前和以後打印了一個簡單的信息。

注意: JDK Proxy 只能代理實現接口的類(即便是extends繼承類也是不能夠代理的)。

JDK Proxy 爲何只能代理實現接口的類?

這個問題要從動態代理的實現方法 newProxyInstance 源碼提及:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
// 省略其餘代碼

來看前兩個源碼參數說明:

* @param   loader the class loader to define the proxy class
* @param   interfaces the list of interfaces for the proxy class to implement
  • loader:爲類加載器,也就是 target.getClass().getClassLoader()
  • interfaces:接口代理類的接口實現列表

因此這個問題的源頭,在於 JDK Proxy 的源碼設計。若是要執意動態代理,非接口實現類就會報錯:

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx

二、Cglib 動態代理

JDK 動態代理機制只能代理實現了接口的類,Cglib 是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現加強,但由於採用的是繼承,因此不能對 final 修飾的類進行代理。

Cglib 能夠經過 Maven 直接進行版本引用,Maven 版本地址:https://mvnrepository.com/artifact/cglib/cglib

本文使用的是最新版本 3.2.9 的 Cglib,在 pom.xml 添加以下引用:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.9</version>
</dependency>

Cglib 代碼實現,以下:

class Panda {
    public void eat() {
        System.out.println("The panda is eating");
    }
}
class CglibProxy implements MethodInterceptor {
    private Object target; // 代理對象
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 設置父類爲實例類
        enhancer.setSuperclass(this.target.getClass());
        // 回調方法
        enhancer.setCallback(this);
        // 建立代理對象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("調用前");
        Object result = methodProxy.invokeSuper(o, objects); // 執行方法調用
        System.out.println("調用後");
        return result;
    }
}

public static void main(String[] args) {
    // CGLIB 動態代理調用
    CglibProxy proxy = new CglibProxy();
    Panda panda = (Panda)proxy.getInstance(new Panda());
    panda.eat();
}

cglib 的調用經過實現 MethodInterceptor 接口的 intercept 方法,調用 invokeSuper 進行動態代理的,能夠直接對普通類進行動態代理。

3、JDK Proxy VS Cglib

JDK Proxy 的優點:

  • 最小化依賴關係,減小依賴意味着簡化開發和維護,JDK 自己的支持,更加可靠;
  • 平滑進行 JDK 版本升級,而字節碼類庫一般須要進行更新以保證在新版上可以使用;

Cglib 框架的優點:

  • 可調用普通類,不須要實現接口;
  • 高性能;

總結: 須要注意的是,咱們在選型中,性能未必是惟一考量,可靠性、可維護性、編程工做量等每每是更主要的考慮因素,畢竟標準類庫和反射編程的門檻要低得多,代碼量也是更加可控的,若是咱們比較下不一樣開源項目在動態代理開發上的投入,也能看到這一點。

本文全部示例代碼:https://github.com/vipstone/java-core-example.git

4、參考文檔

Java核心技術36講:http://t.cn/EwUJvWA

Java反射與動態代理:https://www.cnblogs.com/hanganglin/p/4485999.html

相關文章
相關標籤/搜索