Java-馬士兵動態代理模式
模擬jdk的動態代理的實現原理, 這些東西沒有必要寫出來,寫項目的時候一般用不上,主要是為了面試和理解原理;
java動態代理有什么作用
作用非常大,在很多底層框架中都會用得到,比如struts,Spring等都用到了動態代理,它的作用很簡單,就是將你要使用的類,重新生成一個子類或本類,這樣框架就可以利用這個新生成的類做一些事情,比如在該類的方法前后加一些代碼。。
這樣的話,你想像一下,你是不是不用修改任何已經編寫好的代碼,只要使用代理就可以靈活的加入任何東西,將來不喜歡了,不用也不會影響原來的代碼。
https://www.zhihu.com/question/20794107/answer/23334315
代理模式-聚合與繼承方式比較
參考地址:http://www.cnblogs.com/shamgod/p/4591782.html
一、概述
1.目標:要在Tank的move()方法做時間代理及日志代理(可以設想以后還要增加很多代理處理),且代理間的順序可活更換
2.思路:
(1)聚合:代理類聚合了被代理類,且代理類及被代理類都實現了movable接口,則可實現靈活多變,具體看代碼
(2)繼承:繼承不夠靈活,具體看代碼
二、代碼
1.Movable.java
2.Tank.java
3.TankTimeProxy.java
4.TankLogProxy.java
5.Tank2Time.java
6.Tank3Log.java
7.Client.java
1.Movable.java
-
public interface Movable {
-
public void move();
-
}
2.Tank.java
-
import java.util.Random;
-
-
public class Tank implements Movable {
-
-
@Override
-
public void move() {
-
System.out.println("Tank moving.......");
-
try {
-
Thread.sleep(new Random().nextInt(5000));
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
-
}
3.TankTimeProxy.java
-
public class TankTimeProxy implements Movable {
-
-
Movable m;
-
-
public TankTimeProxy(Movable m) {
-
this.m = m;
-
}
-
-
@Override
-
public void move() {
-
System.out.println("Time Proxy start...........");
-
long start = System.currentTimeMillis();
-
m.move();
-
long end = System.currentTimeMillis();
-
System.out.println("花費時間:"+(end - start));
-
System.out.println("Time Proxy end...........");
-
}
-
-
}
4.TankLogProxy.java
-
public class TankLogProxy implements Movable {
-
Movable m;
-
public TankLogProxy(Movable m) {
-
this.m = m;
-
}
-
@Override
-
public void move() {
-
System.out.println("Log Proxy start...........");
-
m.move();
-
System.out.println("Log Proxy end...........");
-
}
-
}
5.Tank2Time.java
-
public class Tank2Time extends Tank {
-
-
public void move(){
-
System.out.println("Tank2 time start...........");
-
long start = System.currentTimeMillis();
-
super.move();
-
long end = System.currentTimeMillis();
-
System.out.println("花費時間:"+(end - start));
-
System.out.println("Tank2 time end...........");
-
}
-
}
6.Tank3Log.java
-
public class Tank3Log extends Tank2Time {
-
-
public void move(){
-
System.out.println("Tank3Log start...........");
-
super.move();
-
System.out.println("Tank3Log end...........");
-
}
-
}
7.Client.java
-
public class Client {
-
-
@Test
-
public void testProxy(){
-
Tank t = new Tank();
-
Movable m;
-
//一、聚合的方式(較靈活,因為實現了接口)
-
//1.1聚合方式的代理,先日志代理,后時間代理
-
TankTimeProxy ttp1 = new TankTimeProxy(t);
-
TankLogProxy tlp1 = new TankLogProxy(ttp1);
-
m = tlp1;
-
m.move();
-
System.out.println("\n==============================分隔線==========================\n");
-
//1.2聚合方式的代理,先時間代理,后日志代理(可以靈活切換順序)
-
TankLogProxy tlp2 = new TankLogProxy(t);
-
TankTimeProxy ttp2 = new TankTimeProxy(tlp2);
-
m = ttp2;
-
m.move();
-
System.out.println("\n==============================分隔線==========================\n");
-
//二、繼承的方式
-
//2.1代理時間
-
Tank2Time t2 = new Tank2Time();
-
t2.move();
-
System.out.println("\n==============================分隔線==========================\n");
-
//2.2先代理日志,后時間,不能靈活切換
-
Tank3Log t3 = new Tank3Log();
-
t3.move();
-
}
-
}
三、運行結果
四、小結
凡是要求靈活多變的功能,多數用接口多態實現
三:問題引出
每實現一個需求都需要寫一個代理類,比如:為了實現在方法前后加日志TankLogProxy、為了實現記錄方法運行時間TankTimeProxy,隨着系統的復雜,如果還需要實現權限、事務管理,用這種設計方法,代理類會越來越多。有沒有一種方式,能夠讓我們不寫這些代理類? 動態代理,動態的去代理,代理類是動態生成的,不需要我們編寫,這樣就可以解決這個代理類很多的問題,這樣會極大地減少了我們的工作。
代理模式-動態代理 調用Proxy.newProxyInstance()
http://www.cnblogs.com/shamgod/p/4592014.html
一、概述
1.目標:不自己寫代理類,利用Proxy.newProxyInstance()動態生成
2.用到的知識點:
(1)//編譯源碼,生成class,注意編譯環境要換成jdk1.6才有compiler,單純的jre沒有compiler,會空指針錯誤
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
(2)//文件管事器
StandardJavaFileManager fileMgr = jc.getStandardFileManager(null, null, null);
(3)//編譯單元
Iterable units = fileMgr.getJavaFileObjects(file);
(4)//編譯任務
CompilationTask t = jc.getTask(null, fileMgr, null, null, null, units);
(5)//編譯
t.call();
(6)//把類load到內存里
URL[] urls = new URL[] {new URL("file:/"+System.getProperty("user.dir")+"/src/proxy/TankTimeProxy.class")};
URLClassLoader uc = new URLClassLoader(urls);
Class c = uc.loadClass("proxy.TankTimeProxy");
(7)//生成實例
//return c.newInstance(); //c.newInstance()會調用無參數的Construtor,若類沒有無參的Constructor時會出錯
Constructor ctr = c.getConstructor(Movable.class);
return ctr.newInstance(new Tank());
二、代碼
1.Movable.java
2.Tank.java
3.Proxy.java
4.Client.java
1.Moveable.java
-
package com.weiqinshian.proxy;
-
public interface Moveable
-
{
-
public void move();
-
}
2.Tank.java
-
package com.weiqinshian.proxy;
-
import java.util.Random;
-
public class Tank implements Moveable
-
{
-
@Override
-
public void move()
-
{
-
System.out.println("tank move........");
-
try
-
{
-
Thread.sleep(new Random().nextInt(10000));
-
} catch (InterruptedException e)
-
{
-
e.printStackTrace();
-
}
-
}
-
}
3.Proxy.java
-
package com.weiqinshian.proxy;
-
import java.io.File;
-
import java.io.FileWriter;
-
import java.lang.reflect.Constructor;
-
import java.net.URL;
-
import java.net.URLClassLoader;
-
import javax.tools.JavaCompiler;
-
import javax.tools.StandardJavaFileManager;
-
import javax.tools.ToolProvider;
-
import javax.tools.JavaCompiler.CompilationTask;
-
public class Proxy
-
{
-
public static Object newProxyInstance() throws Exception
-
{
-
String rt = "\n\r";
-
// 動態代理文件的源碼
-
String str = "package com.weiqinshian.proxy;" + rt +
-
"public class TankTimeProxy implements Moveable {" + rt +
-
"private Moveable m;" + rt +
-
"public TankTimeProxy(Moveable m) {" + rt + "this.m = m;" + rt + "}" + rt +
-
"@Override" + rt + "public void move() {" + rt + "System.out.println(\"Time Proxy start...........\");" + rt + "long start = System.currentTimeMillis();" + rt + "m.move();" + rt
-
+ "long end = System.currentTimeMillis();" + rt + "System.out.println(\"花費時間:\"+(end - start));" + rt + "System.out.println(\"Time Proxy end...........\");" + rt + "}" + rt +
-
"}";
-
// 把源碼寫到java文件里
-
File file = new File("d:/src/com/weiqinshian/proxy/TankTimeProxy.java");
-
FileWriter fw = new FileWriter(file);
-
fw.write(str);
-
fw.flush();
-
fw.close();
-
// 編譯源碼,生成class,注意編譯環境要換成jdk才有compiler,單純的jre沒有compiler,會空指針錯誤
-
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
-
// 文件管事器
-
StandardJavaFileManager fileMgr = jc.getStandardFileManager(null, null, null);
-
// 編譯單元
-
Iterable units = fileMgr.getJavaFileObjects(file);
-
// 編譯任務
-
CompilationTask t = jc.getTask(null, fileMgr, null, null, null, units);
-
// 編譯
-
t.call();
-
fileMgr.close();
-
// 把類load到內存里src\com\weiqinshian\proxy
-
URL[] urls = new URL[]
-
{ new URL("file:/" + "d:/src/") };
-
System.out.println("file:/" + System.getProperty("user.dir") + "/src/com/weiqinshian/proxy/TankTimeProxy.class");
-
URLClassLoader uc = new URLClassLoader(urls);
-
Class c = uc.loadClass("com.weiqinshian.proxy.TankTimeProxy");
-
// 生成實例
-
// return c.newInstance();
-
// //c.newInstance()會調用無參數的Construtor,若類沒有無參的Constructor時會出錯
-
Constructor ctr = c.getConstructor(Moveable.class);
-
return ctr.newInstance(new Tank());
-
}
-
}
4.Client.java
-
package com.weiqinshian.proxy;
-
public class Client
-
{
-
public static void main(String[] args) throws Exception
-
{
-
Moveable m = (Moveable) Proxy.newProxyInstance();
-
m.move();// 感覺沒有生成任何代理類
-
}
-
}
三、運行結果
三、問題引出
現在動態代理,動態生成的代理類是寫死了的,是用字符串寫死在類里面的,而且,只能動態生成實現了 Moveable接口的代理,如果要實現任意接口的代理應該怎么辦? 那就不將動態生成代理類的字符串寫死,動態拼接生成代理類。
代理模式--動態代理 修改成可以代理任意接口
一、概述
1.目標:把Proxy修改成可以代理任意接口及其任意方法,只要傳接口名給newProxyInstance,就能動態生成實現了該接口的代理類。
2.思路:
(1)代理任意接口:把接口類型作為參數傳給Proxy的newProxyInstance(Class interfze)
(2)代理任意方法:用interfze.getMethods()取出所有方法,拼接實現方法的字符串
二、代碼
1.Movable.java
2.Tank.java
3.Proxy.java
4.Client.java
1.Movable.java
-
package com.weiqinshian.proxy;
-
public interface Moveable
-
{
-
public void move();
-
public void stop();
-
}
2.Tank.java
-
package com.weiqinshian.proxy;
-
import java.util.Random;
-
public class Tank implements Moveable
-
{
-
@Override
-
public void move()
-
{
-
System.out.println("tank move........");
-
try
-
{
-
Thread.sleep(new Random().nextInt(10000));
-
} catch (InterruptedException e)
-
{
-
e.printStackTrace();
-
}
-
}
-
public void stop()
-
{
-
System.out.println("Tank stopping.......");
-
}
-
}
3.Proxy.java
-
package com.weiqinshian.proxy;
-
import java.io.File;
-
import java.io.FileWriter;
-
import java.lang.reflect.Constructor;
-
import java.lang.reflect.Method;
-
import java.net.URL;
-
import java.net.URLClassLoader;
-
import javax.tools.JavaCompiler;
-
import javax.tools.StandardJavaFileManager;
-
import javax.tools.ToolProvider;
-
import javax.tools.JavaCompiler.CompilationTask;
-
-
public class Proxy
-
{
-
public static Object newProxyInstance(Class interfze) throws Exception
-
{
-
String rt = "\n\r";
-
// 拼接"實現接口方法"的字符串
-
String methodStr = "";
-
for (Method m : interfze.getMethods())
-
{
-
// 取出方法的修飾符和返回值類型
-
String[] parts = m.toString().replace("abstract ", "").split("\\.");
-
String[] parts2 = parts[0].split(" ");
-
methodStr += "@Override" + rt + parts2[0] + " " + parts2[1] + " " + m.getName() + "() {" + rt + "System.out.println(\"Time Proxy start...........\");" + rt
-
+ "long start = System.currentTimeMillis();" + rt + "m." + m.getName() + "();" + rt + "long end = System.currentTimeMillis();" + rt
-
+ "System.out.println(\"花費時間:\"+(end - start));" + rt + "System.out.println(\"Time Proxy end...........\");" + rt + "}";
-
}
-
// 動態代理文件的源碼
-
String str = "package com.weiqinshian.proxy; " + rt +
-
"public class TankTimeProxy implements " + interfze.getName() + " {" + rt +
-
"private " + interfze.getName() + " m;" + rt +
-
"public TankTimeProxy(" + interfze.getName() + " m) {" + rt + "this.m = m;" + rt + "}" + rt +
-
methodStr + rt +
-
"}";
-
// 把源碼寫到java文件里
-
File file = new File("d:/src/com/weiqinshian/proxy/TankTimeProxy.java");
-
FileWriter fw = new FileWriter(file);
-
fw.write(str);
-
fw.flush();
-
fw.close();
-
// 編譯源碼,生成class,注意編譯環境要換成jdk才有compiler,單純的jre沒有compiler,會空指針錯誤
-
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
-
// 文件管事器
-
StandardJavaFileManager fileMgr = jc.getStandardFileManager(null, null, null);
-
// 編譯單元
-
Iterable units = fileMgr.getJavaFileObjects(file);
-
// 編譯任務
-
CompilationTask t = jc.getTask(null, fileMgr, null, null, null, units);
-
// 編譯
-
t.call();
-
fileMgr.close();
-
// 把類load到內存里src\com\weiqinshian\proxy
-
URL[] urls = new URL[]
-
{ new URL("file:/" + "d:/src/") };
-
System.out.println("file:/" + System.getProperty("user.dir") + "/src/com/weiqinshian/proxy/TankTimeProxy.class");
-
URLClassLoader uc = new URLClassLoader(urls);
-
Class c = uc.loadClass("com.weiqinshian.proxy.TankTimeProxy");
-
// 生成實例
-
// return c.newInstance();
-
// //c.newInstance()會調用無參數的Construtor,若類沒有無參的Constructor時會出錯
-
Constructor ctr = c.getConstructor(interfze);
-
return ctr.newInstance(new Tank());
-
}
-
}
4.Client.java
-
package com.weiqinshian.proxy;
-
-
public class Client
-
{
-
public static void main(String[] args) throws Exception
-
{
-
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);// 方法參數可以傳任意接口類型
-
m.move();
-
m.stop();
-
}
-
}
三、運行結果
TankTimeProxy 動態生成的類
-
package com.weiqinshian.proxy;
-
public class TankTimeProxy implements com.weiqinshian.proxy.Moveable {
-
private com.weiqinshian.proxy.Moveable m;
-
public TankTimeProxy(com.weiqinshian.proxy.Moveable m) {
-
this.m = m;
-
}
-
@Override
-
public void stop() {
-
System.out.println("Time Proxy start...........");
-
long start = System.currentTimeMillis();
-
m.stop();
-
long end = System.currentTimeMillis();
-
System.out.println("花費時間:" + (end - start));
-
System.out.println("Time Proxy end...........");
-
}
-
@Override
-
public void move() {
-
System.out.println("Time Proxy start...........");
-
long start = System.currentTimeMillis();
-
m.move();
-
long end = System.currentTimeMillis();
-
System.out.println("花費時間:" + (end - start));
-
System.out.println("Time Proxy end...........");
-
}
-
}
四、問題引出
上面這種方式生成的動態代理,只能生成時間上的代理。我要想生成一個log、權限代理,還是需要再寫一個Proxy動態代理類,怎么解決這個問題?我們在可以在生成代理類的同時,調用一個別人指定給我的處理方式。
在代理里面調用方法的時候,方法前面加什么(比如:日志),后面加什么,不寫死,由別人動態指定。方法的執行交給別人了執行,而執行的過程,可以由我們自己來定(多態)。
思路:將大問題分解為小問題
第一步:需要一個可以動態指定對某一方法進行處理的東西 (InvocationHandler 接口,方法調用處理器)
第二步:TimeHandler
代理模式-動態代理 修改成可以任意修改代理邏輯
一、概述
1.目標:動態代理的代理邏輯可以任意修改
2.思路:
(1)要把代理邏輯抽離,站在jvm的角度思考,應獨立出InvocationHandler接口,並接收被代理的對象及方法作為參數invoke(Object o, Method m),並本身作為參數傳給newProxyInstance(Class interfze,InvocationHandler handler)
(2)InvocationHandler本身聚合被代理類target,以便在target的方法前后增加代理邏輯
3.知識點:
(1)按名字找方法java.lang.reflect.Method md = proxy.Movable.class.getMethod("stop");
(2)按"."拆分字符串:String [] parts = m.toString().replace("abstract ", "").split("\\.");
二、代碼
1.InvocationHandler.java
2.TimeHandler.java
3.Movable.java
4.Tank.java
5.Proxy.java
6.Client.java
1.InvocationHandler.java
-
package com.weiqinshian.proxy;
-
import java.lang.reflect.Method;
-
public interface InvocationHandler
-
{
-
public void invoke(Object o, Method m);
-
}
2.TimeHandler.java
-
package com.weiqinshian.proxy;
-
import java.lang.reflect.Method;
-
public class TimeHandler implements InvocationHandler
-
{
-
// 保留被代理的對象
-
private Object target;
-
public TimeHandler(Object target)
-
{
-
this.target = target;
-
}
-
public void invoke(Object o, Method m)
-
{
-
System.out.println("Time Proxy start...........");
-
long start = System.currentTimeMillis();
-
try
-
{
-
// 除了靜態方法,方法的調用都要先已知對象,所以要把對象o作為參數傳進去
-
m.invoke(target);
-
} catch (Exception e)
-
{
-
e.printStackTrace();
-
}
-
long end = System.currentTimeMillis();
-
System.out.println("花費時間:" + (end - start));
-
System.out.println("Time Proxy end...........");
-
}
-
}
3.Moveable.java
-
package com.weiqinshian.proxy;
-
-
public interface Moveable
-
{
-
public void move();
-
public void stop();
-
}
4.Tank.java
-
package com.weiqinshian.proxy;
-
-
import java.util.Random;
-
-
public class Tank implements Moveable
-
{
-
-
@Override
-
public void move()
-
{
-
System.out.println("tank move........");
-
try
-
{
-
Thread.sleep(new Random().nextInt(10000));
-
} catch (InterruptedException e)
-
{
-
e.printStackTrace();
-
}
-
-
}
-
-
public void stop()
-
{
-
System.out.println("Tank stopping.......");
-
-
}
-
}
5.Proxy.java
-
package com.weiqinshian.proxy;
-
-
import java.io.File;
-
import java.io.FileWriter;
-
import java.lang.reflect.Constructor;
-
import java.lang.reflect.Method;
-
import java.net.URL;
-
import java.net.URLClassLoader;
-
-
import javax.tools.JavaCompiler;
-
import javax.tools.StandardJavaFileManager;
-
import javax.tools.ToolProvider;
-
import javax.tools.JavaCompiler.CompilationTask;
-
-
public class Proxy
-
{
-
-
public static Object newProxyInstance(Class interfze, InvocationHandler handler) throws Exception
-
{
-
String rt = "\n\r";
-
// 拼接"實現接口方法"的字符串
-
String methodStr = "";
-
for (Method m : interfze.getMethods())
-
{
-
// 取出方法的修飾符和返回值類型
-
String[] parts = m.toString().replace("abstract ", "").split("\\.");
-
String[] parts2 = parts[0].split(" ");
-
-
methodStr += "@Override" + rt + parts2[0] + " " + parts2[1] + " " + m.getName() + "() {" + rt + "try{" + rt + "java.lang.reflect.Method md = " + interfze.getName() + ".class.getMethod(\""
-
+ m.getName() + "\");" + rt +
-
// 傳this進去其實沒什么用,invoke實際是調用target的方法m.invoke(target)
-
"handler.invoke(this, md);" + rt + "}catch(Exception e){" + rt + " e.printStackTrace();" + rt + "}" + rt + "}" + rt;
-
}
-
// 動態代理文件的源碼
-
String str = " package com.weiqinshian.proxy; " + rt +
-
-
"public class TankTimeProxy implements " + interfze.getName() + " {" + rt +
-
-
// 聚合Handler
-
"private InvocationHandler handler;" + rt +
-
-
"public TankTimeProxy(InvocationHandler handler) {" + rt + "this.handler = handler;" + rt + "}" + rt + methodStr + rt + "}";
-
-
// 把源碼寫到java文件里
-
File file = new File("d:/src/com/weiqinshian/proxy/TankTimeProxy.java");
-
FileWriter fw = new FileWriter(file);
-
fw.write(str);
-
fw.flush();
-
fw.close();
-
-
// 編譯源碼,生成class,注意編譯環境要換成jdk才有compiler,單純的jre沒有compiler,會空指針錯誤
-
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
-
-
// 文件管事器
-
StandardJavaFileManager fileMgr = jc.getStandardFileManager(null, null, null);
-
-
// 編譯單元
-
Iterable units = fileMgr.getJavaFileObjects(file);
-
-
// 編譯任務
-
CompilationTask t = jc.getTask(null, fileMgr, null, null, null, units);
-
-
// 編譯
-
t.call();
-
fileMgr.close();
-
-
// 把類load到內存里src\com\weiqinshian\proxy
-
URL[] urls = new URL[]
-
{ new URL("file:/" + "d:/src/") };
-
System.out.println("file:/" + System.getProperty("user.dir") + "/src/com/weiqinshian/proxy/TankTimeProxy.class");
-
URLClassLoader uc = new URLClassLoader(urls);
-
Class c = uc.loadClass("com.weiqinshian.proxy.TankTimeProxy");
-
-
// 生成實例
-
// return c.newInstance();
-
// //c.newInstance()會調用無參數的Construtor,若類沒有無參的Constructor時會出錯
-
// Constructor ctr = c.getConstructor(interfze);
-
Constructor ctr = c.getConstructor(InvocationHandler.class);
-
return ctr.newInstance(handler);
-
-
}
-
}
6.Client.java
作為客戶來講,調用這個方法的時候傳了接口,我就知道方法返回的對象是實現了那個接口的,所以,強制轉換為接口,這個肯定是沒有什么問題的,往里面傳new timeHandler,我自己要做什么樣的代理的實現是由我自己來決定的
-
package com.weiqinshian.proxy;
-
public class Client
-
{
-
public static void main(String[] args) throws Exception
-
{
-
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, new TimeHandler(new Tank()));//
-
m.move();
-
m.stop();
-
}
-
}
三、運行結果
四、動態生成代碼
-
package com.weiqinshian.proxy;
-
import java.lang.reflect.InvocationHandler;
-
public class TankTimeProxy implements com.weiqinshian.proxy.Moveable {
-
private InvocationHandler handler;
-
public TankTimeProxy(InvocationHandler handler) {
-
this.handler = handler;
-
}
-
@Override
-
public void stop() {
-
try {
-
java.lang.reflect.Method md = com.weiqinshian.proxy.Moveable.class.getMethod("stop");
-
handler.invoke(this, md);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
@Override
-
public void move() {
-
try {
-
java.lang.reflect.Method md = com.weiqinshian.proxy.Moveable.class.getMethod("move");
-
handler.invoke(this, md);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
CGLIB 和ASM 可以直接修改二進制碼實現動態代理
CGLIB(Code Generation Library)是一個開源項目!
是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口。Hibernate用它來實現PO(Persistent Object 持久化對象)字節碼的動態生成。
ASM 是一個 Java 字節碼操控框架。它能夠以二進制形式修改已有類或者動態生成類。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行為。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據用戶要求生成新類。
