动态代理模式是指完全借助proxy对象执行被代理对象接口的设计模式。 动态代理模式的“动态”体现在代理对象代理的接口所来自的实例方法是可以动态指配的。动态代理模式可以应用于:
- 网络服务接口的调用
- 对一些方法做统一的处理以减少重复代码
- 测试mock
动态代理模式的特征是代理类(ProxyClass)和被代理类(RealSubject)实现同一个接口(doAction),使用者(Client)不直接访问被代理类的接口,而是借助代理类实现对接口的访问。
图 1 动态代理模式UML示意图
在Java中,实现动态代理模式的相关工具有JDK自带的动态代理模块(java.lang.reflect)、CgLib和JavaAssist等。首先学习最简单最经典的动态代理实现。
一、JDK下简单的动态代理实现
我们在ChineseUser下定义一个show接口:
1 2 3 4 5 6 7
|
public interface ChineseUser { void show(); }
|
被代理类目前有2个,分别是ChineseYoungMan和ChineseOldMan:
1 2 3 4 5 6 7
| public class ChineseYoungMan implements ChineseUser {
@Override public void show() { System.out.println("我是一个中国青年"); } }
|
1 2 3 4 5 6 7
| public class ChineseOldMan implements ChineseUser {
@Override public void show() { System.out.println("我是一个中国老人"); } }
|
在动态代理模式下,不存在具体的代理类,而是由通用的java.lang.reflect.Proxy
类替代。代理对象是由Proxy.newProxyInstance()
函数动态生成的,我们通过一个代理工厂类包装起来:
1 2 3 4 5 6 7 8 9
| import java.lang.reflect.Proxy;
public class UserProxyFactory { public static Object getProxyInstance(Object obj) { MyInvocationHandler myHandler = new MyInvocationHandler(); myHandler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), myHandler); } }
|
上面代码实现了一个代理对象工厂,关键的函数在于Proxy.newProxyInstance()
,其有3个参数:第1个参数表示被代理对象的类加载器,第2个参数表示被代理对象的接口集合(本例中即show
接口),第3个参数是实现了InvocationHandler.invoke()
接口的对象。该对象实现了将对代理类接口的调用映射至其invoke
接口的功能。
我们再来看看 MyInvocationHandler
中是如何调用代理类方法的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object obj;
public void bind(Object obj) { this.obj = obj; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(obj, args); } }
|
本质就是通过反射机制调用 method.invoke
方法来实现的对show接口的调用,返回值即为show接口返回值。
最后编写测试类(使用者):
1 2 3 4 5 6 7 8 9 10
| public class TextProxy { public static void main(String[] args) { ChineseYoungMan user1 = new ChineseYoungMan(); ChineseUser human = (ChineseUser) UserProxyFactory.getProxyInstance(user1); human.show(); ChineseUser user2 = new ChineseOldMan(); human = (ChineseUser) UserProxyFactory.getProxyInstance(user2); human.show(); } }
|
简单运行看下结果:
运行结果图
简单总结下JDK动态代理模式的4大要素就是:1. 接口; 2. 被代理方法; 3. 生成代理对象Proxy.newProxyInstance(); 4. 通过InvocationHandler.invoke()映射处理调用逻辑。
介绍完基本的JDK动态代理模式后,讲讲动态代理的典型应用场景,包括RPC和Spring AOP。接下来分别结合实例代码理解动态代理模式的使用。
二、RPC中的动态代理模式
在RPC使用的动态代理模式中,主要要素和JDK动态代理模式的相同,所不同的是在调用处理函数invoke中需要使用http客户端进行数据远程调用。
图 2 RPC中使用的动态代理模式
我们以JDChain为例,按照4大要素的模式进行分析:
1. 定义接口:
JDChain网关提供了服务接口BlockchainService
,通过GatewayServiceFactory
创建,该接口进一步细化为用于操作链的HttpBlockchainBrowserService
和用于执行交易的TransactionService
:
服务接口工厂类
执行交易接口
2. 被代理方法:
以TransactionService
定义的process接口为例,其实现方法在TxProcessingController
中:
被代理类实现方法
注意process接口将由合约客户端(Client)发起调用,被代理方法将由区块链平台节点(远端)来执行。
3. 生成代理对象
代理对象生成时候会创建到远端节点的http连接。以客户端部署合约插件执行函数为例,通过类工厂生成了代理对象blockchainService
客户端生成代理对象
对blockchainService接口的调用将通过下一步的invoke函数转为HTTP报文发出。
4. invoke处理映射逻辑
InvocationHandler映射处理逻辑
可以学习一下这里解析生成HTTP报文的操作。
三、Spring AOP中的动态代理模式
与发送http/tcp请求不同,aop在invoke函数中运行了method.invoke进行原方法的调用。在method.invoke之前执行的就是前置切面,在method.invoke之后执行的就是后置切面。
通过一个简单的demo就可以展示:
1.接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public interface Subject {
Subject request(String name);
String getTime();
void test(); }
|
2.被代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class SubjectImpl implements Subject { @Override public Subject request(String name) { System.out.println("调用request方法: " + name); return this; }
@Override public String getTime() { System.out.println("调用getTime方法"); return new Date().toString(); }
@Override public void test() { System.out.println("调用无参方法"); } }
|
3.代理对象生成:
1 2 3 4 5
| Subject subject = new SubjectImpl(); InvocationHandler handler = new ProxyHandler(subject); Class<?> clazz = subject.getClass(); Subject proxy = (Subject) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), handler); System.out.println(proxy.request("name").request("yes").getTime());
|
4.代理调用处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class ProxyHandler implements InvocationHandler { private Object object;
public ProxyHandler(Object object) { this.object = object; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置切面"); if (method.getName().equals("request")) { method.invoke(this.object, args); System.out.println("后置切面"); return proxy; } else if (method.getName().equals("getTime")) { Object time = method.invoke(this.object, args); System.out.println("后置切面"); return time; } else { method.invoke(this.object, args); System.out.println("后置切面"); return "hello world"; } } }
|
Spring AOP使用方法
在SpringBoot项目中添加依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
注入切面类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Aspect @Component public class LogAspect {
@Pointcut("execution(* org.springframework.cloud.netflix.eureka.server.InstanceRegistry.register(..))") public void controllerRequestPointcut() {}
@Around("controllerRequestPointcut()") public Object Interceptor(ProceedingJoinPoint pjp) { } }
|
四、JuLiuSc合约平台中的使用
获取jar包执行方法并展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| URLClassLoader contractCLassLoader = contractService.loadContractJar(contractJarName); Class clazz = Class.forName(contractRefName, false, contractCLassLoader); Object argObj = null; String contractAddr = rand.nextString();
if (null != argsMap) { List<Class> argClasses = new ArrayList<>(); List<Object> argObjects = new ArrayList<>(); for (Map.Entry<String, Object> arg : argsMap.entrySet()) { Class argClazz = Class.forName(arg.getKey()); argClasses.add(argClazz); argObjects.add(argClazz.cast(arg.getValue())); } Constructor constructor = clazz.getConstructor(argClasses.toArray(new Class[argClasses.size()])); argObj = constructor.newInstance(argObjects.toArray(new Object[argObjects.size()])); } else { Constructor constructor = clazz.getConstructor(); argObj = constructor.newInstance(); }
|
注意 clazz.getConstructor
函数的参数由 List
转 []
类型,不可以直接使用强制类型转换 (Class[]) argClasses.toArray()
,不然会报错如下:
1
| java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Class;
|
评论
shortname
for Disqus. Please set it in_config.yml
.