Java動態代理和Cglib動態代理最強王者陣容

前言

上一篇講解了反射的知識[],做爲反射的入門級,而後這一篇主要也是講解動態代理的實現機制。java

動態代理包括jdk的動態代理cglib 的動態代理,二者實現相同的功能,可是實現方式倒是有明顯的區別。程序員

下面咱們就經過代碼的方式層層的深刻這兩種動態代理,瞭解他們的性能以、底層的實現原理及應用場景。web

代理模式

在詳細介紹動態代理以前,先來講說Java中的代理模式。代理模式分爲兩種:編程

  1. 靜態代理:也就是23種設計模式中的代理模式,由程序員本身編寫源代碼並進行編譯,在程序運行以前已經編譯好了.class文件。
  2. 動態代理:包括jdk的動態代理和cglib的動態代理,運行時經過反射動態建立。

代理模式定義:個人我的理解就是給某一個對象提供一個代理對象,在代理對象中擁有被代理對象的引用,並在代理對象中調用被代理對象的方法以前和以後進行方法的加強。設計模式

我這裏畫了一張代理模式的類圖,設計模式中的代理模式比較簡單,代理類和委託類有公共的接口,最後由代理類去執行委託類的方法:編輯器

代理模式就好像生活中的中介,去幫你作事,而不用比本身去作事。舉個例子,好比你要買車,可是買車以前你要處處找車源,找到車源給錢了還要辦理一堆手續。ide

(1)下面咱們以買車這個案例進行代理模式的代碼編寫,首先要有一個公共的接口Person,Person接口裏面定義公共的方法:函數

public interface Person{
    void buyCar();
}
複製代碼

(2)而後定義一個委託類,也就是我本人Myself,並實現Person接口,具體代碼以下:工具

性能

public class Myself implements Person {
@Override
public void buyCar() {
    System.out.println("我要買車了");
}
複製代碼
複製代碼@Override public void buyCar() { System.out.println("我要買車了"); } 複製代碼} 複製代碼

(3)最後就是建立代理類CarProxy,一樣也是實現Person接口,具體實現代碼以下:

public class CarProxy implements Person{
private Myself  myself ;

public CarProxy(final Myself  myself ) {
    this.myself = myself ;
}

@Override
public void buyCar() {
    System.out.println("買車前去找車源");
    myself .buyCar();
    System.out.println("買車後辦理手續");
}
複製代碼
複製代碼private Myself myself ; public CarProxy(final Myself myself ) { this.myself = myself ; } @Override public void buyCar() { System.out.println("買車前去找車源"); myself .buyCar(); System.out.println("買車後辦理手續"); } 複製代碼} 複製代碼

這個代理的demo很簡單,如上面的類圖所示,代理類和委託類都實現公共的接口Person,在委託類中進行方法的具體業務邏輯的實現,而代理類中再次對這個方法進行加強。

代理模式的優勢就是可以對目標對象進行功能的擴展,缺點是每個業務類都要建立一個代理類,這樣會使咱們系統內的類的規模變得很大,不利於維護

因而就出現了動態代理,仔細思考靜態代理的缺點,就是一個委託類就會對象一個代理類,那麼是否能夠將代理類作成一個通用的呢?

咱們仔細來看一下下面的這個圖:

咱們把靜態代理全部的執行過程均可以抽象成這張圖的執行過程,Proxy角色無非是在調用委託類處理業務的方法以前或者以後作一些額外的操做

那麼爲了作一個通用性的處理,就把調用委託類的method的動做抽出來,當作一個通用性的處理類,因而就有了InvocationHandler角色,抽象成一個處理類。

這樣在Proxy和委託類之間就多了一個InvocationHandler處理類的角色,這個角色主要是將以前代理類調用委託類的方法的動做進行統一的調用,都由InvocationHandler來處理

因而以前上面的類圖就有了這樣的改變,在Proxy和委託類之間加入了InvocationHandler,具體的實現圖以下:

看完上面的圖彷佛有那麼一點點的理解,下面咱們就來詳細的深刻動態代理。

jdk動態代理

上面講解到動態代理是在運行時環境動態加載class文件,並建立對應的class對象,那麼動態代理着靜態代理的執行時機是在哪裏呢?

我這邊又畫了一張原理圖,感受我爲畫圖操碎了心,每個點都會畫一個想截圖,是否是很暖。

這個是靜態代理的運行原理圖,靜態代理在程序運行時就已經建立好了class文件,在程序啓動後的某一個時機(用到class文件)就會加載class文件到內存中。

當在運行時期動態生成class文件並加載class文件的運行原理圖以下:

在JVM運行期時遵循JVM字節碼的結構和規範生成二進制文件,並加載到內存中生成對應的Class對象。這樣,就完成了動態建立class文件和Class對象的功能了。

在jdk的動態代理中的Proxy類和委託類要求實現相同的功能,這裏的相同是指他們均可以調用統一的邏輯業務方法。要實現這樣的設計有如下三種方法:

  1. 實現同一個接口:接口裏面定義公共的方法。
  2. 繼承:Proxy繼承委託類,這樣Proxy就有了和委託類同樣的功能,或者二者都繼承同一個類,把公共實現業務邏輯的方法放在父類中,這樣也能實現。
  3. 二者內部都有同一個類的引用:這個和繼承有殊途同歸之妙,均可以統一的調用統一的業務邏輯方法。

在jdk的動態代理中,是採用第一種方法進行實現,必須有公共的接口,下面咱們仍是經過靜態代理的案例使用動態代理來實現。

(1)首先建立一個公共接口Person:

public interface Person{
    void buyCar();
}
複製代碼

(2)而後建立接口的實現類Myself:

public class Myself implements Person {
@Override
public void buyCar() {
    System.out.println("我要買車了");
}
複製代碼
複製代碼@Override public void buyCar() { System.out.println("我要買車了"); } 複製代碼} 複製代碼

(3)這一步就是比較關鍵的,要建立一個類並實現InvocationHandler

public class InvocationHandlerImpl implements InvocationHandler {
private Person person;  
  
public InvocationHandlerImpl(Person  person){  
    this.person=person;  
}  
  
@Override  
public Object invoke(Object proxy, Method method,  Object[] args) throws Throwable {  
    System.out.println("買車前開始找車源。。。。");  
    method.invoke(person, args);  
    System.out.println("買車後辦理手續。。。。");  
    return null;  
}    
複製代碼
複製代碼private Person person; public InvocationHandlerImpl(Person person){ this.person=person; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("買車前開始找車源。。。。"); method.invoke(person, args); System.out.println("買車後辦理手續。。。。"); return null; } 複製代碼} 複製代碼

(4)最後一步就是進行測試:

