設計模式之動態代理模式

java的動態代理代理模式:爲其他對象提供一種代理,並以控制對這個對象的訪問。其特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等.
代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。
代理分爲兩種:靜態代理和動態代理。
靜態代理:由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在。
動態代理:在程序運行時,利用java的反射機制動態創建而成。
動態代理原理      
   JDK動態代理中包含一個類和一個接口:
   InvocationHandler接口:      
   public interface InvocationHandler {        
   public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
       /*     參數說明:        
               Object proxy:指被代理的對象 。         
               Method method:要調用的方法         
               Object[] args:方法調用時所需要的參數         
*/    
    }
可以將InvocationHandler接口的子類想象成一個代理的最終操作類,替換掉ProxySubject。

Proxy類:     
Proxy類是專門完成代理的操作類,可以通過此類爲一個或多個接口動態地生成實現類,此類提供瞭如下的操作方法:
  public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)   throws IllegalArgumentException;        
  /*  ClassLoader loader:類加載器         
      Class<?>[] interfaces:得到全部的接口         
      InvocationHandler h:得到InvocationHandler接口的子類實例
   */



利用JDK的動態代理寫一個小Demo.
先一個Hello.java接口
   public interface Hello{
    void sayHello(String to);
    void print(String p);
   }

再寫這個接口的實現HelloImpl.java



public class HelloImpl implements Hello {
    @Override
    public void sayHello(String to) {
        System.out.println("Say Hello to  " + to);
    }

    @Override
    public void print(String p) {
        System.out.println("Print: " + p);
    }
}
接下來寫JDK動態代理代理類LogHandler.java

public class LogHandler implements InvocationHandler {

    private Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doBefore();
        Object result = method.invoke(target,args);
        doAfter();
        return result;
    }

    public void doBefore(){
        System.out.println("before....");
    }

    public void doAfter(){
        System.out.println("after....");
    }
}

最後寫一個測試類LogTest.java

public class LogTest {

    public static void main(String[] args){

            HelloImpl helloImpl = new HelloImpl();
            LogHandler logHandler = new LogHandler(helloImpl);


            Hello hello = (Hello) Proxy.newProxyInstance(helloImpl.getClass().getClassLoader(), helloImpl.getClass().getInterfaces(), logHandler);
            hello.sayHello("Peter");
            hello.print("z");
    }
}

輸出的結果爲:

before....
Say Hello tozhangbojun
after....
before....
Print: z
after....
這裏我們使用的是JDK中的動態代理來實現。

其實我們可以自己來模擬一個動態代理類來實現相應的功能。

問題描述:爲Tank類的move()函數做一個時間日誌管理。

1.先寫一個Moveable接口

  

public interface Moveable {

    void move();
}

2.再寫Tank.java類


public class Tank implements Moveable{


    @Override
    public void move() {
        System.out.println("Tank is moving");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }
}

3.模擬JDK中的InvocationHandler接口

public interface InvocationHandler {

    public void invoke(Object o,Method method);
}

4.寫相應的時間日誌操作 如TimeHandler

public class TimeHandler implements InvocationHandler {

    private Object target;
    public TimeHandler(Object target) {
        this.target = target;
    }
    @Override
    public void invoke(Object o, Method method) {
        long start = System.currentTimeMillis();
        System.out.println("starttime: " + start);
        System.out.println(o.getClass().getName());
        try {
            method.invoke(target);
        } catch (IllegalAccessException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (InvocationTargetException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        long end = System.currentTimeMillis();
        System.out.println("time:" + (end - start));
    }
}

5.最後是代理類Proxy.java
public class Proxy {

    public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{  //JDK6,   Complier API,  CGLIB  ,ASM
        String methodStr = "";
        String rt = "\r\n";
        Method[] methods = infce.getMethods();

        for(Method m:methods){
            methodStr += "@Override" + rt +
                    "public void " + 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 +

                    "}";
        }

        String src =
                "package org.whut.proxy.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 +


                        "    org.whut.proxy.proxy.InvocationHandler h;" + rt +
                        methodStr +
                        "}";
        String fileName = "d:/src/org/whut/proxy/proxy/$Proxy1.java";//這裏要寫在d盤中寫見src/org/whut/proxy/proxy文件。要將以上的字符串src寫到相應的文件中
        File f = new File(fileName);
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();

        //編譯
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,null,null);
        Iterable units = fileMgr.getJavaFileObjects(fileName);
        JavaCompiler.CompilationTask task = compiler.getTask(null,fileMgr,null,null,null,units);
        task.call();
        fileMgr.close();

        //加載到內存和生成新對象
        URL[] urls = new URL[]{new URL("file:/" + "d:/src/")};
        URLClassLoader ul = new URLClassLoader(urls);
        Class c = ul.loadClass("org.whut.proxy.proxy.$Proxy1");
        System.out.println(c);

        Constructor ctr = c.getConstructor(InvocationHandler.class);
        Object m = ctr.newInstance(h);

         return m;

    }
}
6.測試其正確性

public class Client {

   public static void main(String[] args) throws Exception {

       Tank t = new Tank();
       InvocationHandler h = new TimeHandler(t);


       Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class,h);
       m.move();
   }
}

運行結果爲:
class org.whut.proxy.proxy.$Proxy1
starttime: 1413718875079
org.whut.proxy.proxy.$Proxy1
Tank is moving
time:835

靜態代理與動態代理的比較

     與靜態代理類對照的是動態代理類,動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因爲Java 反射機制可以生成任意類型的動態代理類。java.lang.reflect 包中的Proxy類和InvocationHandler 接口提供了生成動態代理類的能力。

JDK動態代理也有缺陷

   JDK的動態代理依靠接口實現,如果有些類並沒有實現接口,則不能使用JDK代理,這就要使用cglib動態代理。

Cglib動態代理 
JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因爲採用的是繼承,所以不能對final修飾的類進行代理。

cglib實例   

    先導入兩個包cglib.jar和asm.jar

    Cglib中的被代理類必須實現方法

    先寫一個HelloImpl.java類

public class HelloImpl2 {

    public void sayHello(String to) {
        System.out.println("Say Hello to  " + to);
    }


    public void print(String p) {
        System.out.println("Print: " + p);
    }
}

接着LogCglib.java類

public class LogCglib implements MethodInterceptor {

    public Object obj;
    /*
    創建代理對象
     */

    public Object getInstance(Object target){
        System.out.println("========");
        this.obj = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.obj.getClass());
        //回調方法
        enhancer.setCallback(this);
        enhancer.setClassLoader(target.getClass().getClassLoader());
        //創建代理對象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        doBefore();
        methodProxy.invokeSuper(o,objects);
        doAfter();
        return null;

    }

    public void doBefore(){
        System.out.println("before....");
    }

    public void doAfter(){
        System.out.println("after....");
    }
}

測試LogTest2.java

public class LogTest2 {
    public static void main(String[] args){

        LogCglib logCglib = new LogCglib();
        HelloImpl2 helloImpl = (HelloImpl2) logCglib.getInstance(new HelloImpl2());
        helloImpl.sayHello("peter");
        helloImpl.print("z");
    }


}
運行結果

========
before....
Say Hello to  peter
after....
before....
Print: z
after....









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章