1. 程式人生 > >模擬JDK動態代理 ; 自己動手模擬實現java動態代理

模擬JDK動態代理 ; 自己動手模擬實現java動態代理

大家在看java設計模式之 代理模式這篇文章的時候, 可以發現動態代理無非就是以下四個步驟,我們完全可以自己模擬實現。因為java的class檔案格式是公開的,只要最終生成的class格式正確並且可以載入到JVM中我們就可以正常使用啦。
1. 建立代理類的原始碼;
2. 對原始碼進行編譯成位元組碼;
3. 將位元組碼載入到記憶體;
4. 例項化代理類物件並返回給呼叫者;

使用聚合模式實現靜態代理

本質上,動態代理是在程式執行過程中建立生成一個類並且將它載入到JVM中,通過上面的實現步驟,他是把額外的程式碼(spring中叫切面)植入到被代理類(方法)中以後合成一個類。與靜態代理的實現是一樣的.

下面這段程式碼就是聚合模式實現的靜態代理:

public interface HelloWorld {

    void sayHello(String name);
}
public class HelloWorldImpl implements HelloWorld {

    @Override  
    public void sayHello(String name) {  
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是RealSubject。 Hello "
+ name); } }
/**
 * Created by liubenlong on 2017/2/4.
 * 聚合模式實現靜態代理
 */
public class HelloWorldProxy implements HelloWorld {

    private HelloWorldImpl helloWorld;

    public HelloWorldProxy(HelloWorldImpl helloWorld){
        this.helloWorld = helloWorld;
    }

    @Override
    public void
sayHello(String name) { System.out.println("before log..."); helloWorld.sayHello(name); System.out.println("end log...。該方法總共執行時間是10毫秒。"); } }

測試程式碼:

HelloWorldProxy helloWorldProxy = new HelloWorldProxy(new HelloWorldImpl());
helloWorldProxy.sayHello("zhangsan");

結果:

before  log...
我是RealSubject。 Hello zhangsan
end  log...。該方法總共執行時間是10毫秒。

使用JDK自帶的JavaCompiler

必須jdk6及以上

假設我們已經組裝好了程式碼(其實就是上面那段靜態代理的程式碼),我們就可以將其載入到JVM中來使用了。

這裡說的組裝程式碼的過程在JDK中是使用反射來完成的。

package com.lbl.proxy;

import org.apache.commons.io.FileUtils;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

public class Proxy {
    private static final String  RT = "\r\n";
    public static Object newProxyInstance(Class classObj) throws Exception{
        //宣告一段原始碼
        String sourceCode =
        "package com.lbl.proxy;" + RT +
        "/**" + RT +
        " * Created by liubenlong on 2017/2/4." + RT +
        "* 聚合模式實現靜態代理" + RT +
        "*/" + RT +
        "public class $Proxy1 implements HelloWorld {" + RT +
        "   private HelloWorld helloWorld;" + RT +
        "   public $Proxy1(HelloWorld helloWorld){" + RT +
        "       this.helloWorld = helloWorld;" + RT +
        "   }" + RT +
        "   @Override" + RT +
        "   public void sayHello(String name) {" + RT +
        "       System.out.println(\"before  log...\");" + RT +
        "       helloWorld.sayHello(name);" + RT +
        "       System.out.println(\"end  log...。該方法總共執行時間是10毫秒。\");" + RT +
        "   }" + RT +
        "}";


        String filename = System.getProperty("user.dir") + "/src/main/java/com/lbl/proxy/$Proxy1.java";
        File file = new File(filename);
        //使用org.apache.commons.io.FileUtils.writeStringToFile()將原始碼寫入磁碟
        //編寫到處,可以執行一下程式,可以在當前目錄中看到生成的.java檔案
        FileUtils.writeStringToFile(file,sourceCode);


        //獲得當前系統中的編譯器
        JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
        System.out.println(complier.getClass().getName());
        //獲得檔案管理者
        StandardJavaFileManager fileMgr = complier.getStandardFileManager(null, null, null);
        Iterable its = fileMgr.getJavaFileObjects(filename);
        //編譯任務
        JavaCompiler.CompilationTask task = complier.getTask(null, fileMgr, null, null, null, its);
        //開始編譯,執行完可在當前目錄下看到.class檔案
        task.call();
        fileMgr.close();


        //load到記憶體
        URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class cls = urlClassLoader.loadClass("com.lbl.proxy.$Proxy1");


        //生成代理類物件
        Constructor ct = cls.getConstructor(HelloWorld.class);//找一個引數型別是HelloWorldImpl.class的構造方法
        return ct.newInstance(new HelloWorldImpl());
    }
}
class test{
    public static void main(String[] args) throws Exception {
        HelloWorld proxyObj = (HelloWorld) Proxy.newProxyInstance(HelloWorld.class);
        proxyObj.sayHello("zhangsan");
    }
}

