1. 程式人生 > >AOP實現原理——動態代理

AOP實現原理——動態代理

      前幾天阿里面試問AOP是怎麼實現的,感覺自己當時答的不好,於是回來重新研究了一下,找了下資料,現在來做個分享.
      Spring兩大核心IOC與AOP.IOC負責將物件動態的注入到容器,讓容器來管理bean,AOP就是可以讓容器中的物件都享有容器中的公共服務(如日誌記錄等等).那麼AOP是怎麼實現的,下面講一下我的理解——動態代理。
      動態代理就是利用反射和動態編譯將代理模式變成動態的.原理跟動態注入一樣,代理模式在編譯的時候就已經確定代理類將要代理誰,而動態代理在執行的時候才知道自己要代理誰。下面看程式碼:

假設我們要對下面這個使用者管理類進行代理:

package
com.kevindai.AOP; public interface UserService { /** * * @Title: addUser * @Description: 增加user * @param * @author kevindai * @return void 返回型別 * @throws */ public void addUser(); /** * * @Title: delUser * @Description
: 刪除user * @param * @author kevindai * @return void 返回型別 * @throws */
public void delUser(); /** * * @Title: updateUser * @Description: 修改user * @param * @author kevindai * @return void 返回型別 * @throws */ public
void updateUser(); } package com.kevindai.AOP; public class UserServiceImpl implements UserService { /** * * @Title: addUser * @Description: 增加User * @param * @author kevindai * @return * @throws */ public void addUser() { System.out.println("addUser...."); } /** * * @Title: delUser * @Description: 刪除User * @param * @author kevindai * @return * @throws */ public void delUser() { System.out.println("delUser...."); } /** * * @Title: updateUser * @Description: 刪除User * @param * @author kevindai * @return * @throws */ public void updateUser() { System.out.println("updateUser...."); } }

按照代理模式的實現方式,一般是用一個代理類,讓它也實現UserService介面,然後在其內部宣告一個UserServiceImpl,然後分別呼叫addUser和delUser等方法,並在呼叫前後加上我們需要的其他操作。但是這樣都是寫死的,不能做到動態呢。要實現代理,那麼代理類跟被代理類都要實現同一介面,但是動態代理的話根本不知道我們將要代理誰,也就不知道要實現哪個介面。這時候就應該動態生成代理類!
來來一個方法來接收被代理類,這樣我們就可以通過反射知道它的資訊。

package com.kevindai.AOP;

import java.lang.reflect.Method;

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

實現動態代理的關鍵部分,通過Proxy動態生成我們具體的代理類:

package com.kevindai.AOP;

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.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * 
* @ClassName: Proxy
* @Description: 通過動態代理實現AOP
* @author kevindai
* @date 2016-3-31 下午3:56:58
*
 */
public class Proxy {
    /**
     * 
      * @Title: proxyInterface
      * @Description: TODO(這裡用一句話描述這個方法的作用)
      * @param @param 傳入的介面
      * @param @param 代理類
      * @param @return  
      * @author kevindai
      * @return Object    返回型別
      * @throws
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Object proxyInterface(Class clazz,InvocationService invo) throws Exception{
        StringBuffer method = new StringBuffer();
        String br = "\r\n";

        Method[] methods = clazz.getMethods();
        for(Method m : methods){
            method
            //.append(" @Override").append(br)
            .append("   public ").append(m.getReturnType()).append(" ").append(m.getName()).append("(){").append(br)
            .append("       try{").append(br)
            .append("               Method md = ").append(clazz.getName()).append(".class.getMethod(\"").append(m.getName()).append("\");").append(br)
            .append("               invo.invoke(this,md);").append(br)
            .append("           }catch(Exception e){e.printStackTrace();}").append(br)
            .append("   }").append(br).append(br);
        }
        //根據要代理的方法資訊來生成java原始檔
        StringBuffer codeSrc = new StringBuffer();
        codeSrc.append("package com.kevindai.AOP;").append(br)
                .append("import java.lang.reflect.Method;").append(br)
                .append("public class $Proxy1 implements ").append(clazz.getName()).append("{").append(br)
                .append("   public $Proxy1(InvocationService invo){").append(br)
                .append("       this.invo = invo;").append(br)
                .append("   }").append(br)
                .append("   com.kevindai.AOP.InvocationService invo;").append(br)
                .append(method).append(br)
                .append("}");
        String fileName = "C:/myeclipse/starsinoWs/Test/src/com/kevindai/AOP/$Proxy1.java";
        File f = new File(fileName);  
        FileWriter fw = new FileWriter(f);  
        fw.write(codeSrc.toString());  
        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();  

        //載入到記憶體,並例項化  
        URL[] urls = new URL[] {new URL("file:/" + "C:/myeclipse/starsinoWs/Test/src/")};  
        URLClassLoader ul = new URLClassLoader(urls);  
        Class c = ul.loadClass("com.kevindai.AOP.$Proxy1");  

        Constructor ctr = c.getConstructor(InvocationService.class);  
        Object m = ctr.newInstance(invo);  

        return m;  
    }
}

這個類的主要功能就是,根據被代理物件的資訊,動態組裝一個代理類,生成Proxy1.javaProxy1.class。這樣就可以在執行的時候,根據具體的被代理物件生成需要的代理類了。這樣一來,就算不知道需要代理誰,也能生成相應的代理類。

然後寫一些代理資訊,我這裡寫的是事務和時間:

package com.kevindai.AOP;

import java.lang.reflect.Method;

public class TransactionService implements InvocationService {  

    private Object target;  

    public TransactionService(Object target) {  
        super();  
        this.target = target;  
    }  

    public void invoke(Object o, Method m) {  
        System.out.println("開啟事務.....");  
        try {  
            m.invoke(target);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        System.out.println("提交事務.....");  
    }  

} 
package com.kevindai.AOP;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeService implements InvocationService {

    private Object target;  

    public TimeService(Object target) {  
        super();  
        this.target = target;  
    }


    public void invoke(Object o, Method m) {  
        System.out.println("開始時間:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E").format(new Date()));  
        try {  
            m.invoke(target);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        System.out.println("開始時間:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E").format(new Date()));  
    }  
}

現在開始測試一下:

package com.kevindai.AOP;


public class Test {
    public static void main(String[] args) throws Exception {
        UserService userService = new UserServiceImpl();  

        //為使用者管理新增事務處理  
        InvocationService h = new TransactionService(userService);  
        UserService u = (UserService)Proxy.proxyInterface(UserService.class,h);  

        //為使用者管理新增顯示方法執行時間的功能  
        TimeService h2 = new TimeService(u);  
        u = (UserService)Proxy.proxyInterface(UserService.class,h2);  

        u.addUser();  
        System.out.println("\r\n==================================\r\n");  
        u.delUser();  
    }
}

看看輸出:

開始時間:2016-03-31 17:48:23 星期四
開啟事務.....
addUser....
提交事務.....
開始時間:2016-03-31 17:48:23 星期四

==================================

開始時間:2016-03-31 17:48:23 星期四
開啟事務.....
delUser....
提交事務.....
開始時間:2016-03-31 17:48:23 星期四

OK,動態代理完成,這也就是AOP的實現方式。