從裝飾者模式出發:我理解的動態代理

首先說明我的個人觀點:動態代理是對裝飾者模式的升級

1、我們先來寫一個裝飾者模式的簡單demo

一個接口

package decorateTest;

public interface Animal {
    void sleep();

    void eat();

}

一個被裝飾類

package decorateTest;

public class Cat implements Animal {

    @Override
    public void sleep() {
        System.out.println("sleep.............");
    }

    @Override
    public void eat() {
        System.out.println("eat..........");

    }

}

一個裝飾者

package decorateTest;

public class CatDecorator implements Animal {
    private Animal origin;

    public CatDecorator() {
        super();
    }

    public CatDecorator(Animal inter) {
        this.origin = inter;
    }

    @Override
    public void sleep() {
        System.out.println("kunle..................");
        origin.sleep();

    }

    @Override
    public void eat() {
        origin.eat();

    }

}

裝飾者的代碼邏輯比較相似,可以作如下優化

package invocateTest;

import java.lang.reflect.Method;

public class CatDecorator implements Animal {
    private Animal origin;

    public CatDecorator() {
        super();
    }

    public CatDecorator(Animal inter) {
        this.origin = inter;
    }

    @Override
    public void sleep() {
        invoke("sleep");

    }

    @Override
    public void eat() {
        invoke("eat");
    }

    private void invoke(String methodName) {
        try {
            Method method = origin.getClass().getMethod(methodName, null);
            if (methodName.equals("sleep")) {
                System.out.println("kunle...................");
            }
            method.invoke(origin, null);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

2、思考:如果我對方法怎樣加強不確定,甚至加強哪個方法也不確定,該如何做?
解決辦法:將invoke方法抽取出來以便需要時裝配一個invoke方法

package invocateHandlerTest;

public class CatDecorator implements Animal {
    private InvocationHandler handler;

    public CatDecorator() {
        super();
    }

    public CatDecorator(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void sleep() {
        handler.invoke("sleep");
    }

    @Override
    public void eat() {
        handler.invoke("eat");
    }

}
package invocateHandlerTest;

import java.lang.reflect.Method;

public class InvocationHandler {
    private Animal animal;

    public InvocationHandler() {
        super();
    }

    public InvocationHandler(Animal animal) {
        this.animal = animal;
    }

    public void invoke(String methodName) {
        try {
            Method method = animal.getClass().getMethod(methodName, null);
            if (methodName.equals("sleep")) {
                System.out.println("kunle...................");
            }
            method.invoke(animal, null);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

3、思考:如果我並不知道要被加強的是哪個類,有什麼接口,怎麼辦呢?
解決辦法:在被裝飾者和接口未知的情況下,設計一個工具類,根據傳入的被裝飾者和接口生成一個裝飾類

package proxyTest;

import java.io.File;
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.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;

public class Proxy {
    @SuppressWarnings("unchecked")
    public static Object newInstance(Class interface1, InvocationHandler handler) {
        Object newInstance = null;
        String classSimpleName = interface1.getSimpleName() + "Proxy";
        String className = "proxyTest." + classSimpleName;

        // 拼裝包名
        String classString = "package proxyTest;";
        // 拼裝開頭
        classString += "public class " + classSimpleName + " implements " + interface1.getName()
                + "{private InvocationHandler handler;public " + classSimpleName + "(){super();}"//
                + "public " + classSimpleName + "(InvocationHandler handler) {this.handler = handler;}";
        // 拼裝方法
        Method[] methods = interface1.getMethods();
        for (Method method : methods) {
            String methodname = method.getName();
            String returnname = method.getReturnType().getName();
            String modifierName = "public";
            int modifiers = method.getModifiers();
            if (modifiers == 1212) {// 如果 修飾符等於public就拼裝public,修飾符是與一些常量數字一一對應的,記不住就不寫了
                modifierName = "private";
            }
            String methodBody = "{handler.invoke(\"" + method.getName() + "\");}";
            classString = classString + modifierName + " " + returnname + " " + methodname + "()" + methodBody;
        }

        // 結束
        classString += "}";
        System.out.println(classString);

        // 用拼接出來的裝飾類的字符串生成一個class文件
        Class clazz = generateClass(classString, classSimpleName, className);
        try {
            // 調用有參構造生成一個裝飾者的實例
            Constructor constructor = clazz.getConstructor(InvocationHandler.class);
            constructor.setAccessible(true);
            newInstance = constructor.newInstance(handler);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return newInstance;(這裏就可以視爲獲得了動態代理的對象了)
    }
/*
根據class文件的字符串內容寫出一個java文件,再調用編譯器將這個文件編譯,並返回class對象,jdk的動態代理使用的是一個native方法
**/
    private static Class generateClass(String classString, String classSimpleName, String className) {
        String fileName = "H:\\Java\\javaee\\ProxyTest\\src\\proxyTest\\" + classSimpleName + ".java";
        try {// 寫出一個java文件
            File file = new File(fileName);

            FileUtils.writeByteArrayToFile(file, classString.getBytes());
            // 編譯這個java文件
            JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = jc.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects(fileName);
            CompilationTask cTask = jc.getTask(null, fileManager, null, null, null, fileObjects);
            cTask.call();
            fileManager.close();

            // 使用URLClassLoader加載class到內存
            String currentDir = System.getProperty("user.dir");
            URL[] urls = new URL[] { new URL("file:/" + currentDir + "/src/") };
            URLClassLoader cLoader = new URLClassLoader(urls);
            Class<?> c = cLoader.loadClass(className);
            cLoader.close();
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

測試類

package proxyTest;

import org.junit.Test;

public class TestProxy {
    @Test
    public void test1() {
        Animal animal = new Cat();
        InvocationHandler handler = new InvocationHandler(animal);
        Object newInstance = Proxy.newInstance(Animal.class, handler);
        Animal animalProxy = (Animal) newInstance;
        animalProxy.sleep();
    }

}

4、最後還可以對handler做一個接口的抽取,就和常用的動態代理差不多了。
以上內容是我個人理解,歡迎討論,如有雷同,一定是英雄所見略同(手動滑稽)

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