0%

JDK动态代理

日志横切需求

有两个服务,分别是物流寄件服务和订单售后服务:

  • 物流
1
2
3
4
5
public interface LogisticService {

boolean delivery(String packageId);

}
  • 售后
1
2
3
4
5
public interface AfterSalesService {

public void applyAfterSales(String orderId);

}

它们的实现类:

  • 物流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ShunfengLogisticService implements LogisticService {

@Override
public boolean delivery(String packageId) {
System.out.println(String.format("顺丰妥投:包裹号%s", packageId));
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return true;
}

}
  • 售后
1
2
3
4
5
6
7
8
public class FangxinAferSalesService implements AfterSalesService {

@Override
public void applyAfterSales(String orderId) {
System.out.println(String.format("发起售后:订单号%s", orderId));
}

}

它们的实现都完美的运行了很长时间,支撑着业务的发展。但是,当业务发展到一定程度,我们需要精细化管理,我们期望知道“物流环节”和“售后环节”,它们的执行效率数据。比如什么时候开始的,输入数据是什么;什么时候结束的,运行结果是什么;这个过程消耗了多少时间。

静态代理实现

有种实现方式是静态代理。大概样子是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LogisticServiceProxy implements LogisticService {

private LogisticService delegate;

public LogisticServiceProxy(LogisticService delegate) {
this.delegate = delegate;
}

@Override
public boolean delivery(String packageId) {
// pre-handler: 登记日志
return delegate.delivery(packageId);
// post-handler: 统计时耗
}

}

使用代码如下:

1
2
LogisticService logisticService = new LogisticServiceProxy(new ShunfengLogisticService());
logisticService.delivery("SF1234");

这的确可以实现。但是有个问题,对于售后服务AfterSalesService,也得额外定义一个静态代理类,尽管它们要做的事情都是登记日志和统计时耗。我们自然会想着,这个功能能不能由一个服务来提供,但是可以服务于各个类的方法呢?可以的,这就是动态代理技术。

动态代理实现

基于JDK的动态代理Proxy.newProxyInstance,可以给实现接口的所有类提供代理类。

JDKDyProxy

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
33
34
35
36
37
38
39
40
41
42
43
44
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class JDKDyproxy implements InvocationHandler {

private Object target;

public JDKDyproxy(Object target) {
super();
this.target = target;
}

@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance( //
target.getClass().getClassLoader(), // classLoader
target.getClass().getInterfaces(), // interfaces
this); // InvocationHandler
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// proxy.getClass().getName() == com.sun.proxy.$Proxy0

System.out.println( //
String.format("pre-handler class: %s, method: %s, args: %s", target.getClass().getName(),
method.getName(), Arrays.asList(args)));

long tmStart = System.currentTimeMillis();
Object result = method.invoke(target, args);
long tmEnded = System.currentTimeMillis();

System.out.println( //
String.format("post-handler class: %s, method: %s, args: %s result: %s, time cost: %s",
target.getClass().getName(), method.getName(), Arrays.asList(args), //
result, (tmEnded - tmStart)));
return result;

}

}

invoke方法里,我们通过Object result = method.invoke(target, args);来调用目标方法。但是在调用它之前,登记了pre-handler日志;之后登记了post-handler日志,再登记了消耗时间。

使用样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class JDKDyproxyDemo {

public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

LogisticService logisticService = new JDKDyproxy(new ShunfengLogisticService()).getProxy();
logisticService.delivery("SF1234");

AfterSalesService afterSalesService = new JDKDyproxy(new FangxinAferSalesService()).getProxy();
afterSalesService.applyAfterSales("Order#123");

}

}

我们可以看到,我们通用的定义了JDKDyProxy类,便可以给LogisticServiceAfterSalesService都产生代理类。它的运行输出效果:

1
2
3
4
5
6
7
8
pre-handler class: io.downgoon.dyproxy.jdk.ShunfengLogisticService, method: delivery, args: [SF1234]
顺丰妥投:包裹号SF1234
post-handler class: io.downgoon.dyproxy.jdk.ShunfengLogisticService, method: delivery, args: [SF1234] result: true, time cost: 1004


pre-handler class: io.downgoon.dyproxy.jdk.FangxinAferSalesService, method: applyAfterSales, args: [Order#123]
发起售后:订单号Order#123
post-handler class: io.downgoon.dyproxy.jdk.FangxinAferSalesService, method: applyAfterSales, args: [Order#123] result: null, time cost: 0

生成的代理类

上述输出之所以会看到pre-handlerpost-handler,就是因为动态生成了代理类。那我们能不能看下,这些动态生成的代理类呢?可以的。默认情况下,这些动态代理类生成后,直接以字节流的形式被JVM的ClassLoader加载。为了更加显式的看着它们,我们可以做个配置,让动态生成的代理类保存的文件里:

1
2
System.getProperties().put( //
"sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

这些类保存在:

1
2
3
4
5
6
7
8
9
10
➜  dynamic-proxy6 git:(master) ✗ ls
README.md com pom.xml src target
➜ dynamic-proxy6 git:(master) ✗ tree com
com
└── sun
└── proxy
├── $Proxy0.class
└── $Proxy1.class

2 directories, 2 files

可以看到JDKDyProxy给两个接口生成了动态代理类,分别是com.sun.proxy.$Proxy0com.sun.proxy.$Proxy1两个。就是invoke方法的输入参数Object proxy的内容:

1
2
3
4
5
6
7
8
9
10
public class JDKDyproxy implements InvocationHandler {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// proxy.getClass().getName() == com.sun.proxy.$Proxy0
// proxy.getClass().getName() == com.sun.proxy.$Proxy1

}
}

我们反编译看看$Proxy0里面都是什么?用javap -c XXX.class即可:

1
2
3
4
5

➜ dynamic-proxy6 git:(master) ✗ ls
ByteCode.txt README.md com pom.xml src target
➜ dynamic-proxy6 git:(master) ✗ javap -c 'com.sun.proxy.$Proxy0' > 'ByteCode$Proxy0.txt'
➜ dynamic-proxy6 git:(master) ✗ javap -c 'com.sun.proxy.$Proxy0'

反编译结果:

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
33
34
35
36
37
38
39
public final class com.sun.proxy.$Proxy0 extends java.lang.reflect.Proxy implements io.downgoon.dyproxy.jdk.LogisticService {

public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler) throws ;
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return



public final boolean delivery(java.lang.String) throws ;
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #50 // Field m3:Ljava/lang/reflect/Method;
8: iconst_1
9: anewarray #22 // class java/lang/Object
12: dup
13: iconst_0
14: aload_1
15: aastore
16: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
21: checkcast #30 // class java/lang/Boolean
24: invokevirtual #34 // Method java/lang/Boolean.booleanValue:()Z
27: ireturn
28: athrow
29: astore_2
30: new #42 // class java/lang/reflect/UndeclaredThrowableException
33: dup
34: aload_2
35: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
38: athrow
Exception table:
from to target type
0 28 28 Class java/lang/Error
0 28 28 Class java/lang/RuntimeException
0 28 29 Class java/lang/Throwable

Proxy$1是:

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
33
34
35
36
37
public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements io.downgoon.dyproxy.jdk.AfterSalesService {

public com.sun.proxy.$Proxy1(java.lang.reflect.InvocationHandler) throws ;
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return


public final void applyAfterSales(java.lang.String) throws ;
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #50 // Field m3:Ljava/lang/reflect/Method;
8: iconst_1
9: anewarray #22 // class java/lang/Object
12: dup
13: iconst_0
14: aload_1
15: aastore
16: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
21: pop
22: return
23: athrow
24: astore_2
25: new #42 // class java/lang/reflect/UndeclaredThrowableException
28: dup
29: aload_2
30: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
33: athrow
Exception table:
from to target type
0 23 23 Class java/lang/Error
0 23 23 Class java/lang/RuntimeException
0 23 24 Class java/lang/Throwable

我们可以看到:

  1. 两个代理类:为两个接口提供代理,就必须生成两个动态代理类。
  2. 实现接口:每个动态代理类都实现了目标对象的接口,且继承了java.lang.reflect.Proxy对象。比如:com.sun.proxy.$Proxy0 extends java.lang.reflect.Proxy implements io.downgoon.dyproxy.jdk.LogisticService
  3. Callback:动态代理类里面会通过16: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;来回调InvocationHandler.invoke。正因为InvocationHandlerpublic Object invoke(Object proxy, Method method, Object[] args)方法被$Proxy0$Proxy1共享,所以invoke时,第一个输入参数Object proxy才会自报家门。

实现多接口

我们可以定义一个MixedService,它实现两个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MixedService implements LogisticService, AfterSalesService {

@Override
public boolean delivery(String packageId) {
System.out.println(String.format("顺丰妥投:包裹号%s", packageId));
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return true;
}

@Override
public void applyAfterSales(String orderId) {
System.out.println(String.format("发起售后:订单号%s", orderId));
}

}

使用代码如下(给MixedService创建一个动态代理类):

1
2
3
4
5
6
7
8
9
public class JDKDyproxyDemo {

public static void main(String[] args) {
Object mixedService = new JDKDyproxy(new MixedService()).getProxy();
((LogisticService) mixedService).delivery("SF4321");
((AfterSalesService) mixedService).applyAfterSales("Order#321");
}

}

它生成的代理类是com.sun.proxy.$Proxy2

1
2
3
4
5
6
7
8
9
10
public final class com.sun.proxy.$Proxy2 extends java.lang.reflect.Proxy
implements io.downgoon.dyproxy.jdk.LogisticService,
io.downgoon.dyproxy.jdk.AfterSalesService {

public com.sun.proxy.$Proxy2(java.lang.reflect.InvocationHandler) throws ;
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return

跟前面Proxy$0Proxy$1不一样的是Proxy$2实现了两个接口。

动态代理原理

官方文档

官方文档查看如何使用Proxy (java.lang.reflect.Proxy):

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

To create a proxy for some interface Foo:

1
2
3
4
5
6
7
8
InvocationHandler handler = new MyInvocationHandler(...);

Class proxyClass = Proxy.getProxyClass(
Foo.class.getClassLoader(), new Class[] { Foo.class });

Foo f = (Foo) proxyClass.getConstructor(
new Class[] { InvocationHandler.class }
).newInstance(new Object[] { handler });

or more simply:

1
2
3
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class },
handler);

源代码

实现原理我们看看JDK的代码:

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
package java.lang.reflect;

public class Proxy implements java.io.Serializable {

public static Object newProxyInstance(
ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {

final Class<?>[] intfs = interfaces.clone();

/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs); // 关键1

final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
}

private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}

// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
}

这里的proxyClassCache定义为proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());。按图索骥,最终生成代理类的:

1
2
3
4
5
 /*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);

这个类是class sun.misc.ProxyGenerator,这个并不在JDK开源代码里。

自己实现

为了说明它的原理,最好自己尝试实现一个。步骤:

  1. 拼代码: 给我们几个接口类,我们要为每个接口,实现它的方法,并且在关键时刻调用InvocationHandler回调。
  2. 类加载: 对于拼接完成的代码,需要用ClassLoader加载到JVM里。
  3. 换对象: 让需要目标接口的地方,都统统替换成动态生成的代理类。
  • 生成代理
1
2
3
static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
  • 目标回调
1
2
3
4
public class JDKDyproxy implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
  • 参数传递
1
2
LogisticService logisticService = new JDKDyproxy(new 									                           ShunfengLogisticService()).getProxy(); // 调用 newProxyInstance
logisticService.delivery("SF1234");
  • 动态代理类:如何拼接代码?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.downgoon.dyproxy;

import java.lang.reflect.*;

import io.downgoon.dyproxy.jdk.*;

public class $Proxy implements io.downgoon.dyproxy.jdk.LogisticService {

InvocationHandler h;

public $Proxy (InvocationHandler h ) { this.h = h; }

@Override
public boolean delivery(String p0) {
try{
Method method = io.downgoon.dyproxy.jdk.LogisticService.class.getMethod("delivery");
this.h.invoke(this, method, new Object[] {p0} );
}catch(Exception e){
e.printStackTrace();
}
}
}
  • 类加载:拼接的代码可以保存到文件,也可以直接以流的形式加载到JVM
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
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

// 保存源代码到文件
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(proxySrcCode);
fileWriter.flush();
fileWriter.close();


// 源代码编译:此时会由 .java 生成 .class
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> iterable = fileManager.getJavaFileObjects(filePath);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();

// 字节码加载
URL url = new URL("file://" + System.getProperty("user.dir") );
System.out.println("File: " + url.getFile().toString());
URLClassLoader classLoader = new URLClassLoader(new URL[] { new URL("file://" + System.getProperty("user.dir") ) });
Class<?> clazz = classLoader.loadClass("com.downgoon.dyproxy.$Proxy");
Constructor<?> constructor = clazz.getConstructor(InvocationHandler.class);
return constructor.newInstance(h);

AnnotationCallback

过去我们往往用面向接口,然后实现类去实现接口。但如今,早在Struts2的Action就可以不实现接口,用反射。现在许多都是基于Annotation,尽管底层框架依然支持面向接口,但是在面向客户端代码的使用层,会转换成基于Annotation的模式。如下图,当底层回调LogisticService时,适配器会去调用带@logistic注解的类。

annotation-callback-adaptor