程式碼的含義上面註釋已經寫得很清楚了。執行一下,可以看到在專案目錄下出現兩個類:$Proxy1.java$Proxy1.class
這裡寫圖片描述

輸出結果:

com.sun.tools.javac.api.JavacTool
before  log...
我是RealSubject。 Hello zhangsan
end  log...。該方法總共執行時間是10毫秒。

深度模擬

上面的模擬程式碼中,只能支援實現了HelloWOrld介面的類。那麼怎麼才能實現與介面無關,支援任何介面,並且將植入的程式碼獨立出來呢?也就是把生成代理類的程式碼獨立出來與業務無關,這也滿足設計模式中的低耦合、職責單一原則。

要想支援任何介面,即生成代理程式碼的類與實際的介面無關。這個比較簡單,因為我們的類都是實現了介面的,而介面中有哪些方法和引數,都是可以通過反射獲取到的,那麼我們只需要將其實現的介面傳遞過去即可。

那麼怎麼將具體的和業務相關的代理邏輯程式碼抽離出來呢?前面提到了反射,反射可以通過Method呼叫某個方法,那麼我們就可以在呼叫方法前後做一些事情了。

通過上面的簡單分析可以發現,關鍵點在於反射。我們可以把介面和代理實現作為引數傳遞進去就好啦。

我們要做的就是把介面中定義的方法通過反射的方式獲取到,進行生成程式碼。並且將其內部實現轉移到Handler中去。Handler類是具體的要植入的程式碼類。就是把生成的下面這段程式碼修改掉:

@Override
   public void sayHello(String name) {
       System.out.println("before  log...");
       helloWorld.sayHello(name);
       System.out.println("end  log...。該方法總共執行時間是10毫秒。");
   }

代理可以有多種多樣,怎麼取寫程式碼呢?每當出現多種不同的實現的時候我們就應該考慮到java的多型了。我們把代理抽象出一個介面MyInvocationHandler,裡面只有一個方法,該方法用於對被代理物件的呼叫。

類圖如下
這裡寫圖片描述

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by liubenlong on 2017/2/4.
 */
public interface MyInvocationHandler {
    public Object invoke(Method method, Object o, Object ... params) throws InvocationTargetException, IllegalAccessException;
}

我們寫兩個實現類:日誌代理類和計時代理類:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by liubenlong on 2017/2/4.
 */
public class MyLogProxy implements MyInvocationHandler {

    @Override
    public Object invoke(Method method, Object o, Object ... params) throws InvocationTargetException, IllegalAccessException {
        System.out.println("aa");
        Object invoke = method.invoke(o, params);
        System.out.println("bb");
        return invoke;
    }
}
package com.lbl.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by liubenlong on 2017/2/4.
 */
public class MyTimeProxy implements MyInvocationHandler {

    @Override
    public Object invoke(Method method, Object o, Object ... params) throws InvocationTargetException, IllegalAccessException{
        long begin = System.currentTimeMillis();
        Object invoke = method.invoke(o, params);
        System.out.println(method.getName() + " 執行總共耗時:"+(System.currentTimeMillis() - begin) + "毫秒。");
        return invoke;
    }
}

修改後的代理生成類:

package com.lbl.proxy;

import org.apache.commons.io.FileUtils;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.atomic.AtomicLong;

/**
 *
 */
