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