代理模式(二) ---- 完成對所有接口的所有方法實現代理

上一篇文章我們講了靜態代理的實現方式,并比較了聚合實現靜態代理和繼承實現效果的不同。今天我們來逐步實現動態代理,并模仿 JDK 動態代理的實現。


  1. 什么是動態代理?

    我們首先考慮,之前我們是通過編寫新的類實現與被代理對象同樣的接口最終來達到目的的,這樣對于每一個被代理對象,我們都要寫一個新的代理類,這樣的方式肯定不好,如果有這樣一個類,只要我們傳進去被代理對象,它能自動幫助我們生成代理對象,那不就好了嗎? Client 客戶端的要求如下:

    Tank tank = new Tank() ;
    Moveable tankTimeProxy = (Moveable)Proxy.newInstance(tank) ;
    
  2. 動態代理所需要的步驟:

    • 既然要求自動幫助我們生成被代理對象,必須要解決的是:生成被代理類,編譯被代理類,將該類 load 到內存中,生成代理對象。所以我們先以上一篇的時間代理為例子,來解決這個問題:
    public class Proxy {
        public static Moveable newInstance(Moveable m) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            // 要生成的代理類,以字符串的形式寫出
            String str =
                    "package dynamicProxy ;" +
                            "public class TankTimeProxy implements Moveable {\n" +
                            "    public TankTimeProxy(Moveable m) {\n" +
                            "        this.m = m;\n" +
                            "    }\n" +
                            "\n" +
                            "    private Moveable m ;\n" +
                            "\n" +
                    "    @Override\n" +
                    "    public void move() throws InterruptedException {\n" +
                    "        long startTime = System.currentTimeMillis() ;\n" +
                    "        m.move();\n" +
                    "        long endTime = System.currentTimeMillis() ;\n" +
                    "        System.out.println(\"time : \" + (endTime - startTime));\n" +
                    "    }\n" +
                    "}\n" ;
    
    
            // 將定義好的被代理類寫入文件中
            // System.getProperty("user.dir") 為獲取當前項目的路徑
            String fileName = System.getProperty("user.dir") +   "/src/dynamicProxy/TankTimeProxy.java" ; 
            File file = new File(fileName) ;
            FileWriter fw = new FileWriter(file) ;
            fw.write(str);
            fw.flush();
            fw.close();
            
            // 編譯被代理類,這里的實現是才用的 JDK6 之后的版本所提供的編譯器
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler() ;
            // 該編譯器的代碼需要交由 StandardJavaFileManager 來管理
            StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null,null,null)  ;
            // 獲取所要編譯的單元
            Iterable units = standardJavaFileManager.getJavaFileObjects(fileName) ;
            // 獲取編譯任務
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null,standardJavaFileManager,null,null,null,units) ;
            // 執行編譯任務
            task.call() ;
            standardJavaFileManager.close();
    
            // 將編譯好的代理類 load 到內存中并創建代理對象
            
            // 獲取我們編譯好的 class 文件路徑
            URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") +  "/src/")} ;
            // 獲取 classLoader
            URLClassLoader ucl = new URLClassLoader(urls) ;
            //  laod 到內存中
            Class c = ucl.loadClass("dynamicProxy.TankTimeProxy") ;
            // 獲取參數為 Moveable 的構造器
            Constructor constructor = c.getConstructor(Moveable.class) ;
            // 實例化代理對象
            Moveable proxy = (Moveable) constructor.newInstance(new Tank());
    
            return proxy ;
        }
    }
    

    通過以上代碼,我們就解決了系統生成代理對象所需要的步驟。那么問題來了,現在我們只能實現靜態的代理,如何實現動態代理呢?

    • 接著我們先來進行動態代理不同的接口的實現。這里要考慮的問題應該是,對于被代理的對象,我們首先要獲取其所有的方法,并對其所有的方法實現代理,所以就要修改一部分代理類的代碼了。
      • 首先是方法體的定義,應該改為接收被代理對象的接口類,即改:
      public static Moveable newInstance(Moveable m)
      
      為 :
      public static Object newInstance(Class inface)
      
      • 獲取該接口中所有的方法:(這里涉及到 java 的反射機制)
      Method[] methods = Method[] methods = inface.getMethods() ; 
      
      • 拼接代理類代碼的方法字符串:
      String methodStr ="" ;
      for(Method m : methods){
          methodStr +=
                  "    @Override\n" +
                  "    public void " + m.getName() + "() {\n" +
                  "        try{\n" +
                  "            Method md = " + inface.getName() + ".class.getMethod(\""+ m.getName() +"\");\n" +
                  "            handler.invoke(this,md) ;\n" +
                  "        } catch (Exception e){\n" +
                  "            e.printStackTrace() ;\n" +
                  "        }\n" +
                  "    }" ;
      }
      
      這里也許很多人會有疑問,
      Method md = " + inface.getName() + ".class.getMethod(\""+ m.getName() +"\");\n" 這里為什么不能用 Method md = m 來代替呢?其實這里涉及到內外層代碼的嵌套問題。 methodStr 是為了生成代理類的字符串,其包含的 method 是內層代碼,要獲取其方法,就要通過傳進去的接口及方法名來獲取,而 m 是我們的生成代碼,是外層代碼。詳細的區別,大家可以具體去代碼里面嘗試一下,看看生成的代理類的區別就應該可以看出來了。
      • 修改生成代碼 str ,加入方法字符串,并修改實現的接口:
      String str =
              "package dynamicProxy ;\n" +
              "import java.lang.reflect.Method; \n" +
              "public class TankTimeProxy implements " + inface.getName() + " {\n" +
              "    public TankTimeProxy(Moveable m) {\n" +
              "        this.m = m;\n" +
              "    }\n" +
              "\n" +
              "    private " + inface.getName + " m ;\n" +
              "\n" +
              methodStr +
              "}\n" ;
      
      
      • 修改構造器的參數和返回的對象:
      Constructor constructor = c.getConstructor(inface) ;
      Object proxy =  constructor.newInstance(new Tank());
      

到了這里,我們就完成了能對所有的接口的所有方法進行代理。但還有一個問題,就是代理的邏輯已經被寫死了,所以下一步我們要完成的就是實現能夠自定義代理邏輯的動態代理了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,914評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,764評論 18 399
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,768評論 0 9
  • 父母總是告誡女孩子:“找個他愛你比你愛他更多的人在一起,這樣會比較輕松一點”。 網上熱映的“He is just ...
    皮卡丘閱讀 683評論 0 3
  • 我想帶你去看的,是不一樣的重慶 里約奧運會女子100仰泳決賽中 中國選手傅園慧再度爆發洪荒之力 以58秒76摘得銅...
    用正確的姿勢打開重慶閱讀 230評論 0 0