public class Proxy2 {
    private static final String  RT = "\r\n";

    public static final AtomicLong proxyNum = new AtomicLong(0);

    /**
     * @param interfaceObj  引數介面
     * @return
     * @throws Exception
     */
    public static Object newProxyInstance(Class interfaceObj, Object o, MyInvocationHandler handler) throws Exception{


        Method[] methods = interfaceObj.getMethods();
        StringBuilder b = new StringBuilder();
        for (Method method : methods) {
            int parameterCount = method.getParameterCount();
            Class<?>[] parameterTypes = method.getParameterTypes();
            StringBuilder paramStr = new StringBuilder();
            StringBuilder paramClasses = new StringBuilder();
            for(int i = 0 ; i < parameterCount ; i ++){
                paramStr.append(parameterTypes[i].getName()).append(" ").append("arg").append(i).append(",");
                paramClasses.append(parameterTypes[i].getName()).append(".class,");
            }

            b.append("   @Override" + RT )
                    .append("   public void "+method.getName()+"(" + paramStr.substring(0, paramStr.length()-1) + ") {" + RT)//這裡我們假設所有方法都沒有引數
                    .append("try {")
                    .append("       Method method = HelloWorld.class.getMethod(\""+method.getName()+"\", "+paramClasses.substring(0, paramClasses.length()-1)+");")
                    .append("       handler.invoke(method, obj, arg0);" + RT )
                    .append("} catch (Exception e) {e.printStackTrace();}")
                    .append("   }" + RT)
            ;
        }

        long num = proxyNum.getAndIncrement();


        //宣告一段原始碼
        String sourceCode =
                "package com.lbl.proxy;" + RT +
                "import java.lang.reflect.Method;" + RT +
                        "/**" + RT +
                        " * Created by liubenlong on 2017/2/4." + RT +
                        "* 聚合模式實現靜態代理" + RT +
                        "*/" + RT +
                        "public class $Proxy"+num+" implements " + interfaceObj.getName() + " {" + RT +
                        "   private "+interfaceObj.getName()+" obj;" + RT +
                        "   private MyInvocationHandler handler;" + RT +
                        "   public $Proxy" + num +"("+interfaceObj.getName()+" obj, MyInvocationHandler handler){" + RT +
                        "       this.obj = obj;" + RT +
                        "       this.handler = handler;" + RT +
                        "   }" + RT +
                        b.toString() +
                        "}";


        String filename = System.getProperty("user.dir") + "/src/main/java/com/lbl/proxy/$Proxy" + num + ".java";
        File file = new File(filename);
        //使用org.apache.commons.io.FileUtils.writeStringToFile()將原始碼寫入磁碟
        //編寫到處,可以執行一下程式,可以在當前目錄中看到生成的.java檔案
        //並不是一定要生成檔案,如果可以直接生成二進位制程式碼load到JVM中,則不必要那麼麻煩生成檔案了
        FileUtils.writeStringToFile(file,sourceCode);


        //獲得當前系統中的編譯器
        JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
        System.out.println(complier.getClass().getName());
        //獲得檔案管理者
        StandardJavaFileManager fileMgr = complier.getStandardFileManager(null, null, null);
        Iterable its = fileMgr.getJavaFileObjects(filename);
        //編譯任務
        JavaCompiler.CompilationTask task = complier.getTask(null, fileMgr, null, null, null, its);
        //開始編譯,執行完可在當前目錄下看到.class檔案
        task.call();
        fileMgr.close();

        //load到記憶體
        URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class cls = urlClassLoader.loadClass("com.lbl.proxy.$Proxy" + num);


        //生成代理類物件
        Constructor ct = cls.getConstructor(interfaceObj, MyInvocationHandler.class);//找一個引數型別是HelloWorldImpl.class的構造方法
        return ct.newInstance(o, handler);
    }
}
class test2{
    public static void main(String[] args) throws Exception {
        HelloWorld proxyObj = (HelloWorld) Proxy2.newProxyInstance(HelloWorld.class, new HelloWorldImpl(), new MyTimeProxy());
        proxyObj.sayHello("zhangsan");
    }
}

