Java 设计模式系列(十二)代理模式
设计模式之美 - 代理模式
设计模式之美目录: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);
}
参考:
- 实战CGLib系列文章 MethodInterceptor和Enhancer_CGLib:https://yq.aliyun.com/ziliao/296216
每天用心记录一点点。内容也许不重要,但习惯很重要!
最新文章
- Hemodynamic response function (HRF) - FAQ
- [DHCP服务]——一个验证DHCP原理实验(VMware)
- Heritrix源码分析(十二) Heritrix的控制中心(大脑)CrawlController(一)(转)
- 删除元素 不存在 NO 存在 输出余下元素
- IOS 弹出式 POPMenuView
- HDU 3577 Fast Arrangement (线段树区间更新)
- MAC 环境下初始化mysql root 密码
- 【C语言】在两个数成对出现的数组中找到一个单独的数。
- Ffmpeg 视频教程
- 【Android Developers Training】 78. 序言:执行网络操作
- MySQL之增_insert-replace
- Java面向对象 异常
- 利用python搭建Powersploit powershell脚本站点
- 组合(composite)模式
- 为准确生成执行计划更新统计信息-analyze与dbms_stats
- LNMP中常见的502错误及处理方法
- Win环境 Android Studio使用Git 教程 ( 一 )
- 6.SpringMVC2
- auto uninstaller 简体中文版 更新下载地址
- 不删除记录的表CRUD的常见处置
热门文章
- 每天一个linux命令(网络):【转载】ifconfig命令
- ambassador 学习四 grpc 处理
- [CLPR] 卷积神经网络的结构
- 渐进式 jpg 和 交错式 gif png 提高图片站体验
- 虚拟机Linux桥接模式下设置静态IP
- Oracle 性能调优之:使用 V$SQL_PLAN 视图查询内存中的执行计划
- PHP报错open_basedir restriction in effect
- tesseract-ocr训练方法
- Firemonkey Button 颜色
- 跟我学算法-PCA(降维)基本原理推导