SSH Spring容器AOP的實(shí)現(xiàn)原理——動(dòng)態(tài)代理

2018-09-28 18:57 更新

Spring 容器AOP的實(shí)現(xiàn)原理——?jiǎng)討B(tài)代理

之前寫了一篇關(guān)于IOC的博客——《Spring容器IOC解析及簡(jiǎn)單實(shí)現(xiàn)》,今天再來(lái)聊聊AOP。大家都知道Spring的兩大特性是IOC和AOP。

IOC負(fù)責(zé)將對(duì)象動(dòng)態(tài)的注入到容器,從而達(dá)到一種需要誰(shuí)就注入誰(shuí),什么時(shí)候需要就什么時(shí)候注入的效果,可謂是招之則來(lái),揮之則去。想想都覺得爽,如果現(xiàn)實(shí)生活中也有這本事那就爽歪歪了,至于有多爽,各位自己腦補(bǔ)吧;而AOP呢,它實(shí)現(xiàn)的就是容器的另一大好處了,就是可以讓容器中的對(duì)象都享有容器中的公共服務(wù)。那么容器是怎么做到的呢?它怎么就能讓在它里面的對(duì)象自動(dòng)擁有它提供的公共性服務(wù)呢?答案就是我們今天要討論的內(nèi)容——?jiǎng)討B(tài)代理。

動(dòng)態(tài)代理其實(shí)并不是什么新鮮的東西,學(xué)過(guò)設(shè)計(jì)模式的人都應(yīng)該知道代理模式,代理模式是一種靜態(tài)代理,而動(dòng)態(tài)代理就是利用反射和動(dòng)態(tài)編譯將代理模式變成動(dòng)態(tài)的。原理跟動(dòng)態(tài)注入一樣,代理模式在編譯的時(shí)候就已經(jīng)確定代理類將要代理誰(shuí),而動(dòng)態(tài)代理在運(yùn)行的時(shí)候才知道自己要代理誰(shuí)。

Spring的動(dòng)態(tài)代理有兩種:一是JDK的動(dòng)態(tài)代理;另一個(gè)是cglib動(dòng)態(tài)代理(通過(guò)修改字節(jié)碼來(lái)實(shí)現(xiàn)代理)。今天咱們主要討論JDK動(dòng)態(tài)代理的方式。JDK的代理方式主要就是通過(guò)反射跟動(dòng)態(tài)編譯來(lái)實(shí)現(xiàn)的,下面咱們就通過(guò)代碼來(lái)看看它具體是怎么實(shí)現(xiàn)的。

假設(shè)我們要對(duì)下面這個(gè)用戶管理進(jìn)行代理:

//用戶管理接口  
package com.tgb.proxy;  

public interface UserMgr {  
    void addUser();  
    void delUser();  
}  

//用戶管理的實(shí)現(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("刪除用戶.....");  
    }  

}  

按照代理模式的實(shí)現(xiàn)方式,肯定是用一個(gè)代理類,讓它也實(shí)現(xiàn)UserMgr接口,然后在其內(nèi)部聲明一個(gè)UserMgrImpl,然后分別調(diào)用addUser和delUser方法,并在調(diào)用前后加上我們需要的其他操作。但是這樣很顯然都是寫死的,我們?cè)趺醋龅絼?dòng)態(tài)呢?別急,接著看。我們知道,要實(shí)現(xiàn)代理,那么我們的代理類跟被代理類都要實(shí)現(xiàn)同一接口,但是動(dòng)態(tài)代理的話我們根本不知道我們將要代理誰(shuí),也就不知道我們要實(shí)現(xiàn)哪個(gè)接口,那么要怎么辦呢?我們只有知道要代理誰(shuí)以后,才能給出相應(yīng)的代理類,那么我們何不等知道要代理誰(shuí)以后再去生成一個(gè)代理類呢?想到這里,我們好像找到了解決的辦法,就是動(dòng)態(tài)生成代理類!

這時(shí)候我們親愛的反射又有了用武之地,我們可以寫一個(gè)方法來(lái)接收被代理類,這樣我們就可以通過(guò)反射知道它的一切信息——包括它的類型、它的方法等等(如果你不知道怎么得到,請(qǐng)先去看看我寫的反射的博客《反射一》《反射二》)。

JDK動(dòng)態(tài)代理的兩個(gè)核心分別是InvocationHandler和Proxy,下面我們就用簡(jiǎn)單的代碼來(lái)模擬一下它們是怎么實(shí)現(xiàn)的:

InvocationHandler接口:

package com.tgb.proxy;  

import java.lang.reflect.Method;  

public interface InvocationHandler {  
    public void invoke(Object o, Method m);  
}  

實(shí)現(xiàn)動(dòng)態(tài)代理的關(guān)鍵部分,通過(guò)Proxy動(dòng)態(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)存,并實(shí)例化  
        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;  
    }  

}  

這個(gè)類的主要功能就是,根據(jù)被代理對(duì)象的信息,動(dòng)態(tài)組裝一個(gè)代理類,生成$Proxy1.java文件,然后將其編譯成$Proxy1.class。這樣我們就可以在運(yùn)行的時(shí)候,根據(jù)我們具體的被代理對(duì)象生成我們想要的代理類了。這樣一來(lái),我們就不需要提前知道我們要代理誰(shuí)。也就是說(shuō),你想代理誰(shuí),想要什么樣的代理,我們就給你生成一個(gè)什么樣的代理類。

然后,在客戶端我們就可以隨意的進(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í)行時(shí)間的功能  
        TimeHandler h2 = new TimeHandler(u);  
        u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);  

        u.addUser();  
        System.out.println("\r\n==========華麗的分割線==========\r\n");  
        u.delUser();  
    }  
}  

運(yùn)行結(jié)果:

開始時(shí)間:2014年-07月-15日 15時(shí):48分:54秒  
開啟事務(wù).....  
添加用戶.....  
提交事務(wù).....  
結(jié)束時(shí)間:2014年-07月-15日 15時(shí):48分:57秒  
耗時(shí):3秒  

==========華麗的分割線==========  

開始時(shí)間:2014年-07月-15日 15時(shí):48分:57秒  
開啟事務(wù).....  
刪除用戶.....  
提交事務(wù).....  
結(jié)束時(shí)間:2014年-07月-15日 15時(shí):49分:00秒  
耗時(shí):3秒  

這里我寫了兩個(gè)代理的功能,一個(gè)是事務(wù)處理,一個(gè)是顯示方法執(zhí)行時(shí)間的代理,當(dāng)然都是非常簡(jiǎn)單的寫法,只是為了說(shuō)明這個(gè)原理。當(dāng)然,我們可以想Spring那樣將這些AOP寫到配置文件,因?yàn)橹澳瞧呀?jīng)寫了怎么通過(guò)配置文件注入了,這里就不重復(fù)貼了。到這里,你可能會(huì)有一個(gè)疑問:你上面說(shuō),只要放到容器里的對(duì)象,都會(huì)有容器的公共服務(wù),我怎么沒看出來(lái)呢?好,那我們就繼續(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ù).....");  
    }  

}  

從代碼中不難看出,我們代理的功能里沒有涉及到任何被代理對(duì)象的具體信息,這樣有什么好處呢?這樣的好處就是將代理要做的事情跟被代理的對(duì)象完全分開,這樣一來(lái)我們就可以在代理和被代理之間隨意的進(jìn)行組合了。也就是說(shuō)同一個(gè)功能我們只需要一個(gè)。同樣的功能只有一個(gè),那么這個(gè)功能不就是公共的功能嗎?不管容器中有多少給對(duì)象,都可以享受容器提供的服務(wù)了。這就是容器的好處。

不知道我講的夠不夠清楚,歡迎大家積極交流、討論。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)