前言
上一篇講解了反射的知識[],作爲反射的入門級,然後這一篇主要也是講解動態代理的實現機制。
動態代理包括jdk的動態代理和cglib 的動態代理,兩者實現相同的功能,但是實現方式卻是有明顯的區別。
下面我們就通過代碼的方式層層的深入這兩種動態代理,瞭解他們的性能以、底層的實現原理及應用場景。
代理模式
在詳細介紹動態代理之前,先來說說Java中的代理模式。代理模式分爲兩種:
- 靜態代理:也就是23種設計模式中的代理模式,由程序員自己編寫源代碼並進行編譯,在程序運行之前已經編譯好了.class文件。
- 動態代理:包括jdk的動態代理和cglib的動態代理,運行時通過反射動態創建。
代理模式定義:我的個人理解就是給某一個對象提供一個代理對象,在代理對象中擁有被代理對象的引用,並在代理對象中調用被代理對象的方法之前和之後進行方法的增強。
我這裏畫了一張代理模式的類圖,設計模式中的代理模式比較簡單,代理類和委託類有公共的接口,最後由代理類去執行委託類的方法:
代理模式就好像生活中的中介,去幫你做事,而不用比自己去做事。舉個例子,比如你要買車,但是買車之前你要到處找車源,找到車源給錢了還要辦理一堆手續。
(1)下面我們以買車這個案例進行代理模式的代碼編寫,首先要有一個公共的接口Person,Person接口裏面定義公共的方法:
public interface Person{
void buyCar();
}
(2)然後定義一個委託類,也就是我本人Myself,並實現Person接口,具體代碼如下:
public class Myself implements Person {
@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("買車後辦理手續");
}
}
這個代理的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類和委託類要求實現相同的功能,這裏的相同是指他們都可以調用統一的邏輯業務方法。要實現這樣的設計有以下三種方法:
- 實現同一個接口:接口裏面定義公共的方法。
- 繼承:Proxy繼承委託類,這樣Proxy就有了和委託類一樣的功能,或者兩者都繼承同一個類,把公共實現業務邏輯的方法放在父類中,這樣也能實現。
- 兩者內部都有同一個類的引用:這個和繼承有異曲同工之妙,都可以統一的調用統一的業務邏輯方法。
在jdk的動態代理中,是採用第一種方法進行實現,必須有公共的接口,下面我們還是通過靜態代理的案例使用動態代理來實現。
(1)首先創建一個公共接口Person:
public interface Person{
void buyCar();
}
(2)然後創建接口的實現類Myself:
public class Myself implements Person {
@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;
}
}
(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();
}
}
整體來說jdk動態代理的應用過程還是比較簡單的,重要的實現理解他的底層實現過程,它的重要實現步驟就是InvocationHandler
中 的invoke
方法處理。
invoke方法纔是實現方法的調用者,根據上面的參數最後纔會創建代理對象newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
。
那麼在實現jdk動態代理的過程都做了哪些工作呢?具體有以下6個步驟:
- 獲取委託類也就是Myself上的所有接口。
- 生成代理,生成的代理的名稱也是有規律的,一般是在com.sun.proxy.$ProxyXXX。
- 動態創建代理類的字節碼信息,也就是class文件。
- 根據class文件創建Class對象。
- 創建自己的InvocationHandler並實現InvocationHandler重寫invoke方法,實現對委託類方法的調用和增強。
- 最後是代理對象的創建,並調用方法,實現代理的功能。
我們可以通過反編譯工具來看看生成的代理類的源碼是怎麼樣的,我這裏使用的反編譯工具是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
對象來進行調用。
仔細的總結可以看出上面反編譯出來的代理類有以下特徵:
- 繼承 java.lang.reflect.Proxy類,並實現統一的接口Person。
- 所有的方法都是final修飾的。
- 在代理類中都是通過InvocationHandler對象執行invoke方法的調用統一調用函數,invoke方法通過Method參數來區分是什麼方法,進而相應的處理。
到這裏我想大家應該對jdk的動態代理有一個清晰的認識了,包括他的底層實現的原理,下面我們就來詳細的瞭解cglib動態代理的是實現方式。
cglib動態代理
在實現jdk的動態代理的實現會發現,jdk動態代理必須實現一個接口,並且代理類也只能代理接口中實現的方法,要是實現類中有自己私有的方法,而接口中沒有的話,該方法不能進行代理調用。
基於這種情況cglib
便出現了,他也可以在運行期擴展Java類和Java接口。
cglib
底層是採用字節碼技術,其原理是通過字節碼技術生成一個子類,並在子類中攔截父類的方法的調用,織入業務邏輯。
因爲原理是採用繼承的方式,所以被代理的類不能被final修飾,在Spring Aop中底層的實現是以這兩種動態代理作爲基礎進行實現。
當使用cglib動態代理一個類demo時,JVM又做了哪些工作呢?
- 首先找到demo類中的所有非final的公共方法。
- 然後將這些方法轉化爲字節碼。
- 通過這些字節碼轉化爲Class對象。
- 最後由MethodInterceptor實現代理類中所有方法的調用。
(1)那麼我們通過代碼也來實現cglib動態代理,還是創建Myself類,但是此時不需要實現接口:
public class Myself {
@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動態代理的方式更加合適。