之前寫了一篇關(guān)于IOC的博客——《Spring容器IOC解析及簡單實現(xiàn)》,今天再來聊聊AOP。大家都知道Spring的兩大特性是IOC和AOP。
IOC負(fù)責(zé)將對象動態(tài)的注入到容器,從而達(dá)到一種需要誰就注入誰,什么時候需要就什么時候注入的效果,可謂是招之則來,揮之則去。想想都覺得爽,如果現(xiàn)實生活中也有這本事那就爽歪歪了,至于有多爽,各位自己腦補吧;而AOP呢,它實現(xiàn)的就是容器的另一大好處了,就是可以讓容器中的對象都享有容器中的公共服務(wù)。那么容器是怎么做到的呢?它怎么就能讓在它里面的對象自動擁有它提供的公共性服務(wù)呢?答案就是我們今天要討論的內(nèi)容——動態(tài)代理。
動態(tài)代理其實并不是什么新鮮的東西,學(xué)過設(shè)計模式的人都應(yīng)該知道代理模式,代理模式是一種靜態(tài)代理,而動態(tài)代理就是利用反射和動態(tài)編譯將代理模式變成動態(tài)的。原理跟動態(tài)注入一樣,代理模式在編譯的時候就已經(jīng)確定代理類將要代理誰,而動態(tài)代理在運行的時候才知道自己要代理誰。
Spring的動態(tài)代理有兩種:一是JDK的動態(tài)代理;另一個是cglib動態(tài)代理(通過修改字節(jié)碼來實現(xiàn)代理)。今天咱們主要討論JDK動態(tài)代理的方式。JDK的代理方式主要就是通過反射跟動態(tài)編譯來實現(xiàn)的,下面咱們就通過代碼來看看它具體是怎么實現(xiàn)的。
假設(shè)我們要對下面這個用戶管理進(jìn)行代理:
//用戶管理接口
package com.tgb.proxy;
public interface UserMgr {
void addUser();
void delUser();
}
//用戶管理的實現(xiàn)
package com.tgb.proxy;
public class UserMgrImpl implements UserMgr {
@Override
public void addUser() {
System.out.println("添加用戶.....");
}
@Override
public void delUser() {
System.out.println("刪除用戶.....");
}
}
按照代理模式的實現(xiàn)方式,肯定是用一個代理類,讓它也實現(xiàn)UserMgr接口,然后在其內(nèi)部聲明一個UserMgrImpl,然后分別調(diào)用addUser和delUser方法,并在調(diào)用前后加上我們需要的其他操作。但是這樣很顯然都是寫死的,我們怎么做到動態(tài)呢?別急,接著看。我們知道,要實現(xiàn)代理,那么我們的代理類跟被代理類都要實現(xiàn)同一接口,但是動態(tài)代理的話我們根本不知道我們將要代理誰,也就不知道我們要實現(xiàn)哪個接口,那么要怎么辦呢?我們只有知道要代理誰以后,才能給出相應(yīng)的代理類,那么我們何不等知道要代理誰以后再去生成一個代理類呢?想到這里,我們好像找到了解決的辦法,就是動態(tài)生成代理類!
這時候我們親愛的反射又有了用武之地,我們可以寫一個方法來接收被代理類,這樣我們就可以通過反射知道它的一切信息——包括它的類型、它的方法等等(如果你不知道怎么得到,請先去看看我寫的反射的博客《反射一》《反射二》)。
JDK動態(tài)代理的兩個核心分別是InvocationHandler和Proxy,下面我們就用簡單的代碼來模擬一下它們是怎么實現(xiàn)的:
InvocationHandler接口:
package com.tgb.proxy;
import java.lang.reflect.Method;
public interface InvocationHandler {
public void invoke(Object o, Method m);
}
實現(xiàn)動態(tài)代理的關(guān)鍵部分,通過Proxy動態(tài)生成我們具體的代理類:
package com.tgb.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 {
/**
*
* @param infce 被代理類的接口
* @param h 代理類
* @return
* @throws Exception
*/
public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception {
String methodStr = "";
String rt = "\r\n";
//利用反射得到infce的所有方法,并重新組裝
Method[] methods = infce.getMethods();
for(Method m : methods) {
methodStr += " @Override" + rt +
" public "+m.getReturnType()+" " + m.getName() + "() {" + rt +
" try {" + rt +
" Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
" h.invoke(this, md);" + rt +
" }catch(Exception e) {e.printStackTrace();}" + rt +
" }" + rt ;
}
//生成Java源文件
String srcCode =
"package com.tgb.proxy;" + rt +
"import java.lang.reflect.Method;" + rt +
"public class $Proxy1 implements " + infce.getName() + "{" + rt +
" public $Proxy1(InvocationHandler h) {" + rt +
" this.h = h;" + rt +
" }" + rt +
" com.tgb.proxy.InvocationHandler h;" + rt +
methodStr + rt +
"}";
String fileName =
"d:/src/com/tgb/proxy/$Proxy1.java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
fw.write(srcCode);
fw.flush();
fw.close();
//將Java文件編譯成class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(fileName);
CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//加載到內(nèi)存,并實例化
URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("com.tgb.proxy.$Proxy1");
Constructor ctr = c.getConstructor(InvocationHandler.class);
Object m = ctr.newInstance(h);
return m;
}
}
這個類的主要功能就是,根據(jù)被代理對象的信息,動態(tài)組裝一個代理類,生成$Proxy1.java文件,然后將其編譯成$Proxy1.class。這樣我們就可以在運行的時候,根據(jù)我們具體的被代理對象生成我們想要的代理類了。這樣一來,我們就不需要提前知道我們要代理誰。也就是說,你想代理誰,想要什么樣的代理,我們就給你生成一個什么樣的代理類。
然后,在客戶端我們就可以隨意的進(jìn)行代理了。
package com.tgb.proxy;
public class Client {
public static void main(String[] args) throws Exception {
UserMgr mgr = new UserMgrImpl();
//為用戶管理添加事務(wù)處理
InvocationHandler h = new TransactionHandler(mgr);
UserMgr u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h);
//為用戶管理添加顯示方法執(zhí)行時間的功能
TimeHandler h2 = new TimeHandler(u);
u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);
u.addUser();
System.out.println("\r\n==========華麗的分割線==========\r\n");
u.delUser();
}
}
運行結(jié)果:
開始時間:2014年-07月-15日 15時:48分:54秒
開啟事務(wù).....
添加用戶.....
提交事務(wù).....
結(jié)束時間:2014年-07月-15日 15時:48分:57秒
耗時:3秒
==========華麗的分割線==========
開始時間:2014年-07月-15日 15時:48分:57秒
開啟事務(wù).....
刪除用戶.....
提交事務(wù).....
結(jié)束時間:2014年-07月-15日 15時:49分:00秒
耗時:3秒
這里我寫了兩個代理的功能,一個是事務(wù)處理,一個是顯示方法執(zhí)行時間的代理,當(dāng)然都是非常簡單的寫法,只是為了說明這個原理。當(dāng)然,我們可以想Spring那樣將這些AOP寫到配置文件,因為之前那篇已經(jīng)寫了怎么通過配置文件注入了,這里就不重復(fù)貼了。到這里,你可能會有一個疑問:你上面說,只要放到容器里的對象,都會有容器的公共服務(wù),我怎么沒看出來呢?好,那我們就繼續(xù)看一下我們的代理功能:
事務(wù)處理:
package com.tgb.proxy;
import java.lang.reflect.Method;
public class TransactionHandler implements InvocationHandler {
private Object target;
public TransactionHandler(Object target) {
super();
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
System.out.println("開啟事務(wù).....");
try {
m.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("提交事務(wù).....");
}
}
從代碼中不難看出,我們代理的功能里沒有涉及到任何被代理對象的具體信息,這樣有什么好處呢?這樣的好處就是將代理要做的事情跟被代理的對象完全分開,這樣一來我們就可以在代理和被代理之間隨意的進(jìn)行組合了。也就是說同一個功能我們只需要一個。同樣的功能只有一個,那么這個功能不就是公共的功能嗎?不管容器中有多少給對象,都可以享受容器提供的服務(wù)了。這就是容器的好處。
不知道我講的夠不夠清楚,歡迎大家積極交流、討論。
更多建議: