java 中的动态代理

介绍

这篇文章是关于java动态代理的,它是 java 语言提供给我们主要的代理机制。

简单的说,代理就是使用自己定义的方法,包装原始对象的方法调用,在自己的方法中可以做一些额外的事情。用户直接使用代理对象完成业务请求。

动态代理允许一个具有单个方法的类为任意数量的方法对任意类进行多个方法调用。动态代理可以看做是一个「Facade」模式,但是也可以包装成一个接口的实现。在这两种特性之下, 它可以将所有方法的调用路由到某一个方法 invoke() 中。

代理并不常用于一些日常的开发任务的。动态代理经常用于框架的编程,或者只有在运行时才能确定具体的类实现的场景中。

此特性内置于标准的 JDK 中,因此不需要额外的依赖项。

Invocation Handler

首先来构建一个简单的代理,除了打印请求调用的方法之外,实际上什么也不做,并返回一个硬编码的数字。

首先创建一个实现了 java.lang.reflect.InvocationHandler 接口的的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.logging.Logger;


public class DynamicInvocationHandler implements InvocationHandler {

private static Logger LOGGER = Logger.getLogger("DynamicInvocationHandler");

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
LOGGER.info("invoke method {} " + method.getName());
return 1;
}
}

上面定义了一个简单的代理,日志输出哪个方法被调用了,并且返回1。

创建 proxy 实例

现在通过类 java.lang.reflect.Proxy 类的工厂方法 newProxyInstance() 创建刚刚定义的 InvocationHandler 类实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.proxy;

import java.lang.reflect.Proxy;
import java.util.Map;


public class Main {
public static void main(String[] args) {
Map map = (Map) Proxy.newProxyInstance(Main.class.getClassLoader(),
new Class[]{Map.class},
new DynamicInvocationHandler());
map.put("some", "stuff");
}
}

输出结果为:

一月 23, 2018 11:53:54 上午 com.proxy.DynamicInvocationHandler invoke

信息: invoke method {} put

通过 lambda 表达式实现 InvocationHandler

由于 java.lang.reflect.InvocationHandler 是一个函数式接口,可以使用lambda表达式直接定义一个 Invocation Handler 。

1
2
3
4
5
6
7
8
9
10
11
public static void main2() {
Proxy.newProxyInstance(Main.class.getClassLoader(),
new Class[]{Map.class},
(Object proxy, Method method, Object[] args) -> {
if (method.getName().equals("get")) {
return 1;
} else {
throw new UnsupportedOperationException("Unsupported method: " + method.getName());
}
});
}

main2() 方法中,定义了一个 InvocationHandler ,如果 get() 方法被调用,则返回1,否则抛出 UnsupportedOperationException 异常。

1
2
3
Object some = map.get("some");
System.out.println(some);
map.put("some", "stuff");

返回结果:

23

Exception in thread “main” java.lang.UnsupportedOperationException:
Unsupported method: put

at com.proxy.Main.lambda$main2$0(Main.java:22)

at com.sun.proxy.$Proxy0.put(Unknown Source)

at com.proxy.Main.main2(Main.java:29)

at com.proxy.Main.main(Main.java:12)

实际应用场景

有这样一个应用场景,假设我们想记录下我们的方法的调用耗时,此时我们先定义一个Invocation Handler,来包装我们的真实对象,即:target 对象,通过反射获取到 target 对象的信息。

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
26
27
28
29
30
31
32
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;


public class TimingDynamicInvocationHandler implements InvocationHandler {
private static final Logger LOGGER = Logger.getLogger("TimingDynamicInvocationHandler");

private Object target;

private final Map<String, Method> methods = new HashMap<String, Method>();

public TimingDynamicInvocationHandler(Object target) {
this.target = target;
for (Method method : target.getClass().getDeclaredMethods()) {
this.methods.put(method.getName(), method);
}
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.nanoTime();
Object result = methods.get(method.getName()).invoke(target, args);
long elapsed = System.nanoTime() - startTime;
LOGGER.info("Executing " + method.getName() + "finished in " + elapsed + "ns");
return result;
}
}

然后,这个代理可以被运用于各种对象类型中:

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
package com.proxy;

import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class TimingDynamicProxyTest {

public static void main(String[] args) {
//代理HashMap对象
Map mapProxyInstance = (Map) Proxy.newProxyInstance(TimingDynamicProxyTest.class.getClassLoader(),
new Class[]{Map.class}, new TimingDynamicInvocationHandler(new HashMap<>()));
mapProxyInstance.put("some", "something");
Object some = mapProxyInstance.get("some");
System.out.println(some);

//代理CharSequence对象
CharSequence helloWorld = (CharSequence) Proxy.newProxyInstance(TimingDynamicProxyTest.class.getClassLoader(),
new Class[]{CharSequence.class}, new TimingDynamicInvocationHandler("hello world"));
int length = helloWorld.length();
System.out.println(length);

}
}

输出内容为:

一月 23, 2018 2:39:00 下午 com.proxy.TimingDynamicInvocationHandler invoke

信息: Executing “put” finished in 17728ns

一月 23, 2018 2:39:00 下午 com.proxy.TimingDynamicInvocationHandler invoke

信息: Executing “get” finished in 10575ns

一月 23, 2018 2:39:00 下午 com.proxy.TimingDynamicInvocationHandler invoke

信息: Executing “length” finished in 6531ns

something

11

总结

以上的例子,简单的介绍了 java 动态代理的使用方法和可能的应用场景。这些场景更多的使用在框架的设计之中。

------ 本文结束 ------
TisaKong wechat
启发思维个人微信公众号
0%