0%

java动态代理学习笔记

用法

java动态代理机制可以实现使用一个抽象的中介类对任意类的任意方法进行进行代理,大致原理是运行时生成一个代理类,代理类再调用委托对象。

创建委托对象的接口

1
2
3
4
public interface Sell {
void sell();
void add();
}

创建接口的实现

1
2
3
4
5
6
7
8
9
10
11
public class SellImpl implements Sell {
@Override
public void sell() {
System.out.println("In sell method.");
}

@Override
public void add() {
System.out.println("In add method.");
}
}

实现中介类

中介类实现java.lang.reflect.InvocationHandler接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyInvocationHandler implements InvocationHandler {
// 委托类对象
private Object object;

public DynamicProxy(Object object){
this.object=object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("Before");
Object result = method.invoke(object, args);
System.out.println("After");
return result;
}
}

用户调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
//创建中介类实例
MyInvocationHandler inter = new MyInvocationHandler(new SellImpl());
//加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

//获取代理类实例sell
Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));

//通过代理类对象调用代理类方法,实际上会转到invoke方法调用
sell.sell();
sell.add();
}
}

原理分析

  1. 动态生成代理类,并且动态编译,再通过反射创建对象并加载到内存中:
graph LR
   proxy[Proxy.newProxyInstance] -- JavaPoet --> java[C$Proxy0.java]
   java -- Compile --> clazz[C$Proxy0.class]
   clazz -- reflect --> sell[C$Proxy0]
  1. 在调用时,C$Proxy0代理InvocationHandler,InvocationHander代理subject。

生成代理类源码

newProxyInstance就是将中介类生成代理类源代码的方法,生成的代理类如下,可以看到其包含一个InvocationHander类,实现subject的接口,使用反射调用InvocationHander:

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
import java.lang.Override;
import java.lang.reflect.Method;

class Proxy0 implements Sell {
private InvocationHandler handler;

public Proxy0(InvocationHandler handler) {
this.handler=handler;
}

@Override
public void add() {
try {
Method method = MyImpl.Sell.class.getMethod("add");
this.handler.invoke(this, method, null);
} catch(Exception e) {
e.printStackTrace();
}
}

@Override
public void sell() {
try {
Method method = MyImpl.Sell.class.getMethod("sell");
this.handler.invoke(this, method, null);
} catch(Exception e) {
e.printStackTrace();
}
}
}

可以使用JavaPoet实现,代码来自10分钟看懂动态代理设计模式:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import javax.lang.model.element.Modifier;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class Proxy {

public static Object newProxyInstance(Class subject,InvocationHandler handler) throws IOException,
ClassNotFoundException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 生成proxy.java
TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("Proxy0")
.addSuperinterface(subject);

FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();
typeSpecBuilder.addField(fieldSpec);

MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(InvocationHandler.class, "handler")
.addStatement("this.handler=handler")
.build();
typeSpecBuilder.addMethod(constructorMethodSpec);

Method[] methods = subject.getDeclaredMethods();
for (Method method : methods) {
MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(method.getReturnType())
.addCode("try {\n")
.addStatement("\t$T method = " + subject.getName() + ".class.getMethod(\"" + method.getName() + "\")",
Method.class)
// 为了简单起见,这里参数直接写死为空
.addStatement("\tthis.handler.invoke(this, method, null)")
.addCode("} catch(Exception e) {\n")
.addCode("\te.printStackTrace();\n")
.addCode("}\n")
.build();
typeSpecBuilder.addMethod(methodSpec);
}


JavaFile javaFile = JavaFile.builder("MyImpl", typeSpecBuilder.build()).build();
// 为了看的更清楚,我将源码文件生成到桌面
String srcPath="./MyProxy";
javaFile.writeTo(new File(srcPath));

// 编译源代码
// TODO

// 加载进内存并反射创建对象
// TODO
return obj;
}
}

编译Proxy0的源代码

1
2
3
4
5
6
7
8
9
10
public class JavaCompiler {
public static void compile(File javaFile) throws IOException {
javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
Iterable iterable = fileManager.getJavaFileObjects(javaFile);
javax.tools.JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();
}
}

在Proxy.newProxyInstance()中调用:

1
JavaCompiler.compile(new File(srcPath+"/MyImpl/Proxy0.java"));

加载进内存并创建对象

使用URLClassLoader加载.class文件

1
2
3
4
5
6
7
File filpath=new File("");
URL[] urls = new URL[] {new URL("file:"+filpath.getAbsoluteFile()+"\\MyProxy\\")};
URLClassLoader classLoader = new URLClassLoader(urls);
Class clazz = classLoader.loadClass("MyImpl.Proxy0");
Constructor constructor = clazz.getConstructor(InvocationHandler.class);
constructor.setAccessible(true); //不懂 为什么已经是public的方法还需要setAccessible
Object obj = constructor.newInstance(handler);

使用

可以像系统内置的那样设置动态代理

1
Sell sell = (Sell)Proxy.newProxyInstance(Sell.class, new MyInvocationHandler(new SellImpl()));

参考文献