设计模式之美 - 代理模式

设计模式之美目录:https://www.cnblogs.com/binarylei/p/8999236.html

代理模式:给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。GoF 的《设计模式》一书中把 RPC 称作远程代理。其它应用场景如缓存、监控、统计、鉴权、限流、事务、幂等、日志等。

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();
}

很明显静态代理每个被代理的类都要手写一个代理类,当修改被代理的类时也要修改对应的代理类。解决这个问题则是由程序来生成对应的代理类,这就是动态代理。

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: $Proxy0

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 接口

@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);
}

参考:

  1. 实战CGLib系列文章 MethodInterceptor和Enhancer_CGLib:https://yq.aliyun.com/ziliao/296216

每天用心记录一点点。内容也许不重要,但习惯很重要!

最新文章

  1. Hemodynamic response function (HRF) - FAQ
  2. [DHCP服务]——一个验证DHCP原理实验(VMware)
  3. Heritrix源码分析(十二) Heritrix的控制中心(大脑)CrawlController(一)(转)
  4. 删除元素 不存在 NO 存在 输出余下元素
  5. IOS 弹出式 POPMenuView
  6. HDU 3577 Fast Arrangement (线段树区间更新)
  7. MAC 环境下初始化mysql root 密码
  8. 【C语言】在两个数成对出现的数组中找到一个单独的数。
  9. Ffmpeg 视频教程
  10. 【Android Developers Training】 78. 序言:执行网络操作
  11. MySQL之增_insert-replace
  12. Java面向对象 异常
  13. 利用python搭建Powersploit powershell脚本站点
  14. 组合(composite)模式
  15. 为准确生成执行计划更新统计信息-analyze与dbms_stats
  16. LNMP中常见的502错误及处理方法
  17. Win环境 Android Studio使用Git 教程 ( 一 )
  18. 6.SpringMVC2
  19. auto uninstaller 简体中文版 更新下载地址
  20. 不删除记录的表CRUD的常见处置

热门文章

  1. 每天一个linux命令(网络):【转载】ifconfig命令
  2. ambassador 学习四 grpc 处理
  3. [CLPR] 卷积神经网络的结构
  4. 渐进式 jpg 和 交错式 gif png 提高图片站体验
  5. 虚拟机Linux桥接模式下设置静态IP
  6. Oracle 性能调优之:使用 V$SQL_PLAN 视图查询内存中的执行计划
  7. PHP报错open_basedir restriction in effect
  8. tesseract-ocr训练方法
  9. Firemonkey Button 颜色
  10. 跟我学算法-PCA(降维)基本原理推导