摘要
本篇筆記針對(duì)Java設(shè)計(jì)模式中最難理解的代理者模式進(jìn)行講解,從靜態(tài)代理彤灶、動(dòng)態(tài)代理旷太,及Java相關(guān)代理類的應(yīng)用等幾個(gè)方面講解展懈。
一、簡(jiǎn)介
主要內(nèi)容:
1供璧、由問(wèn)題引出設(shè)計(jì)模式
2存崖、靜態(tài)代理的產(chǎn)生與實(shí)現(xiàn)
3、繼承與聚合哪個(gè)好
4睡毒、動(dòng)態(tài)代理的產(chǎn)生與實(shí)現(xiàn)
5来惧、JDK proxy的實(shí)現(xiàn)及應(yīng)用
6、總結(jié)與補(bǔ)充
二演顾、問(wèn)題引出
在實(shí)際應(yīng)用中供搀,我們經(jīng)常要對(duì)一些類的的功能進(jìn)行一些日志啊隅居,權(quán)限驗(yàn)證等操作,事務(wù)控制等處理葛虐,而這些類很多都是打成jar包的胎源,本就看不到源碼、別說(shuō)修改了屿脐。那我們?cè)趺崔k涕蚤?問(wèn)題就來(lái)了:如何在不修改一個(gè)類的代碼的情況下去實(shí)現(xiàn)我們想要的上述的功能?比如性能測(cè)試中我們想知道某個(gè)方法的執(zhí)行時(shí)間是多少摄悯?
三赞季、靜態(tài)代理
1愧捕、簡(jiǎn)單靜態(tài)代理
為了解決以上問(wèn)題奢驯,我們首先想到的就是
1、自己寫一個(gè)類繼承其它被代理類次绘。
2瘪阁、在main方法中調(diào)用父類的被測(cè)試方法之前記錄當(dāng)前系統(tǒng)時(shí)間作為起始時(shí)間。
3邮偎、調(diào)用被測(cè)試方法管跺、調(diào)用完之后記錄結(jié)束時(shí)間、并輸出時(shí)間差禾进、這就是要測(cè)試的方法的執(zhí)行時(shí)間豁跑。下面Java模仿實(shí)現(xiàn)代碼
a)假設(shè)Car使我們看不到的源碼類、它有一個(gè)run方法泻云,我們想測(cè)試其運(yùn)行時(shí)間
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) 我們自己定義個(gè)CarTimeProxy類艇拍、繼承Car類、那么我們自己定義的類就擁有了使用Car類的running方法宠纯、那就可以實(shí)現(xiàn)測(cè)試執(zhí)行時(shí)間卸夕。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婆瓜、測(cè)試結(jié)果
start time is 1433255852213
Car is runing
end time is 1433255853199
it takes 986 ms
2快集、靜態(tài)代理的進(jìn)一步實(shí)現(xiàn)
上面完美的解決了對(duì)一個(gè)類的一個(gè)方法的一種處理方式、看起來(lái)我們的RunTimeProxy是不是有點(diǎn)代理的樣子了廉白?但是明顯上面有很大的局限性个初、甚至可以說(shuō)是缺陷!那就是沒有用到我們常常掛在嘴邊的抽象猴蹂、多態(tài)院溺。當(dāng)然這么講可能覺得不理解、沒有直觀的印象晕讲。問(wèn)題:當(dāng)我想換一個(gè)會(huì)跑的對(duì)象(比如狗覆获,馬)來(lái)測(cè)試一下他跑的方法的執(zhí)行時(shí)間的時(shí)候怎么辦马澈?是不是只能重新寫一個(gè)我們自己的類來(lái)繼承動(dòng)物類、然后重復(fù)上邊的步驟弄息?很顯然痊班、這樣的做法沒有任何我們覺得可取的優(yōu)點(diǎn)。只會(huì)將時(shí)間浪費(fèi)在重復(fù)的coding代碼中摹量。
對(duì)與會(huì)飛的東西涤伐、我們第一想法應(yīng)該是可以抽象出他們共同的特性——會(huì)跑、那么我們可不可以定義一個(gè)runAble接口缨称、提供一個(gè)running方法呢凝果、這樣想具有跑的功能就實(shí)現(xiàn)這個(gè)接口就ok了。
到了上面一步睦尽、距離我們想要的結(jié)構(gòu)就更近一步了器净、那就是使用聚合的形式將被代理類與代理類相結(jié)合!這樣我們?cè)谝粋€(gè)代理類中就可以對(duì)任意實(shí)現(xiàn)了我們規(guī)定的接口的實(shí)現(xiàn)類進(jìn)行我們想要的處理当凡、可能這樣講有點(diǎn)迷惑山害、直接通過(guò)代碼來(lái)、在上面的代碼基礎(chǔ)上修改即可:
1沿量、添加一個(gè)RunAble接口浪慌、里面就一個(gè)running方法——RunAble代碼:
package com.mmb.proxy;
public interface RunAble {
public void running();
}
2、讓想要具有跑的功能的類都實(shí)現(xiàn)個(gè)這個(gè)接口朴则。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权纤、此時(shí)的代理類——RunAbleTimeProxy(此時(shí)再叫RunTimeProxy就不合適了、因?yàn)樗F(xiàn)在是所有能飛的東西的時(shí)間代理類)代碼:
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乌妒、為了正規(guī)點(diǎn)我們另起一個(gè)Client測(cè)試代理類:
package com.mmb.proxy;
public class RunAbleClient {
public static void main(String[] args) {
RunAbleTimeProxy runAbleTimeProxy = new RunAbleTimeProxy(new Dog());
runAbleTimeProxy.running();
}
}
5汹想、測(cè)試結(jié)果:
start time is 1433256898668
Dog is running
end time is 1433256899343
it takes 675 ms
3、靜態(tài)代理的更進(jìn)一步實(shí)現(xiàn)
上面的代碼就顯得有點(diǎn)能看了芥被、最起碼我們?cè)诶锩婺苷业蕉鄳B(tài)欧宜、接口這些東西的使用、同時(shí)也解決了一個(gè)代理可以代理具有同一特性的類(這里就是多態(tài)的強(qiáng)大與靈活)拴魄、但是設(shè)計(jì)是無(wú)止境的冗茸!一個(gè)問(wèn)題的解決往往伴隨這新的問(wèn)題的出現(xiàn):如果我想要對(duì)fly方法進(jìn)行日志記錄、怎么辦匹中?你腦海中的第一反應(yīng)可能會(huì)是再寫一個(gè)類RunAbleLogProxy夏漱、沒錯(cuò)!這本身就是一種解決辦法顶捷、更好的解決方法下面會(huì)有挂绰、這里不是重點(diǎn)、重點(diǎn)是——我既想記錄時(shí)間服赎、又想記錄日志葵蒂、怎么辦交播?寫第三個(gè)代理類:RunAbleLogAndTimeProxy。好吧践付、繼續(xù)來(lái)問(wèn)題:我想先記錄時(shí)間秦士、在記錄日志?之后又想先記錄日志再記錄時(shí)間永高?之后我覺得還應(yīng)該添加一個(gè)事務(wù)控制隧土、然后他們?nèi)齻€(gè)的順序我還想換換?然后我覺得還應(yīng)該加個(gè)權(quán)限控制命爬。曹傀。。饲宛。皆愉。。不要覺得我犯賤落萎、想這想那亥啦。炭剪。练链。問(wèn)題真的來(lái)臨的時(shí)候你怎么解決?難道每出現(xiàn)一個(gè)新的變動(dòng)就要寫一個(gè)新的代理類奴拦?那要寫多少媒鼓?什么時(shí)候是個(gè)頭?想解決問(wèn)題错妖、還是得找Java的多態(tài)绿鸣、抽象。四個(gè)字說(shuō)起來(lái)簡(jiǎn)單暂氯、真真正正的用的時(shí)候你才會(huì)體會(huì)到他的博大精深潮模!解決方法:
我們有沒有想過(guò)代理類也是類?也可以被其他的代理類所代理痴施?條件無(wú)非就是和原始的被代理類實(shí)現(xiàn)同一個(gè)接口擎厢!然后可以根據(jù)不同的需求順序來(lái)調(diào)整他們的代理順序?比如我先使用記錄時(shí)間的類來(lái)代理被代理類辣吃、然后使用記錄日志的代理類來(lái)代理記錄時(shí)間的代理類动遭?這樣是不是實(shí)現(xiàn)了先記錄時(shí)間后記錄日志的代理?如果有多個(gè)神得、我們完全可以按照這種方式去實(shí)現(xiàn)厘惦!還是以代碼來(lái)說(shuō)明:
1、 新添加一個(gè)能夠記錄日志的代理類(想來(lái)沒有任何難度哩簿、注意實(shí)現(xiàn)了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();
}
}
結(jié)果:
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宵蕉、繼承與聚合
留心的可以發(fā)現(xiàn)酝静、上面的靜態(tài)代理是一步步從類的繼承走向聚合的。首先是通過(guò)繼承來(lái)實(shí)現(xiàn)單一類的代理羡玛、這樣的代理形入、一個(gè)代理類只能代理一個(gè)類或者其父類。如果想代理別的類缝左、或者對(duì)同一類實(shí)現(xiàn)不同的代理亿遂、那就要另造代理類、如果需要代理的類特別多渺杉、則隨之衍生的代理類則無(wú)限的膨脹下去蛇数、簡(jiǎn)稱——類爆炸。這樣的話我們基本看不到他們的可取之處是越。設(shè)計(jì)的不合理可以定位與對(duì)Java多態(tài)的沒有充分利用耳舅。
當(dāng)我們使用聚合的時(shí)候、發(fā)現(xiàn)會(huì)靈活的太多倚评、最起碼進(jìn)一步解決了繼承所帶來(lái)的類爆炸的問(wèn)題(當(dāng)然沒有完全解決浦徊、對(duì)于不同的功能還是要我們?nèi)?shí)現(xiàn)不同的代理類)、代理?yè)碛斜淮眍惖母割愐锰煳唷⑦@樣代理可以代理代理類盔性、這種隨意的組合無(wú)疑讓我們方便很多!這是繼承所不能帶給我們的優(yōu)勢(shì)呢岗。同時(shí)從這里可以看出一點(diǎn):從接口出發(fā)的優(yōu)勢(shì)冕香!所以我們?cè)谟迷S多框架的時(shí)候、他會(huì)強(qiáng)制要求我們提供接口后豫、然后再提供他的實(shí)現(xiàn)類悉尾、就是為了更強(qiáng)的靈活性和可擴(kuò)展性!說(shuō)到底這就是一種抽象挫酿、多態(tài)的應(yīng)用构眯!
四、動(dòng)態(tài)代理
1早龟、動(dòng)態(tài)代理的產(chǎn)生
通過(guò)靜態(tài)處理之后惫霸、我們發(fā)現(xiàn)現(xiàn)實(shí)與理想更進(jìn)一步了。最起碼上面的看起來(lái)并不是需要寫那么多的類了拄衰。但是還是要避免不了的去寫不同的類的代理它褪、不同功能的代理。那么有沒有可能使用一個(gè)類來(lái)完成上面的所有功能的呢翘悉?
動(dòng)態(tài)代理茫打!動(dòng)態(tài)代理可以對(duì)任意的對(duì)象、任意的接口的方法、實(shí)現(xiàn)任意的代理老赤!我們不必關(guān)心轮洋、也關(guān)心不了代理內(nèi)部的實(shí)現(xiàn)、只要按照代理類的要求來(lái)使用他抬旺、就能實(shí)現(xiàn)上面的功能弊予。當(dāng)然、孤木不成林开财、我們需要按照他的要求來(lái)實(shí)現(xiàn)具有自己指定功能的類汉柒、以及被代理類。
2责鳍、動(dòng)態(tài)代理的實(shí)現(xiàn)
a)既然我們的目標(biāo)是對(duì)任意對(duì)象碾褂、任意接口的方法、實(shí)現(xiàn)任意的代理历葛、顯然要使用的是一個(gè)高度抽象的接口正塌、或者類來(lái)構(gòu)建骨架。然后在使用時(shí)傳入具體的實(shí)現(xiàn)類的對(duì)象恤溶。
b)開始的模型從簡(jiǎn)單的開始乓诽、就為一個(gè)Bird生成一個(gè)動(dòng)態(tài)代理。保留Bird類和FlyAble接口咒程、接下來(lái)就圍繞如何使用動(dòng)態(tài)代理來(lái)生成Bird的代理類鸠天。
c)既然是需要有任意性、那么就先定義一個(gè)所有代理類必須實(shí)現(xiàn)的接口InvocationHandler孵坚、使得代理類具有任意性粮宛、并且聲明一個(gè)方法invoke(Object o, Method m)、用于調(diào)用實(shí)現(xiàn)類中的此方法來(lái)實(shí)現(xiàn)代理卖宠。InvocationHandler接口:
package com.mmb.designPattern;
import java.lang.reflect.Method;
public interface InvocationHandler {
// 此方法是在Proxy中真正被執(zhí)行的,是核心
// o是被帶理的對(duì)象忧饭,m是被執(zhí)行的方法
public void invoke(Object o ,Method m);
}
d)InvocationHandler的一個(gè)實(shí)現(xiàn)子類:TimeHandler(它不再是某一具體類的時(shí)間代理扛伍、而是Object):
package com.mmb.designPattern;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
//被代理對(duì)象
public Object target;
public TimeHandler(Object o)
{
this.target = o;
}
/**
* 細(xì)心的可以發(fā)現(xiàn)第一個(gè)參數(shù)Object、并沒有使用词裤。
* 只是在這里沒有使用刺洒、我們可以按照自己的需求、吼砂、
* 傳入一個(gè)需要借助的Object來(lái)實(shí)現(xiàn)特定的功能逆航。
* 此方法會(huì)在Proxy中生成的代理類$Proxy1被真正的執(zhí)行、Object會(huì)傳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)接下來(lái)就是核心的Proxy!Proxy只有一個(gè)功能——為我們產(chǎn)生代理類!當(dāng)然需要條件抹剩、為誰(shuí)產(chǎn)生代理類(包括他的所有方法)撑帖?產(chǎn)生什么樣的代理類?這個(gè)類當(dāng)我們完成之后澳眷、就不必再修改胡嘿。就是因?yàn)樗娜我庑裕‘?dāng)然這里面使用的點(diǎn)反射的東西钳踊、但是也沒有多少衷敌、看懂不難。
f)主要實(shí)現(xiàn)思路:
i拓瞪、將所有方法代碼拼接成字符串逢享、
ii、 將生成代理類的代碼拼接成字符串(包含所有方法拼接成的字符串)吴藻、iii瞒爬、將此字符串寫入文件中、使用JavaComplier對(duì)齊進(jìn)行編譯沟堡、
v侧但、load進(jìn)內(nèi)存供我們使用。返回代理實(shí)例航罗。
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;
/**
為所有的類生成指定的代理類禀横。
簡(jiǎn)單起見僅僅考慮沒有返回值沒有參數(shù)的方法的代理。
@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主要是由動(dòng)態(tài)代理和CGLIB來(lái)完成代理粥血。
1柏锄、CGLIB:是針對(duì)類生成代理,針對(duì)指定的類生成一個(gè)子類复亏,覆蓋里面的方法趾娃,所以指定的類不能是final包括方法。
2:如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口缔御,默認(rèn)情況下會(huì)采用JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP
3:目標(biāo)對(duì)象實(shí)現(xiàn)了接口抬闷,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP
4:如目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口,必須采用CGLIB庫(kù)耕突,spirng會(huì)自動(dòng)在JDK動(dòng)態(tài)代理和CGLIB之間轉(zhuǎn)換
六:總結(jié)補(bǔ)充
1笤成、總結(jié)
代理到這里就告一段落了、從問(wèn)題的產(chǎn)生眷茁、到一步步的解決炕泳、優(yōu)化。從繼承式的靜態(tài)代理到聚合式的靜態(tài)代理上祈、從靜態(tài)代理到動(dòng)態(tài)代理培遵、逐漸的揭示了代理的功能與實(shí)現(xiàn)方式浙芙。當(dāng)一個(gè)代理類被創(chuàng)建好之后、我們就不必再關(guān)心他的信息荤懂、包括如何實(shí)現(xiàn)茁裙、具體在哪里等等、所要知道的就是如何使用即可节仿。
我們完全可以通過(guò)配置文件來(lái)指定我們想要使用的代理類晤锥。這點(diǎn)是不是讓你想到了spring的AOP?沒錯(cuò)廊宪、spring的AOP是動(dòng)態(tài)代理的一種應(yīng)用矾瘾、而不是動(dòng)態(tài)代理是AOP的一種應(yīng)用、別弄混了箭启。
還有點(diǎn)題外話壕翩、spring控制的事務(wù)是在什么時(shí)候開啟的?是在調(diào)用Dao層開啟的還是在調(diào)用Service層還是Action層傅寡?答案:一個(gè)Service層有可能調(diào)用多個(gè)Dao放妈、所以是在調(diào)用Service層方法開始、方法執(zhí)行完之后結(jié)束荐操、根據(jù)結(jié)果來(lái)判斷是提交還是回滾芜抒。
以上內(nèi)容:嚴(yán)重參考 http://blog.csdn.net/crave_shy/article/details/21000887
后面是我自己擴(kuò)充的一些知識(shí)
七、JDK的Proxy類的使用方法
先發(fā)布了托启,這部分宅倒,等我學(xué)明白了再寫