執行一下,我們檢視一下輸出結果:

com.sun.tools.javac.api.JavacTool
我是RealSubject。 Hello zhangsan
sayHello 執行總共耗時:1502毫秒。

然後看一下程式碼生成的代理類

import java.lang.reflect.Method;
/**
 * Created by liubenlong on 2017/2/4.
* 聚合模式實現靜態代理
*/
public class $Proxy0 implements com.lbl.proxy.HelloWorld {
   private com.lbl.proxy.HelloWorld obj;
   private MyInvocationHandler handler;
   public $Proxy0(com.lbl.proxy.HelloWorld obj, MyInvocationHandler handler){
       this.obj = obj;
       this.handler = handler;
   }
   @Override
   public void sayHello(java.lang.String arg0) {
        try {       
            //最關鍵的兩行程式碼
            Method method = HelloWorld.class.getMethod("sayHello", java.lang.String.class);       
            handler.invoke(method, obj, arg0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

到此為止,代理類可以永遠不需要改變了。

每當需要新的代理需求,則編寫一個實現了MyInvocationHandler的類就行,然後呼叫也相當的方便,只需要修改對應的這一行程式碼的引數即可:HelloWorld proxyObj = (HelloWorld) Proxy2.newProxyInstance(HelloWorld.class, new HelloWorldImpl(), new MyTimeProxy());

相關推薦

模擬JDK動態代理 ; 自己動手模擬實現java動態代理

大家在看java設計模式之 代理模式這篇文章的時候, 可以發現動態代理無非就是以下四個步驟,我們完全可以自己模擬實現。因為java的class檔案格式是公開的,只要最終生成的class格式正確並且可以載入到JVM中我們就可以正常使用啦。 1. 建立代

自己動手模擬開發一個簡單的Web伺服器

開篇:每當我們將開發好的ASP.NET網站部署到IIS伺服器中,在瀏覽器正常瀏覽頁面時,可曾想過Web伺服器是怎麼工作的,其原理是什麼?“紙上得來終覺淺,絕知此事要躬行”,於是我們自己模擬一個簡單的Web伺服器來體會一下。 一、請求-處理-響應模型 1.1 基本過程介紹 每一個HTTP請求都會經

自己動手實現“你的名字”濾鏡

height 使用 圖片 很好 board courier mage margin ges 我喜歡《你的名字》這個故事,前一段時間在微信上使用過它的濾鏡,實現的效果很驚艷,應該類似於下面的這些結果 這三幅圖應該都是手機版本制作的,它們一個比較顯著的特點

【原創】自己動手循序漸進實現觀察者模式

接口 定義 。。 推導 ole com package exce ++ 引言 自上一篇《自己動手實現牛逼的單例模式》問世之後,得到了不錯的評價。於是博主在五一放棄出去遊玩機會,趕制了這篇《自己動手循序漸進實現觀察者模式》,依然還是一步一步推導出最終版的觀察者模式。 觀察者模

自己動手程式設計實現並講解TCP connect scan/TCP stealth scan/TCP XMAS scan/UDP scan

實驗5 自己動手程式設計實現並講解TCP connect scan/TCP stealth scan/TCP XMAS scan/UDP scan 實驗工具 scapy version 2.4.0 ipython version 5.5.0 netc

自己動手實現簡單的C++ smart pointer

Why Smart Pointer? 為什麼需要智慧指標?因為c++的記憶體管理一直是個令人頭疼的問題。 假如我們有如下person物件:每個person有自己的名字,並且可以告訴大家他叫什麼名字: // //a person who can tell us his/her name. // #inclu

JAVA動態代理 你真的完全瞭解Java動態代理嗎?

網上講JAVA動態代理,說的天花亂墜,發現一篇文章寫的通俗易懂,特意轉載過來 原文地址:https://www.jianshu.com/p/95970b089360   動態代理看起來好像是個什麼高大上的名詞,但其實並沒有那麼複雜,直接從字面就很容易理解。動態地代理,可以猜測一下它的含義,在執行時

自己動手實現簡單的事務管理(動態代理+註解)一

使用原生的servlet,沒有使用框架。運用動態代理技術手動實現service層的事務管理。並增加註解功能,用於查詢時無需事務管理。 事務管理需要保證連線Connection的一致,即dao層和service層的connection是同一個,這就需要用到Thr

自己動手實現簡單的事務管理(動態代理+註解)二

上一篇介紹了ThreadLocalJDBCUtils(用於獲得與執行緒繫結的connection的工具類)。不熟悉的可以看下面的連結。 自己動手實現簡單的事務管理(動態代理+註解)一 動態代理不做贅述,下面的連結希望可以幫助你理解。 動態代理快速理解 我

手動模擬JDK動態代理

為哪些方法代理? 實現自己動態代理,首先需要關注的點就是,代理物件需要為哪些方法代理? 原生JDK的動態代理的實現是往上抽象出一層介面,讓目標物件和代理物件都實現這個介面,怎麼把介面的資訊告訴jdk原生的動態代理呢? 如下程式碼所示,Proxy.newProxyInstance()方法的第二個引數將介面的資訊

基於JDK實現動態代理

  JDK動態代理是基於java.lang.reflect.*包提供的方式,他必須藉助一個接口才能產生代理物件,所以先定義介面: 實現類 ​​​​​ 此時可以開始實現動態代理了,首先建立起真實物件和代理物件的關係,然後實現代理邏輯。

jdk動態代理與cglib程式碼實現--SpringAop底層原理

動態代理分為兩類:基於介面的代理和基於繼承的代理 兩類實現的代表是:JDK代理 與 CGlib代理 cglib實現動態代理: 1、定義目標物件: public class RealSubject { //目標物件RealSubject,cglib不

Java動態代理JDK實現和CGlib實現

     原文地址:http://www.cnblogs.com/ygj0930/p/6542259.html       一:代理模式(靜態代理)    &nbs

【轉載】Java動態代理JDK實現和CGlib實現(簡單易懂)

      原文地址:http://www.cnblogs.com/ygj0930/p/6542259.html       一:代理模式(靜態代理)           代理模式是常用設計模式的一種,我們在軟體設計時常用的代理一般是指靜態代理,也就是在程式碼中顯式指定的

模擬大華或海康相機的迴圈&&動態連結庫的形式實現&&回撥函式的使用

1.動態連結庫封裝函式 Dll的cpp檔案 #include <iostream> #include <windows.h> using namespace std; l

AOP的底層實現-CGLIB動態代理JDK動態代理

        AOP是目前Spring框架中的核心之一,在應用中具有非常重要的作用,也是Spring其他元件的基礎。它是一種面向切面程式設計的思想。關於AOP的基礎知識,相信多數童鞋都已經瞭如指掌,我們就略過這部分,來講解下AOP的核心功能的底層實現機制:如何用動態代理來

Java動態代理JDK實現和CGlib實現(簡單易懂)

原文地址:http://www.cnblogs.com/ygj0930/p/6542259.html 一:代理模式(靜態代理)           代理模式是常用設計模式的一種,我們在軟體設計時常用的代理一般是指靜態代理,也就是在程式碼中顯式指定的代理。      

理解Java 動態代理和AOP(可以自己動手寫AOP框架!)

說到AOP,很容易就想到 spring AOP。因為它是AOP裡最優秀的實現框架。但本文不打算討論 spring AOP,只想把如何通過動態代理方式實現AOP思想說通。當然,整明白了這個道理,理解 spring AOP 也就簡單了! 首先我覺得需特別強調一下什麼是面向介面程

JDK動態代理與Dubbo自實現動態代理的研究

1、何為代理?         為了增強目標物件(委託物件)功能,在訪問目標物件的路徑上增加控制訪問物件,該物件負責目標物件執行前後的 附加功能, 該訪問控制物件即為代理物件, 這種設計模式即為代理!

動態代理及其兩種實現方式(JDK、CGLIB)

什麼是代理模式 為某物件提供一個代理,從而通過代理來訪問這個物件。 代理模式的角色組成 代理模式有三種角色組成: 抽象角色:通過介面或抽象類宣告真實角色實現的業務方法。 代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象