0%

java基础学习之动态代理

静态代理

在我的理解中,静态代理中代理类就是个中间处理类,实现被代理类中的方法调用

静态代理大致就是客户端实例化一个代理类,被代理类对象作为参数进入代理类实现方法调用。在其中代理类以及被代理类都是共同接口实现类。

java动态代理

java的动态代理主要分为两类jdk动态代理CGLIB动态代理

jdk动态代理

jdk动态代理执行方法调用的过程简图:

image-20201010171220499

client通过jdk动态代理执行被代理类UserServiceImplselect()方法和update()方法。创建代理主要涉及的两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

简单实现实现一下动态代理过程

创建一个接口类UserService.java,存在select()以及update()方法

1
2
3
4
5
6
package proxy;

public interface UserService {
public void select();
public void update();
}

被代理类UserServiceImpl.java实现UserService接口中的select()方法和update()方法

1
2
3
4
5
6
7
8
9
10
11
12
package proxy;

public class UserServiceImpl implements UserService{

public void select() {
System.out.println("查询 selectById");
}

public void update() {
System.out.println("更新 update");
}
}

日志逻辑处理器LogHandler.java,实现java.lang.reflect.InvocationHandler接口,在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色)在 invoke 方法中编写方法调用的逻辑处理

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
Object target;

public LogHandler(Object target){
this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
// 调用invoke方法之前执行
private void before(){
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法之后执行
private void after(){
System.out.println(String.format("log end time [%s] ", new Date()));
}
}java

客户端Client.java调用java.lang.reflect.Proxy代理类实现动态代理,这里运用反射把被代理类的Classloader以及接口获取,再创建日志处理器对象传给代理类

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

import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {
public static void main(String[] args) {
// 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 1. 创建被代理的对象,UserService接口的实现类
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
Class[] interfaces = userServiceImpl.getClass().getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
// 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
InvocationHandler logHandler = new LogHandler(userServiceImpl);
/*
5.根据上面提供的信息,创建代理对象 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建代理实例
*/
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
// 调用代理的方法
proxy.select();
proxy.update();

// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
}
}

创建一个ProxyUtils类把动态生成的类保存出来

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

import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

public class ProxyUtils {
/**
* 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
* params: clazz 需要生成动态代理类的类
* proxyName: 为动态生成的代理类的名称
*/
public static void generateClassFile(Class clazz, String proxyName) {
// 根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盘中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

然后在UserServiceProxy.class中可以看到动态代理类中会先调用super.h.invoke(this, m1, (Object[])null)其中h就是创建代理时传递给Proxy.newProxyInstance的日志处理器类LogHandler

CGLIB动态代理

CGLIB动态代理主要是利用ASM机制,需要使用Javassist,本人对于Javassist还不是很了解,先暂时留个坑位等学了再回来填坑

References

https://www.cnblogs.com/whirly/p/10154887.html