設計模式之美 - 代理模式
[toc]html
設計模式之美目錄:https://www.cnblogs.com/binarylei/p/8999236.htmljava
<b>代理模式</b>:給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。GoF 的《設計模式》一書中把 RPC 稱做遠程代理。其它應用場景如緩存、監控、統計、鑑權、限流、事務、冪等、日誌等。git
package com.github.binarylei.design.proxy; public interface UserService { public void say(); } public class UserServiceImpl implements UserService { @Override public void say() { System.out.println("Hello World!"); } }
1. 靜態代理
public void test() { UserServiceImpl obj = new UserServiceImpl(); UserService userService = new UserService() { @Override public void say() { System.out.println("這是靜態代理"); obj.say(); } }; userService.say(); }
很明顯靜態代理每一個被代理的類都要手寫一個代理類,當修改被代理的類時也要修改對應的代理類。解決這個問題則是由程序來生成對應的代理類,這就是動態代理。github
2. 動態代理
2.1 JDK 動態代理
public void test1() throws Exception { UserServiceImpl obj = new UserServiceImpl(); UserService userService = (UserService) Proxy.newProxyInstance( UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getDeclaringClass() == Object.class) { return method.invoke(obj, args); } else { System.out.println(proxy.getClass().getName()); System.out.println(method); Object ret = method.invoke(obj, args); return ret; } } }); userService.say(); System.out.println(userService); }
注意:JDK 中所要進行動態代理的類必需要實現一個接口 ,也就是說只能對該類所實現接口中定義的方法進行代理,這在實際編程中具備必定的侷限性,並且使用反射的效率也並非很高。編程
2.2 CGLib 動態代理
使用 CGLib 實現動態代理,徹底不受代理類必須實現接口的限制,並且 CGLib 底層採用 ASM 字節碼生成框架,使用字節碼技術生成代理類,比使用 Java 反射效率要高。惟一須要注意的是,CGLib 不能對聲明爲 final 的方法進行代理,由於 CGLib 原理是動態生成被代理類的子類。設計模式
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.8</version> </dependency>
public void test2() { Enhancer enhancer = new Enhancer(); //1. 設置父類 enhancer.setSuperclass(UserServiceImpl.class); //2. 設置回調函數 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(method + " proxy"); Object ret = proxy.invokeSuper(obj, args); return null; } }); //3. 獲取代理對象 UserService userService = (UserService) enhancer.create(); userService.say(); }
參數:Object 爲由 CGLib 動態生成的代理類實例,Method 爲上文中實體類所調用的被代理的方法引用,Object[] 爲參數值列表,MethodProxy 爲生成的代理類對方法的代理引用。proxy.invokeSuper(obj, arg) 從代理實例的方法調用返回的值。緩存
3. 動態代理原理
3.1 原理
JDK 的動態代理其實是在內存中生成了一個字節碼類,並進行編譯,加載。JVM 生成的類名稱都是以 $ 開頭,eg: $Proxy0app
public void test1() throws Exception { UserServiceImpl obj = new UserServiceImpl(); UserService userService = (UserService) Proxy.newProxyInstance( UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(proxy.getClass().getName()); // $Proxy0 Object ret = method.invoke(obj, args); return ret; } }); userService.say(); System.out.println(userService); byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", obj.getClass().getInterfaces()); FileOutputStream out = new FileOutputStream( this.getClass().getResource("").getPath() + "$Proxy0.class"); out.write(bytes); }
查看生成的 $Proxy0.class 類以下:框架
import com.github.binarylei.design.proxy.UserService; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements UserService { private static Method m3; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final void say() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m3 = Class.forName("com.github.binarylei.design.proxy.UserService").getMethod("say", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } // ... }
3.2 手寫動態代理
(1) 定義 MyInvocationHandler 接口ide
@FunctionalInterface public interface MyInvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
(2) 實現本身的 MyProxy 類
package com.github.binarylei.design.proxy.my; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Method; /** * @author: leigang * @version: 2018-10-02 */ public class MyProxy { public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) { FileWriter out = null; try { // 1. 動態生成源代碼 .java 文件 String src = generateSrc(interfaces); // 2. .java 文件生成到磁盤 File file = new File(MyProxy.class.getResource("").getPath() + "$Proxy1.java"); out = new FileWriter(file); out.write(src); out.flush(); out.close(); // 3. 把 .java 文件編譯成 .class 文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager manager = compiler.getStandardFileManager( null, null, null); Iterable<? extends JavaFileObject> iterable = manager.getJavaFileObjects(file); JavaCompiler.CompilationTask task = compiler.getTask( null, manager, null, null, null, iterable); task.call(); manager.close(); // 4. 編譯生成的 .class 類到 JVM 中 Class<?> clazz = Class.forName("com.github.binarylei.design.proxy.my.$Proxy1"); // 5. 返回字節碼重組之後新代理對象 Constructor<?> constructor = clazz.getConstructor(MyInvocationHandler.class); return constructor.newInstance(h); } catch (Exception e) { e.printStackTrace(); } return null; } private static final String ln = "\r\n"; private static String generateSrc(Class<?>[] interfaces) { StringBuilder sb = new StringBuilder(); sb.append("package com.github.binarylei.design.proxy.my;").append(ln); sb.append("import com.github.binarylei.design.proxy.UserService;").append(ln); sb.append("import java.lang.reflect.Method;").append(ln); sb.append("public final class $Proxy1 implements ").append(interfaces[0].getSimpleName()).append(" {").append(ln); sb.append("private static MyInvocationHandler h;").append(ln); sb.append("public $Proxy1(MyInvocationHandler h) throws Exception {").append(ln); sb.append("this.h = h;").append(ln); sb.append("}").append(ln); for (Class<?> clazz : interfaces) { Method[] methods = clazz.getMethods(); for (Method m : methods) { sb.append("public final void say() {").append(ln); sb.append("try {").append(ln); sb.append("Method m = Class.forName(\"").append(clazz.getName()).append("\").getMethod(\"") .append(m.getName()).append("\", new Class[0]);").append(ln); sb.append("h.invoke(this, m, (Object[]) null);").append(ln); sb.append("} catch (Throwable e) {").append(ln); sb.append("e.printStackTrace();").append(ln); sb.append("}").append(ln); sb.append("}").append(ln); } } sb.append("}").append(ln); return sb.toString(); } }
(3) 測試
public void test3() { UserServiceImpl obj = new UserServiceImpl(); UserService userService = (UserService) MyProxy.newProxyInstance( UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new MyInvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(proxy.getClass().getName()); System.out.println(method); Object ret = method.invoke(obj, args); return ret; } }); userService.say(); System.out.println(userService); }
參考:
- 實戰CGLib系列文章 MethodInterceptor和Enhancer_CGLib:https://yq.aliyun.com/ziliao/296216
天天用心記錄一點點。內容也許不重要,但習慣很重要!