设计模式 之 动态代理

动态代理模式是指完全借助proxy对象执行被代理对象接口的设计模式。 动态代理模式的“动态”体现在代理对象代理的接口所来自的实例方法是可以动态指配的。动态代理模式可以应用于:

  1. 网络服务接口的调用
  2. 对一些方法做统一的处理以减少重复代码
  3. 测试mock

  动态代理模式的特征是代理类(ProxyClass)和被代理类(RealSubject)实现同一个接口(doAction),使用者(Client)不直接访问被代理类的接口,而是借助代理类实现对接口的访问。

图 1 动态代理模式UML示意图

  在Java中,实现动态代理模式的相关工具有JDK自带的动态代理模块(java.lang.reflect)、CgLib和JavaAssist等。首先学习最简单最经典的动态代理实现。

一、JDK下简单的动态代理实现

我们在ChineseUser下定义一个show接口:

1
2
3
4
5
6
7
/*
这是一个动态代理调用的模拟接口,该接口的实现可以是一个本地方法,
也可以是一个远程方法,即通过转化为http报文发出
*/
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;
}

/*
当通过代理类的对象调用方法a时,就会动态转为调用如下的invoke方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
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);

/**
* 方法无具体含义,为了测试方法的返回值
* @return
*/
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"; // return null 或任意一个对象都是可以的
}
}
}

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 {
/**
* 定义拦截规则:拦截org.springframework.cloud.netflix.eureka.server.InstanceRegistry.register方法
*/
@Pointcut("execution(* org.springframework.cloud.netflix.eureka.server.InstanceRegistry.register(..))")
public void controllerRequestPointcut() {}

@Around("controllerRequestPointcut()")
public Object Interceptor(ProceedingJoinPoint pjp) {
// aspect code here
}
}

四、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();

// select the constructor based on args to initialize,
// else the default constructor is used
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;
Operating System Maintainance and Cleaning 使用Swagger构建接口文档

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×