public class Test {
public static void main(String[] args) {  
    Myself myself= new Myself();  
    // 建立代理對象,這裏有三個參數,第一個是類的ClassLoader,第二個是該類的接口集合,第三個就是InvocationHandler
    Object o = Proxy.newProxyInstance(myself.getClass().getClassLoader(), myself.getClass().getInterfaces(), new InvocationHandlerImpl(myself));  
    Person person= (Person) o;  
    person.buyCar();  
}  
複製代碼
複製代碼public static void main(String[] args) { Myself myself= new Myself(); // 建立代理對象,這裏有三個參數,第一個是類的ClassLoader,第二個是該類的接口集合,第三個就是InvocationHandler Object o = Proxy.newProxyInstance(myself.getClass().getClassLoader(), myself.getClass().getInterfaces(), new InvocationHandlerImpl(myself)); Person person= (Person) o; person.buyCar(); } 複製代碼} 複製代碼

總體來講jdk動態代理的應用過程仍是比較簡單的,重要的實現理解他的底層實現過程,它的重要實現步驟就是InvocationHandler中 的invoke方法處理。

invoke方法纔是實現方法的調用者,根據上面的參數最後纔會建立代理對象newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

那麼在實現jdk動態代理的過程都作了哪些工做呢?具體有如下6個步驟:

  1. 獲取委託類也就是Myself上的全部接口。
  2. 生成代理,生成的代理的名稱也是有規律的,通常是在 com.sun.proxy.$ProxyXXX
  3. 動態建立代理類的字節碼信息,也就是class文件。
  4. 根據class文件建立Class對象。
  5. 建立本身的InvocationHandler並實現InvocationHandler重寫invoke方法,實現對委託類方法的調用和加強。
  6. 最後是代理對象的建立,並調用方法,實現代理的功能。

咱們能夠經過反編譯工具來看看生成的代理類的源碼是怎麼樣的,我這裏使用的反編譯工具是jd-gui,推薦給你們。

public final class MyselfProxy extends Proxy implements Person { private static Method m1; private static Method m3; private static Method m0; private static Method m2;

public MyselfProxy(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}

public final boolean equals(Object paramObject) throws {
try { // InvocationHandler 實現equals的調用 return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}

public final void buyCar() throws {
try {
// InvocationHandler實現buyCar的調用 this.h.invoke(this, m3, null);
return;
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}

public final int hashCode() throws {
try {
// InvocationHandler實現hashCode方法的調用 return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}

public final String toString() throws {
try {
// InvocationHandler實現toString的調用 return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}

複製代碼static {
try { //在靜態塊中經過反射初始化函數 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.ldc.org.Person").getMethod("buyCar", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
} catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
} 複製代碼

從上面反編譯的源碼中能夠能夠看出,在靜態塊中直接經過反射的方式來生成Method對象,對方法的調用則是經過InvocationHandler對象來進行調用。

仔細的總結能夠看出上面反編譯出來的代理類有如下特徵:

  1. 繼承 java.lang.reflect.Proxy類,並實現統一的接口Person。
  2. 全部的方法都是 final修飾的。
  3. 都是經過 InvocationHandler對象執行invoke方法的調用統一調用函數,invoke方法經過Method參數來區分是什麼方法,進而相應的處理。

到這裏我想你們應該對jdk的動態代理有一個清晰的認識了,包括他的底層實現的原理,下面咱們就來詳細的瞭解cglib動態代理的是實現方式。

cglib動態代理

在實現jdk的動態代理的實現會發現,jdk動態代理必須實現一個接口,而且代理類也只能代理接口中實現的方法,要是實現類中有本身私有的方法,而接口中沒有的話,該方法不能進行代理調用。

基於這種狀況cglib便出現了,他也能夠在運行期擴展Java類和Java接口。

cglib底層是採用字節碼技術,其原理是經過字節碼技術生成一個子類,並在子類中攔截父類的方法的調用,織入業務邏輯。

由於原理是採用繼承的方式,因此被代理的類不能被final修飾,在Spring Aop中底層的實現是以這兩種動態代理做爲基礎進行實現。

當使用cglib動態代理一個類demo時,JVM又作了哪些工做呢?

  1. 首先找到demo類中的全部非final的公共方法。
  2. 而後將這些方法轉化爲字節碼。
  3. 經過這些字節碼轉化爲Class對象。
  4. 最後由MethodInterceptor實現代理類中全部方法的調用。

(1)那麼咱們經過代碼也來實現cglib動態代理,仍是建立Myself類,可是此時不須要實現接口:

public class Myself {
@Override
public void buyCar() {
    System.out.println("I'm going to buy a house");
}
複製代碼
複製代碼@Override public void buyCar() { System.out.println("I'm going to buy a house"); } 複製代碼} 複製代碼

(2)而後是建立MyMethodInterceptor類實現MethodInterceptor接口,這個和動態代理實現InvocationHandler方式同樣,實現統一方法的調用。

public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("買車前開始找車源。。。。"); proxy.invokeSuper(obj, args); System.out.println("買車後辦理手續。。。。"); return null; } 複製代碼} 複製代碼

(3)最後是進行測試

public class Test {  
    public static void main(String[] args) {  
        Myself myself= new Myself();  
        MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor ();  
        //cglib 中增強器,用來建立動態代理  
        Enhancer enhancer = new Enhancer();    
         //設置要建立的代理類  
        enhancer.setSuperclass(myself.getClass());    
        // 設置回調,這裏至關因而對於代理類上全部方法的調用
         enhancer.setCallback(myMethodInterceptor );  
         // 建立代理類
         Programmer proxy =(Myself)enhancer.create();  
         proxy.buyCar();  
    }  
}
複製代碼

總結來講cglib是一個強大的、高性能的Code生產類庫,在Spring中就是經過cglib方式繼承要被代理的類,重寫父類的方法,實現Aop編程。

cglib建立動態代理對象的性能時機要比jdk動態代理的方式高不少,可是建立對象所花的時間卻要比jdk動態代理方式多不少。

在應用方面單例模式更適合用cglib,無需頻繁的建立對象,相反,則使用jdk動態代理的方式更加合適。

相關文章
相關標籤/搜索