我的認爲動態代理在設計模式中算是比較難的, 本篇文章將從無到有, 從一個簡單代碼示例開始迭代, 逐步深刻講解動態代理思想.java
Moveable
接口, 裏面有一個move()
移動的方法. 代碼以下:class Tank implements Moveable{
@Override
public void move(){
System.out.println("坦克開始移動...");
try {
Thread.sleep((long) (Math.random() * 5000));
System.out.println("坦克移動結束...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
interface Moveable{
public void move();
}
複製代碼
move()
方法的先後添加一些代碼, 用於記錄坦克move()
方法的執行時間.繼承
的方式進行代理.class MoveTimeProxy1 extends Tank{
@Override
public void move() {
long start = System.currentTimeMillis();//開始時間
super.move();//調用坦克的move()方法
long end = System.currentTimeMillis();//結束時間
System.out.println("執行該方法用了" + (end - start) + "毫秒");
}
}
複製代碼
聚合
實現代理的方式class MoveTimeProxy2 implements Moveable{
Tank tank;
public MoveTimeProxy2(Tank tank){
this.tank = tank;
}
@Override
public void move() {
long start = System.currentTimeMillis();//開始時間
tank.move();//調用坦克的move()方法
long end = System.currentTimeMillis();//結束時間
System.out.println("執行該方法用了" + (end - start) + "毫秒");
}
}
複製代碼
繼承
方式的代理會差一些. 想一想看, 若是如今除了記錄時間, 還要記錄日誌的話, 則要建立一個新的繼承
代理類並重寫move()
方法. 若是需求變動, 須要先記錄日誌, 再記錄時間的話, 又要建立一個新的繼承
代理類. 如此下去, 代理類的建立將沒完沒了.聚合
實現的代理類則靈活得多. 每個聚合
代理類可以實現一種代理, 而且代理的順序是能夠替換的. 請看代碼(聚合
代理類的代碼有所修改)public class ProxyTest {
public static void main(String[] args) {
TimeProxy tp = new TimeProxy(new Tank());
LogProxy lp = new LogProxy(tp);
lp.move();
}
}
class TimeProxy implements Moveable{//記錄時間的代理
Moveable m;//再也不持有Tank引用, 而是持有Moveable接口引用
public TimeProxy(Moveable m){
this.m = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();//開始時間
m.move();//調用move()方法
long end = System.currentTimeMillis();//結束時間
System.out.println("執行該方法用了" + (end - start) + "毫秒");
}
}
class LogProxy implements Moveable{//打印日誌的代理
Moveable m;
public LogProxy(Moveable m){
this.m = m;
}
@Override
public void move() {
System.out.println("日誌: 開始測試坦克移動...");
m.move();
System.out.println("日誌: 坦克移動結束...");
}
}
複製代碼
代理
一詞有更深入的理解. 可是上面的代碼中, 爲坦克生成的代理類TimeProxy
是咱們在代碼中寫死的, 因此這頂多算個靜態代理, 如何經過動態的方式產生代理呢?聚合
代理方式經過持有某個接口的引用完成代理, 因此咱們是針對某個接口產生代理, 而不是對某個具體的對象產生代理.Java
中的實現, 咱們建立一個Proxy
類, 裏面提供一個newProxyInstance()
方法, 用於返回一個代理. 咱們但願經過以下代碼就能動態生成一個代理.public static void main(String[] args) {
Tank tank = new Tank();
Moveable m = (Moveable)Proxy.newProxyInstance();//動態得到一個代理
m.move();
}
複製代碼
newProxyInstance()
方法中, 咱們先把原來TimeProxy
的源代碼以字符串的方式存放, 再經過寫入文件的方式建立出TimeProxy.java
文件. 而後經過Java原生的編譯api將TimeProxy.java
編譯成TimeProxy.class
文件. 最後把該class文件加載到內存中, 並調用其構造方法建立對象, 返回該代理對象.class Proxy{
public static Object newProxyInstance() throws Exception {
//把整個TimeProxy類的實現寫入字符串, 經過編譯這一字符串獲得TimeProxy對象
String src = "package designPattern.proxy;\n" +
"\n" +
"class TimeProxy implements Moveable{\n" +
" Moveable m;//再也不持有Tank引用, 而是持有Moveable接口引用\n" +
"\n" +
" public TimeProxy(Moveable m){\n" +
" this.m = m;\n" +
" }\n" +
"\n" +
" @Override\n" +
" public void move() {\n" +
" long start = System.currentTimeMillis();//開始時間\n" +
" m.move();//調用坦克的move()方法\n" +
" long end = System.currentTimeMillis();//結束時間\n" +
" System.out.println(\"執行該方法用了\" + (end - start) + \"毫秒\");\n" +
" }\n" +
"}";
String filename = System.getProperty("user.dir")
+ "/src/main/java/designPattern/proxy/TimeProxy.java";//文件名(生成類的路徑)
File f = new File(filename);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//編譯
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系統當前默認的編譯器, 即Javac
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(filename);//獲得文件對象
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();//進行編譯
fileManager.close();
//把class文件加載進內存並建立對象
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("designPattern.proxy.TimeProxy");//拿到class對象
Constructor ctr = c.getConstructor(Moveable.class);//拿到參數爲Moveable的構造方法
Moveable m = (Moveable)ctr.newInstance(new Tank());//建立代理對象
return m;
}
}
複製代碼
moveable
接口的代理對象. 而上面咱們提到過動態代理是基於某個接口的(聚合型代理), 因此咱們但願可以動態地指定接口, 並生成相應的代理類.public class ProxyTest {
public static void main(String[] args) throws Exception {
Tank tank = new Tank();
Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class);//傳入接口參數動態得到一個代理
m.move();
}
}
class Proxy{
public static Object newProxyInstance(Class intfce) throws Exception {
//把整個TimeProxy類的實現寫入字符串, 經過編譯這一字符串獲得TimeProxy對象
String methodStr = "";
String n = "\n";
Method[] methods = intfce.getMethods();//拿到接口中的全部方法
for(Method m : methods){//拼接方法
methodStr += " @Override\n" +
" public void " + m.getName() + "() {\n" +
" long start = System.currentTimeMillis();//開始時間\n" +
" m.move();//調用坦克的move()方法\n" +
" long end = System.currentTimeMillis();//結束時間\n" +
" System.out.println(\"執行該方法用了\" + (end - start) + \"毫秒\");\n" +
" }\n";
}
//拼接出整個類
String src = "package designPattern.proxy;\n" +
"\n" +
"class TimeProxy implements " + intfce.getName() + "{\n" +
" Moveable m;//再也不持有Tank引用, 而是持有Moveable接口引用\n" +
"\n" +
" public TimeProxy(Moveable m){\n" +
" this.m = m;\n" +
" }\n" +
"\n" + methodStr +
"}";
String filename = System.getProperty("user.dir")
+ "/src/main/java/designPattern/proxy/TimeProxy.java";//文件名(生成類的路徑)
File f = new File(filename);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//編譯
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系統當前默認的編譯器, 即Javac
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(filename);//獲得文件對象
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();//進行編譯
fileManager.close();
//把class文件加載進內存並建立對象
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("designPattern.proxy.TimeProxy");//拿到class對象
Constructor ctr = c.getConstructor(Moveable.class);//拿到參數爲Moveable的構造方法
Object m = ctr.newInstance(new Tank());//建立代理對象
return m;
}
}
複製代碼
TimeProxy
), 輸出日誌(LogProxy
), 事務操做等等.InvocationHandler
接口, 並在它的實現類中給出具體的操做. 咱們以初始的記錄時間操做爲例.public class ProxyTest {
public static void main(String[] args) throws Exception {
Tank tank = new Tank();
InvocationHandler h = new TimeHandler(tank);
Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class, h);//動態得到一個代理
m.move();
}
}
interface InvocationHandler{
public void invoke(Object o, Method m);//參數o指定執行對象(代理對象, 可能會用到), m指定執行的方法
}
class TimeHandler implements InvocationHandler{
private Object target;
public TimeHandler(Object target){
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
long start = System.currentTimeMillis();//這行是用戶本身加的加強代碼
try{
m.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();//這行是用戶本身加的加強代碼
System.out.println("執行該方法用了" + (end - start) + "毫秒");//這行是用戶本身加的加強代碼
}
}
class Proxy{
public static Object newProxyInstance(Class intfce, InvocationHandler h) throws Exception {
//把整個TimeProxy類的實現寫入字符串, 經過編譯這一字符串獲得TimeProxy對象
String methodStr = "";
Method[] methods = intfce.getMethods();//拿到接口中的全部方法
for(Method m : methods){//拼接方法
methodStr += " @Override\n" +
" public void " + m.getName() + "() {\n" +
" try{\n" +
" Method md = " + intfce.getName() + ".class.getMethod(\"" + m.getName() + "\");\n" +
" h.invoke(this, md);\n" +
" }catch(Exception e){e.printStackTrace();}\n" +
" }\n";
}
//拼接出整個類
String src = "package designPattern.proxy;\n" +
"import java.lang.reflect.Method;\n" +
"\n" +
"class $Proxy1 implements " + intfce.getName() + "{\n" +
" designPattern.proxy.InvocationHandler h;\n" +
"\n" +
" public $Proxy1(InvocationHandler h){\n" +
" this.h = h;\n" +
" }\n" +
"\n" + methodStr +
"}";
String filename = System.getProperty("user.dir")
+ "/src/main/java/designPattern/proxy/$Proxy1.java";//文件名(生成類的路徑)
File f = new File(filename);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//編譯
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系統當前默認的編譯器, 即Javac
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(filename);//獲得文件對象
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();//進行編譯
fileManager.close();
//把class文件加載進內存並建立對象
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("designPattern.proxy.$Proxy1");//拿到class對象
Constructor ctr = c.getConstructor(InvocationHandler.class);//拿到參數爲Moveable的構造方法
Object m = ctr.newInstance(h);//建立代理對象
return m;
}
}
class Tank implements Moveable{
@Override
public void move(){
System.out.println("坦克開始移動...");
try {
Thread.sleep((long) (Math.random() * 5000));
System.out.println("坦克移動結束...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//輸出結果
坦克開始移動...
坦克移動結束...
執行該方法用了4302毫秒
複製代碼
Moveable
接口, 而Tank
坦克類實現了Moveable
接口, 並實現了move()
方法.move()
方法進行加強, 好比說記錄這個方法的執行時間, 咱們須要動態地得到一個代理類.InvocationHandler
接口, 裏面有一個invoke(Object o, Method m)
方法. 調用invoke()
方法時, 須要傳遞兩個參數, 一個是代理對象的引用o
(可能會用上), 另外一個是須要被加強的方法, 本例中是move()
方法.invoke()
方法中咱們能夠在被加強方法的先後添加加強代碼.public void invoke(Object o, Method m) {
long start = System.currentTimeMillis();//這行是用戶本身加的加強代碼
try{
m.invoke(target);//執行被加強的方法, 例子中的move()
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();//這行是用戶本身加的加強代碼
System.out.println("執行該方法用了" + (end - start) + "毫秒");//這行是用戶本身加的加強代碼
}
複製代碼
InvocationHandler
的具體對象, 好比這裏的TimeHander
, 須要傳入被加強的對象, 這裏是tank
, 由於被加強方法move()
須要由被加強對象執行.
InvocationHandler
後, 回頭看爲咱們動態產生代理的Proxy
類, 這個類須要有一個屬性字段InvocationHandler h
, 由於在進行加強時, 調用的是InvocationHandler
實現類中的invoke()
方法. 在動態代理進階
一節的最後版本代碼中, 咱們動態生成的代理類源碼是這樣的:class $Proxy1 implements designPattern.proxy.Moveable{
designPattern.proxy.InvocationHandler h;
public $Proxy1(InvocationHandler h){
this.h = h;
}
@Override
public void move() {
try{
Method md = designPattern.proxy.Moveable.class.getMethod("move");
h.invoke(this, md);
}catch(Exception e){e.printStackTrace();}
}
}
複製代碼
move()
方法進行加強時, 會調用InvocaitonHandler
的實現類中的invoke()
方法, 傳入代理類自身和被加強的方法, 這樣就可使用自定義的加強代碼進行加強了.