首頁>Club>
21
回覆列表
  • 1 # 淘課之家

    Java動態代理實現方式一

    Java代理設計模式(Proxy)的四種具體實現:靜態代理和動態代理

    實現方式一:靜態代理靜態代理方式的優點靜態代理方式的缺點Java動態代理實現方式一:InvocationHandlerJava動態代理實現方式二:CGLIB用CGLIB實現Java動態代理的侷限性

    面試問題:Java裡的代理設計模式(Proxy Design Pattern)一共有幾種實現方式?這個題目很像孔乙己問“茴香豆的茴字有哪幾種寫法?

    所謂代理模式,是指客戶端(Client)並不直接呼叫實際的物件(下圖右下角的RealSubject),而是透過呼叫代理(Proxy),來間接的呼叫實際的物件。

    代理模式的使用場合,一般是由於客戶端不想直接訪問實際物件,或者訪問實際的物件存在技術上的障礙,因而透過代理物件作為橋樑,來完成間接訪問。

    實現方式一:靜態代理

    開發一個介面IDeveloper,該介面包含一個方法writeCode,寫程式碼。

    public interface IDeveloper {

    public void writeCode();

    }

    建立一個Developer類,實現該介面。

    public class Developer implements IDeveloper{

    private String name;

    public Developer(String name){

    this.name = name;

    }

    @Override

    public void writeCode() {

    System.out.println("Developer " + name + " writes code");

    }

    }

    測試程式碼:建立一個Developer例項,名叫Jerry,去寫程式碼!

    public class DeveloperTest {

    public static void main(String[] args) {

    IDeveloper jerry = new Developer("Jerry");

    jerry.writeCode();

    }

    }

    為了強迫每個程式設計師在開發時記著寫文件,而又不影響大家寫程式碼這個動作本身, 我們不修改原來的Developer類,而是建立了一個新的類,同樣實現IDeveloper介面。這個新類DeveloperProxy內部維護了一個成員變數,指向原始的IDeveloper例項:

    public class DeveloperProxy implements IDeveloper{

    private IDeveloper developer;

    public DeveloperProxy(IDeveloper developer){

    this.developer = developer;

    }

    @Override

    public void writeCode() {

    System.out.println("Write documentation...");

    this.developer.writeCode();

    }

    }

    這個代理類實現的writeCode方法裡,在呼叫實際程式設計師writeCode方法之前,加上一個寫文件的呼叫,這樣就確保了程式設計師寫程式碼時都伴隨著文件更新。

    測試程式碼:

    靜態代理方式的優點

    1. 易於理解和實現

    2. 代理類和真實類的關係是編譯期靜態決定的,和下文馬上要介紹的動態代理比較起來,執行時沒有任何額外開銷。

    靜態代理方式的缺點

    每一個真實類都需要一個建立新的代理類。還是以上述文件更新為例,假設老闆對測試工程師也提出了新的要求,讓測試工程師每次測出bug時,也要及時更新對應的測試文件。那麼採用靜態代理的方式,測試工程師的實現類ITester也得建立一個對應的ITesterProxy類。

    public interface ITester {

    public void doTesting();

    }

    Original tester implementation class:

    public class Tester implements ITester {

    private String name; public Tester(String name){

    this.name = name;

    }

    @Override

    public void doTesting() {

    System.out.println("Tester " + name + " is testing code");

    }

    }

    public class TesterProxy implements ITester{

    private ITester tester;

    public TesterProxy(ITester tester){

    this.tester = tester;

    }

    @Override

    public void doTesting() {

    System.out.println("Tester is preparing test documentation..."); tester.doTesting();

    }

    }

    正是因為有了靜態程式碼方式的這個缺點,才誕生了Java的動態代理實現方式。

    Java動態代理實現方式一:InvocationHandler

    InvocationHandler的原理我曾經專門寫文章介紹過:Java動態代理之

    InvocationHandler最簡單的入門教程

    透過InvocationHandler, 我可以用一個EnginnerProxy代理類來同時代理Developer和Tester的行為。

    public class EnginnerProxy implements InvocationHandler {

    Object obj;

    public Object bind(Object obj) {

    this.obj = obj;

    return Proxy.newProxyInstance(obj.getClass().getClassLoader(),

    obj .getClass().getInterfaces(),

    this);

    }

    @Override

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

    System.out.println("Enginner writes document");

    Object res = method.invoke(obj, args);

    return res;

    }

    }

    真實類的writeCode和doTesting方法在動態代理類裡透過反射的方式進行執行。

    測試輸出:

    透過InvocationHandler實現動態代理的侷限性

    假設有個產品經理類(ProductOwner) 沒有實現任何介面。

    public class ProductOwner {

    private String name; public ProductOwner(String name){

    this.name = name;

    }

    public void defineBackLog(){

    System.out.println("PO: " + name + " defines Backlog.");

    }

    }

    我們仍然採取EnginnerProxy代理類去代理它,編譯時不會出錯。執行時會發生什麼事?

    ProductOwner po = new ProductOwner("Ross");

    ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po);

    poProxy.defineBackLog();

    執行時報錯。所以侷限性就是:如果被代理的類未實現任何介面,那麼不能採用透過InvocationHandler動態代理的方式去代理它的行為。

    Java動態代理實現方式二:CGLIB

    CGLIB是一個Java位元組碼生成庫,提供了易用的API對Java位元組碼進行建立和修改。關於這個開源庫的更多細節,請移步至CGLIB在github上的倉庫:

    https://github.com/cglib/cglib

    我們現在嘗試用CGLIB來代理之前採用InvocationHandler沒有成功代理的ProductOwner類(該類未實現任何介面)。

    現在我改為使用CGLIB API來建立代理類:

    public class EnginnerCGLibProxy {

    Object obj;

    public Object bind(final Object target) {

    this.obj = target;

    Enhancer enhancer = new Enhancer();

    enhancer.setSuperclass(obj.getClass());

    enhancer.setCallback(new MethodInterceptor() {

    @Override

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

    System.out.println("Enginner 2 writes document"); Object res = method.invoke(target, args); return res;

    }

    }

    );

    return enhancer.create();

    }

    }

    測試程式碼:

    ProductOwner ross = new ProductOwner("Ross");

    ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross);

    rossProxy.defineBackLog();

    儘管ProductOwner未實現任何程式碼,但它也成功被代理了:

    用CGLIB實現Java動態代理的侷限性

    如果我們瞭解了CGLIB建立代理類的原理,那麼其侷限性也就一目瞭然。我們現在做個實驗,將ProductOwner類加上final修飾符,使其不可被繼承:

    再次執行測試程式碼,這次就報錯了: Cannot subclass final class XXXX。

    所以透過CGLIB成功建立的動態代理,實際是被代理類的一個子類。那麼如果被代理類被標記成final,也就無法透過CGLIB去建立動態代理。

    Java動態代理實現方式三:透過編譯期提供的API動態建立代理類

    假設我們確實需要給一個既是final,又未實現任何介面的ProductOwner類建立動態程式碼。除了InvocationHandler和CGLIB外,我們還有最後一招:

    我直接把一個代理類的原始碼用字串拼出來,然後基於這個字串呼叫JDK的Compiler(編譯期)API,動態的建立一個新的.java檔案,然後動態編譯這個.java檔案,這樣也能得到一個新的代理類。

    測試成功:

    我拼好了程式碼類的原始碼,動態建立了代理類的.java檔案,能夠在Eclipse裡開啟這個用程式碼建立的.java檔案,

    下圖是如何動態建立ProductPwnerSCProxy.java檔案:

    下圖是如何用JavaCompiler API動態編譯前一步動態創建出的.java檔案,生成.class檔案:

    下圖是如何用類載入器載入編譯好的.class檔案到記憶體:

  • 2 # Remindme276

    動態代理

    java裡的代理模式,就是指客戶端(Client)不直接去呼叫實際的物件,而是透過中間者代理(Proxy),來間接的呼叫實際的物件,透過代理物件作為橋樑,來完成間接訪問。

    看下面簡單的例子:

    開發一個介面,包含兩個方法,可以向指定的人問候“sayHello”者“sayGoodBye"

    public interface ISay {

    void sayHello(String name);

    void sayGoodBye(String name);

    }

    建立一個簡單的類,實現這個ISay介面。

    public class SayImplements implements ISay {

    @Override

    public void sayHello(String name) {

    System.out.println("Hello " + name);

    }

    @Override

    public void sayGoodBye(String name) {

    System.out.println(name+" GoodBye!");

    }

    }

    接著有個需求就是實現一個每次問候某人時,把問候的細節打印出來。

    直接修改的方式是:

    public class SayImplements implements ISay {

    @Override

    public void sayHello(String name) {

    System.out.println("say Hello 之前的細節操作。。");

    System.out.println("Hello " + name);

    }

    @Override

    public void sayGoodBye(String name) {

    System.out.println(name+" GoodBye!");

    }

    }

    然而不允許你修改原來的SayImplements類。在現實場景中,SayImplements可能是第三方的jar包提供的,這時候我們就會建立一個新的Java類作為代理。同樣實現ISay介面,然後將SayImplements類的例項傳入代理類

    public class Proxy implements ISay {

    private ISay isay;

    public void setImpl(ISay impl){

    this.isay = impl;

    }

    @Override

    public void sayHello(String name) {

    System.out.println("say Hello 之前的細節操作。。");

    isay.sayHello(name);

    }

    @Override

    public void sayGoodBye(String name) {

    System.out.println("問候之前的日誌記錄...");

    isay.sayGoodBye(name);

    }

    static public void main(String[] arg) {

    SayImplements hello = new SayImplements();

    Proxy proxy = new Proxy();

    proxy.setImpl(hello);

    proxy.sayHello("Jerry");

    }

    }

    自己模擬實現java的動態代理了,類似InvocationHandler實現同樣的效果。

  • 3 # 碼農的一天

    自動生成Proxy,然後使用自定義類載入器載入

    定義InvocationHandler介面

    2.實現 YpProxy代理類

    3.自定義ClassLoader,從指定目錄載入檔案

    4.測試

    (1) 實現YpInvocationHandler介面

    (2)定義UserServer介面

    (3)定義UserService實現類

    (4)定義測試類

    結果如下:

    使用Javassist模擬實現JDK的動態代理

    1.加入javassist jar包

    2.定義InvocationHandler介面

    測試如下

  • 中秋節和大豐收的關聯?
  • 你覺得人類對兩極地區的開發是利還是弊?