摘要
本篇筆記針對Java設計模式中最難理解的代理者模式進行講解,從靜態代理、動態代理,及Java相關代理類的應用等幾個方面講解。
一、簡介
主要內容:
1、由問題引出設計模式
2、靜態代理的產生與實現
3、繼承與聚合哪個好
4、動態代理的產生與實現
5、JDK proxy的實現及應用
6、總結與補充
二、問題引出
在實際應用中,我們經常要對一些類的的功能進行一些日志啊,權限驗證等操作,事務控制等處理,而這些類很多都是打成jar包的,本就看不到源碼、別說修改了。那我們怎么辦?問題就來了:如何在不修改一個類的代碼的情況下去實現我們想要的上述的功能?比如性能測試中我們想知道某個方法的執行時間是多少?
三、靜態代理
1、簡單靜態代理
為了解決以上問題,我們首先想到的就是
1、自己寫一個類繼承其它被代理類。
2、在main方法中調用父類的被測試方法之前記錄當前系統時間作為起始時間。
3、調用被測試方法、調用完之后記錄結束時間、并輸出時間差、這就是要測試的方法的執行時間。下面Java模仿實現代碼
a)假設Car使我們看不到的源碼類、它有一個run方法,我們想測試其運行時間
package com.mmb.proxy;
import java.util.Random;
public class Car {
public void running()
{
System.out.println("Car is runing");
try
{
Thread.sleep(new Random().nextInt(1000));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
b) 我們自己定義個CarTimeProxy類、繼承Car類、那么我們自己定義的類就擁有了使用Car類的running方法、那就可以實現測試執行時間。CarTimeProxy——代碼:
package com.mmb.proxy;
public class CarTimeProxy extends Car {
@Override
public void running() {
long startTime = System.currentTimeMillis();
System.out.println("start time is " + startTime);
super.running();
long endTime = System.currentTimeMillis();
System.out.println("end time is " + endTime);
System.out.println("it takes " + (endTime-startTime) + " ms");
}
}
4、Client代碼:package com.mmb.proxy;
public class Client {
public static void main(String[] args) {
CarTimeProxy carTimeProxy = new CarTimeProxy();
carTimeProxy.running();
}
}
5、測試結果
start time is 1433255852213
Car is runing
end time is 1433255853199
it takes 986 ms
2、靜態代理的進一步實現
上面完美的解決了對一個類的一個方法的一種處理方式、看起來我們的RunTimeProxy是不是有點代理的樣子了?但是明顯上面有很大的局限性、甚至可以說是缺陷!那就是沒有用到我們常常掛在嘴邊的抽象、多態。當然這么講可能覺得不理解、沒有直觀的印象。問題:當我想換一個會跑的對象(比如狗,馬)來測試一下他跑的方法的執行時間的時候怎么辦?是不是只能重新寫一個我們自己的類來繼承動物類、然后重復上邊的步驟?很顯然、這樣的做法沒有任何我們覺得可取的優點。只會將時間浪費在重復的coding代碼中。
對與會飛的東西、我們第一想法應該是可以抽象出他們共同的特性——會跑、那么我們可不可以定義一個runAble接口、提供一個running方法呢、這樣想具有跑的功能就實現這個接口就ok了。
到了上面一步、距離我們想要的結構就更近一步了、那就是使用聚合的形式將被代理類與代理類相結合!這樣我們在一個代理類中就可以對任意實現了我們規定的接口的實現類進行我們想要的處理、可能這樣講有點迷惑、直接通過代碼來、在上面的代碼基礎上修改即可:
1、添加一個RunAble接口、里面就一個running方法——RunAble代碼:
package com.mmb.proxy;
public interface RunAble {
public void running();
}
2、讓想要具有跑的功能的類都實現個這個接口。Dog代碼:Horse代碼:
package com.mmb.proxy;
import java.util.Random;
public class Horse extends RunAble {
@Override
public void running() {
System.out.println("Horse is runing");
try
{
Thread.sleep(new Random().nextInt(1000));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
package com.mmb.proxy;
import java.util.Random;
public class Dog implements RunAble {
@Override
public void running() {
System.out.println("Dog is running");
try
{
Thread.sleep(new Random().nextInt(1000));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
3、此時的代理類——RunAbleTimeProxy(此時再叫RunTimeProxy就不合適了、因為他現在是所有能飛的東西的時間代理類)代碼:
package com.mmb.proxy;
public class RunAbleTimeProxy implements RunAble{
private RunAble runAbleObject;
public RunAbleTimeProxy(RunAble runAbleObject)
{
super();
this.runAbleObject = runAbleObject;
}
@Override
public void running() {
long startTime = System.currentTimeMillis();
System.out.println("start time is " + startTime);
runAbleObject.running();
long endTime = System.currentTimeMillis();
System.out.println("end time is " + endTime);
System.out.println("it takes " + (endTime-startTime) + " ms");
}
}
4、為了正規點我們另起一個Client測試代理類:
package com.mmb.proxy;
public class RunAbleClient {
public static void main(String[] args) {
RunAbleTimeProxy runAbleTimeProxy = new RunAbleTimeProxy(new Dog());
runAbleTimeProxy.running();
}
}
5、測試結果:
start time is 1433256898668
Dog is running
end time is 1433256899343
it takes 675 ms
3、靜態代理的更進一步實現
上面的代碼就顯得有點能看了、最起碼我們在里面能找到多態、接口這些東西的使用、同時也解決了一個代理可以代理具有同一特性的類(這里就是多態的強大與靈活)、但是設計是無止境的!一個問題的解決往往伴隨這新的問題的出現:如果我想要對fly方法進行日志記錄、怎么辦?你腦海中的第一反應可能會是再寫一個類RunAbleLogProxy、沒錯!這本身就是一種解決辦法、更好的解決方法下面會有、這里不是重點、重點是——我既想記錄時間、又想記錄日志、怎么辦?寫第三個代理類:RunAbleLogAndTimeProxy。好吧、繼續來問題:我想先記錄時間、在記錄日志?之后又想先記錄日志再記錄時間?之后我覺得還應該添加一個事務控制、然后他們三個的順序我還想換換?然后我覺得還應該加個權限控制。。。。。。不要覺得我犯賤、想這想那。。。問題真的來臨的時候你怎么解決?難道每出現一個新的變動就要寫一個新的代理類?那要寫多少?什么時候是個頭?想解決問題、還是得找Java的多態、抽象。四個字說起來簡單、真真正正的用的時候你才會體會到他的博大精深!解決方法:
我們有沒有想過代理類也是類?也可以被其他的代理類所代理?條件無非就是和原始的被代理類實現同一個接口!然后可以根據不同的需求順序來調整他們的代理順序?比如我先使用記錄時間的類來代理被代理類、然后使用記錄日志的代理類來代理記錄時間的代理類?這樣是不是實現了先記錄時間后記錄日志的代理?如果有多個、我們完全可以按照這種方式去實現!還是以代碼來說明:
1、 新添加一個能夠記錄日志的代理類(想來沒有任何難度、注意實現了RunAble接口)——RunAbleLogProxy代碼:
package com.mmb.proxy;
public class ComplexClient {
public static void main(String[] args) {
RunAbleTimeProxy runAbleTimeProxy = new RunAbleTimeProxy(new Dog());
RunAbleLogProxy runAbleLogProxy = new RunAbleLogProxy(new Dog());
runAbleLogProxy.running();
runAbleTimeProxy.running();
System.out.println("---------------");
RunAbleLogProxy runAbleLogProxy1 = new RunAbleLogProxy(new Dog());
RunAbleTimeProxy runAbleTimeProxy1 = new RunAbleTimeProxy(runAbleLogProxy1);
runAbleTimeProxy1.running();
System.out.println("========================");
RunAbleTimeProxy runAbleTimeProxy2 = new RunAbleTimeProxy(new Dog());
RunAbleLogProxy runAbleLogProxy2 = new RunAbleLogProxy(runAbleTimeProxy2);
runAbleLogProxy2.running();
}
}
結果:
log is start
Dog is running
log is end
start time is 1433257548692
Dog is running
end time is 1433257549486
it takes 794 ms
---------------
start time is 1433257549486
log is start
Dog is running
log is end
end time is 1433257549802
it takes 316 ms
========================
log is start
start time is 1433257549802
Dog is running
end time is 1433257549980
it takes 178 ms
log is end
4、繼承與聚合
留心的可以發現、上面的靜態代理是一步步從類的繼承走向聚合的。首先是通過繼承來實現單一類的代理、這樣的代理、一個代理類只能代理一個類或者其父類。如果想代理別的類、或者對同一類實現不同的代理、那就要另造代理類、如果需要代理的類特別多、則隨之衍生的代理類則無限的膨脹下去、簡稱——類爆炸。這樣的話我們基本看不到他們的可取之處。設計的不合理可以定位與對Java多態的沒有充分利用。
當我們使用聚合的時候、發現會靈活的太多、最起碼進一步解決了繼承所帶來的類爆炸的問題(當然沒有完全解決、對于不同的功能還是要我們去實現不同的代理類)、代理擁有被代理類的父類引用、這樣代理可以代理代理類、這種隨意的組合無疑讓我們方便很多!這是繼承所不能帶給我們的優勢。同時從這里可以看出一點:從接口出發的優勢!所以我們在用許多框架的時候、他會強制要求我們提供接口、然后再提供他的實現類、就是為了更強的靈活性和可擴展性!說到底這就是一種抽象、多態的應用!
四、動態代理
1、動態代理的產生
通過靜態處理之后、我們發現現實與理想更進一步了。最起碼上面的看起來并不是需要寫那么多的類了。但是還是要避免不了的去寫不同的類的代理、不同功能的代理。那么有沒有可能使用一個類來完成上面的所有功能的呢?
動態代理!動態代理可以對任意的對象、任意的接口的方法、實現任意的代理!我們不必關心、也關心不了代理內部的實現、只要按照代理類的要求來使用他、就能實現上面的功能。當然、孤木不成林、我們需要按照他的要求來實現具有自己指定功能的類、以及被代理類。
2、動態代理的實現
a)既然我們的目標是對任意對象、任意接口的方法、實現任意的代理、顯然要使用的是一個高度抽象的接口、或者類來構建骨架。然后在使用時傳入具體的實現類的對象。
b)開始的模型從簡單的開始、就為一個Bird生成一個動態代理。保留Bird類和FlyAble接口、接下來就圍繞如何使用動態代理來生成Bird的代理類。
c)既然是需要有任意性、那么就先定義一個所有代理類必須實現的接口InvocationHandler、使得代理類具有任意性、并且聲明一個方法invoke(Object o, Method m)、用于調用實現類中的此方法來實現代理。InvocationHandler接口:
package com.mmb.designPattern;
import java.lang.reflect.Method;
public interface InvocationHandler {
// 此方法是在Proxy中真正被執行的,是核心
// o是被帶理的對象,m是被執行的方法
public void invoke(Object o ,Method m);
}
d)InvocationHandler的一個實現子類:TimeHandler(它不再是某一具體類的時間代理、而是Object):
package com.mmb.designPattern;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
//被代理對象
public Object target;
public TimeHandler(Object o)
{
this.target = o;
}
/**
* 細心的可以發現第一個參數Object、并沒有使用。
* 只是在這里沒有使用、我們可以按照自己的需求、、
* 傳入一個需要借助的Object來實現特定的功能。
* 此方法會在Proxy中生成的代理類$Proxy1被真正的執行、Object會傳this、也就是$Proxy1!
*/
@Override
public void invoke(Object o, Method m) {
long start = System.currentTimeMillis();
System.out.println("start time is " + start);
System.out.println(o.getClass().getName());
try{
m.invoke(target);
}
catch (Exception e)
{
e.printStackTrace();;
}
long end = System.currentTimeMillis();
System.out.println("end time is " + end);
System.out.println("it takes " + (end-start) +" ms");
}
}
、e)接下來就是核心的Proxy!Proxy只有一個功能——為我們產生代理類!當然需要條件、為誰產生代理類(包括他的所有方法)?產生什么樣的代理類?這個類當我們完成之后、就不必再修改。就是因為他的任意性!當然這里面使用的點反射的東西、但是也沒有多少、看懂不難。
f)主要實現思路:
i、將所有方法代碼拼接成字符串、
ii、 將生成代理類的代碼拼接成字符串(包含所有方法拼接成的字符串)、iii、將此字符串寫入文件中、使用JavaComplier對齊進行編譯、
v、load進內存供我們使用。返回代理實例。
Proxy代碼:
package com.mmb.designPattern;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
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;
/**
為所有的類生成指定的代理類。
簡單起見僅僅考慮沒有返回值沒有參數的方法的代理。
@author mmb
-
*/
public class Proxy {
public static Object newInstance(Class interfaceA, InvocationHandler h) throws Exception {String methodStr = ""; String rt = "\r\n"; Method[] methods = interfaceA.getMethods(); for (Method m : methods) { methodStr = "@Override" + rt + "public void " + m.getName() + "(){" + rt + " try {" + rt + " Method md = " + interfaceA.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt + " h.invoke(this, md);" + rt + " }catch(Exception e) {e.printStackTrace();}" + rt + "}"; } String src = "package com.mmb.designPattern;" + rt + "import java.lang.reflect.Method;" + rt + "public class $Proxy1 implements " + interfaceA.getName() + "{" + rt + " com.mmb.designPattern.InvocationHandler h;" + rt + " public $Proxy1(InvocationHandler h){" + rt + " this.h = h;" + rt + " }" + rt + " "+methodStr + "}"; String fileName = "C:\\WorkPlace\\DesignPattern\\src\\com\\mmb\\designPattern\\$Proxy1.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //compile the proxy class JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(fileName); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //load into memory and create an instance URL[] urls = new URL[]{new URL("file:/" + "C:\\WorkPlace\\DesignPattern\\src")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("com.mmb.designPattern.$Proxy1"); Constructor ctr = c.getConstructor(InvocationHandler.class); Object m = ctr.newInstance(h); return m; } public static void main(String[] args) throws Exception{ Proxy proxy = new Proxy(); Car car = new Car(); TimeHandler timeHandler = new TimeHandler(car); Object o = Proxy.newInstance(RunAble.class, timeHandler); System.out.println(o); } }
生成的代理類 $Proxy1
package com.mmb.designPattern;
import java.lang.reflect.Method;
public class $Proxy1 implements
com.mmb.designPattern.RunAble{
com.mmb.designPattern.InvocationHandler h;
public $Proxy1(InvocationHandler h){
this.h = h;
}
@Override
public void running(){
try {
Method md = com.mmb.designPattern.RunAble.class.getMethod("running");
h.invoke(this, md);
}catch(Exception e) {e.printStackTrace();}
}}
五:CGLIB
spring的AOP主要是由動態代理和CGLIB來完成代理。
1、CGLIB:是針對類生成代理,針對指定的類生成一個子類,覆蓋里面的方法,所以指定的類不能是final包括方法。
2:如果目標對象實現了接口,默認情況下會采用JDK的動態代理實現AOP
3:目標對象實現了接口,可以強制使用CGLIB實現AOP
4:如目標對象沒有實現接口,必須采用CGLIB庫,spirng會自動在JDK動態代理和CGLIB之間轉換
六:總結補充
1、總結
代理到這里就告一段落了、從問題的產生、到一步步的解決、優化。從繼承式的靜態代理到聚合式的靜態代理、從靜態代理到動態代理、逐漸的揭示了代理的功能與實現方式。當一個代理類被創建好之后、我們就不必再關心他的信息、包括如何實現、具體在哪里等等、所要知道的就是如何使用即可。
我們完全可以通過配置文件來指定我們想要使用的代理類。這點是不是讓你想到了spring的AOP?沒錯、spring的AOP是動態代理的一種應用、而不是動態代理是AOP的一種應用、別弄混了。
還有點題外話、spring控制的事務是在什么時候開啟的?是在調用Dao層開啟的還是在調用Service層還是Action層?答案:一個Service層有可能調用多個Dao、所以是在調用Service層方法開始、方法執行完之后結束、根據結果來判斷是提交還是回滾。
以上內容:嚴重參考 http://blog.csdn.net/crave_shy/article/details/21000887
后面是我自己擴充的一些知識
七、JDK的Proxy類的使用方法
先發布了,這部分,等我學明白了再寫