代理模式實現方式及優缺點對比

       代理模式最典型的應用就是AOP,本文結合主要講解了代理模式的幾種實現方式:靜態代理和動態代理,這裏動態代理又能夠分爲jdk代理和Cglib代理,另外,本文也對這幾種代理模式的優缺點進行了對比。java

       代理,顧名思義,即代替被請求者來處理相關事務。代理對象通常會全權代理被請求者的所有隻能,客戶訪問代理對象就像在訪問被請求者同樣,雖然代理對象最終仍是可能會訪問被請求者,可是其能夠在請求以前或者請求以後進行一些額外的工做,或者說客戶的請求不合法,直接拒絕客戶的請求。以下圖所示爲代理模式的一份簡圖:編程

代理模式的角色:安全

  • ISubject:代理者與被代理者共同實現的接口,能夠理解爲須要代理的行爲;
  • SubjectImpl:被代理者,其爲具備某種特定行爲的實現者;
  • SubjectProxy:代理者,其會全權代理SubjectImpl所具備的功能,在實現其功能的基礎上作一些額外的工做;
  • Client:客戶端,客戶端訪問代理者與訪問被代理者具備相似的效果,其沒法區分訪問的是代理者仍是被代理者。

1. 靜態代理

       靜態代理模式也即上圖中描述的這種模式,從圖中能夠看出,SubjectProxy保存一個ISubject實例,當客戶端調用SubjectProxy的request()方法時,其除了作額外的工做以外,還會調用ISubject實例的request()方法。以下是這三個類的一個簡單實現:jvm

public interface ISubject {
  void request();
}
public class SubjectImpl implements ISubject {
  @Override
  public void request() {
    System.out.println("request SubjectImpl.");
  }
}
public class SubjectProxy implements ISubject {
  private ISubject target;

  public SubjectProxy(ISubject target) {
    this.target = target;
  }

  @Override
  public void request() {
    System.out.println("before safety check.");
    target.request();
    System.out.println("after safety check.");
  }
}

       能夠看到,代理對象在調用被代理對象的方法以前和以後都打印了相關的語句。以下是客戶端請求示例:ide

public class Client {
  @Test
  public void testStaticProxy() {
    ISubject subject = new SubjectImpl();
    ISubject proxy = new SubjectProxy(subject);
    proxy.request();
  }
}

運行上述用例,可獲得以下結果:this

before safety check.
request SubjectImpl.
after safety check.

       從客戶端訪問方式能夠看出,客戶端獲取的是一個實現ISubject接口的實例,其在調用的request()方法其實是代理對象的request()方法。這種代理方式稱爲靜態代理,而且這種代理方式也是效率最高的一種方式,由於全部的類都是已經編寫完成的,客戶端只須要取得代理對象而且執行便可。3d

       靜態代理雖然效率較高,但其也有不可避免的缺陷。能夠看到,客戶端在調用代理對象時,使用的是代理對象和被代理對象都實現的一個接口,咱們能夠將該接口理解爲定義了某一種業務需求的實現規範。若是有另一份業務需求(如進行數據修改),其與當前需求並行的,沒有交集的,可是其在進行正常業務以外所作的安全驗證工做與當前需求是一致的。以下是咱們進行該數據修改業務的實現代碼:代理

public interface IUpdatable {
  void update();
}
public class UpdatableImpl implements IUpdatable {
  @Override
  public void update() {
    System.out.println("update UpdatableImpl.");
  }
}
public class UpdatableProxy implements IUpdatable {
  private IUpdatable updatable;

  public UpdatableProxy(IUpdatable updatable) {
    this.updatable = updatable;
  }

  @Override
  public void update() {
    System.out.println("pre safety check.");
    updatable.update();
    System.out.println("after safety check.");
  }
}

以下是客戶端代碼:code

public class Client {
  @Test
  public void testStaticProxy() {
    ISubject subject = new SubjectImpl();
    ISubject proxy = new SubjectProxy(subject);
    proxy.request();
    
    IUpdatable updatable = new UpdatableImpl();
    IUpdatable proxy = new UpdatableProxy(updatable);
    proxy.update();
  }
}

       能夠看到,要實現相同的對象代理功能(安全驗證),靜態代理方式須要爲每一個接口實現一個代理類,而這些代理類中的代碼幾乎是一致的。這在大型系統中將會產生很大的維護問題。對象

2. 動態代理

① jdk代理

       所謂的jdk代理指的是藉助jdk所提供的相關類來實現代理模式,其主要有兩個類:InvocationHandler和Proxy。在實現代理模式時,只須要實現InvocationHandler接口便可,以下是實現該接口的一個示例:

public class SafetyInvocationHandler implements InvocationHandler {
  private Object target;

  public SafetyInvocationHandler(Object target) {
    this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("before safety check.");
    Object result = method.invoke(target, args);
    System.out.println("after safety check.");
    return result;
  }
}

       以下是客戶端調用方式:

public class Client {
  @Test
  public void testDynamicProxy() {
    ISubject subject = new SubjectImpl();
    ISubject proxySubject = (ISubject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{ISubject.class}, new SafetyInvocationHandler(subject));
    proxySubject.request();

    IUpdatable updatable = new UpdatableImpl();
    IUpdatable proxyUpdatable = (IUpdatable) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{IUpdatable.class}, new SafetyInvocationHandler(updatable));
    proxyUpdatable.update();
  }
}

       能夠看到,客戶端在調用代理對象時使用的都是同一個SafetyInvocationHandler。這裏jdk代理其實在底層利用反射爲每一個須要代理的對象都建立了一個InvocationHandler實例,在調用目標對象時,其首先會調用代理對象,而後在代理對象的邏輯中請求目標對象。這也就是爲何在代理類中能夠保存目標對象實例的緣由,好比上述的SafetyInvocationHandler,其聲明瞭一個Object類型的屬性用來保存目標對象的實例。

       jdk代理解決了靜態代理須要爲每一個業務接口建立一個代理類的問題,雖然使用反射建立代理對象效率比靜態代理稍低,但其在現代高速jvm中也是能夠接受的,在Spring的AOP代理中默認就是使用的jdk代理實現的。這裏jdk代理的限制也是比較明顯的,即其須要被代理的對象必須實現一個接口。這裏若是被代理對象沒有實現任何接口,或者被代理的業務方法沒有相應的接口,咱們則可使用另外一種方式來實現,即Cglib代理。

② Cglib代理

       Cglib代理是功能最爲強大的一種代理方式,由於其不只解決了靜態代理須要建立多個代理類的問題,還解決了jdk代理須要被代理對象實現某個接口的問題。對於須要代理的類,若是能爲其建立一個子類,而且在子類中編寫相關的代理邏輯,由於「子類 instanceof 父類」,於是在進行調用時直接調用子類對象的實例,也能夠達到代理的效果。Cglib代理的原理其實是動態生成被代理類的子類字節碼,因爲其字節碼都是按照jvm編譯後的class文件的規範編寫的,於是其能夠被jvm正常加載並運行。這也就是Cglib代理爲何不須要爲每一個被代理類編寫代理邏輯的緣由。這裏須要注意的是,根據Cglib實現原理,因爲其是經過建立子類字節碼的形式來實現代理的,若是被代理類的方法被聲明final類型,那麼Cglib代理是沒法正常工做的,由於final類型方法不能被重寫。以下是使用Cglib代理的一個示例:

/**
 * 被代理類
 */
public class Suject {
  public void request() {
    System.out.println("update without implement any interface.");
  }
}
/**
 * 代理類
 */
public class SafetyCheckCallback implements MethodInterceptor {
  @Override
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("before safety check.");
    Object result = methodProxy.invokeSuper(o, objects);
    System.out.println("after safety check.");
    return result;
  }
}

以下是客戶端訪問方式:

public class Client {
  @Test
  public void testCglibProxy() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Suject.class);
    enhancer.setCallback(new SafetyCheckCallback());
    Suject proxy = (Suject) enhancer.create();
    proxy.request();
  }
}

       能夠看到,客戶端代碼中首先建立了一個Enhancer對象,而且設置了父類及代理回調類對象。該Enhancer對象會爲目標類建立相關的子類字節碼,而且將代理代碼植入該子類字節碼中。

3. 總結

       本文主要對代理模式的三種實現方式進行了詳細講解,而且比較了各個代理方式的優缺點,Spring主要使用的是動態代理方式實現切面編程的。這裏讀者可能會有一個疑問,即上述代理代碼中,根據實現方式的不一樣,對客戶端代碼都有必定的侵入性,好比靜態代理客戶端須要侵入代理類的實例,jdk代理須要侵入Proxy類,而Cglib代理則須要侵入子類子類對象建立等代碼。理論上,客戶端只須要獲取目標對象,不管是否爲代理過的,而後調用其相關方法實現特定功能便可。這其實也是工廠方法的強大之處,由於工廠方法會將對象的建立封裝起來,對象的具體建立過程能夠根據具體的業務處理便可,客戶端只須要依賴工廠類調用相關的方法便可。一樣的這也就說明了Spring IoC容器是自然支持AOP代理的緣由,由於其將對象的建立過程交由容器進行了。

相關文章
相關標籤/搜索