首先說明我的個人觀點:動態代理是對裝飾者模式的升級
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做一個接口的抽取,就和常用的動態代理差不多了。
以上內容是我個人理解,歡迎討論,如有雷同,一定是英雄所見略同(手動滑稽)