Java动态代理
代理模式
我们生活中充满了代理的例子:
- 例如房产中介,卖房者将房子委托给房产中介,买房者买房去房产中介而不是直接去寻找卖房者,这里面房产中介就是代理。
- 再比如,现在都流行点外卖,在点外卖的场景中,商户将自己委托给外卖平台,而客户在这个外卖平台直接下单,省去了实地寻找商户的过程,这里外卖平台就是代理
通过上述生活中的实际例子,相信我们都知道代理是什么了,代理模式是一种结构型设计模式,让你能够提供对象的替代品或其占位符,代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
下面是代理模式的类图:
在实际的使用中,代理模式又衍生出了静态代理和动态代理,静态代理就是我们将要代理的服务硬编码到代码中,运行期间无法进行修改;
而动态代理是在运行期间,或者说加载对象的时候,选择我们要代理的服务对其进行代理的方法,动态代理是在运行时生成它对应的代理类。
通过代理我们可以增强原有类的功能。
静态代理
首先我们看看静态代理的例子
静态代理示例代码:
// 代理要实现的接口
public interface UserService {
/**
* 测试静态代理的方法
*/
void addUser();
}
// 代理类接口的实现
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("成功添加用户!");
}
}
// 进行代理类的代理
public class UserServiceProxy implements UserService{
UserService impl;
public UserServiceProxy() {
this.impl = new UserServiceImpl();
}
@Override
public void addUser() {
System.out.println("进行了代理!");
impl.addUser();
}
}
// 测试静态代理
public class Client {
public static void main(String[] args) {
UserService proxy = new UserServiceProxy();
proxy.addUser();
}
}
上述静态代理对应的类图如下:
动态代理
首先我们回顾下java类是如何加载的:
我们可以通过修改.class文件的指令来生成需要的代理对象,这就是动态代理,例如cglib就是这样实现动态代理的。
在jdk的动态代理中,是通过适配器来代理源目标的:
我们来看看使用javassist实现自己的动态代理:
首先看看javassist的maven仓库
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
接下来就是具体的实现
package com.wyg.learn_javassist;
import javassist.*;
import javassist.util.proxy.ProxyFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* @description:
* @author: WYG
* @time: 2021/4/8 19:34
*/
public class TestJavassist {
public static void main(String[] args) throws Exception {
// 我们自己使用javassist实现的动态代理方法
UserService userService = perfectCreateProxy(UserService.class, new InvocationHandler() {
@Override
public Object invoke(String methodName, Object[] args) {
if (methodName.equals("sayHello")) {
System.out.println("hello:\t" + args[0]);
} else if (methodName.equals("addUser")) {
System.out.println("添加user" + args[0] + "成功!");
}
return null;
}
});
userService.sayHello("wyg");
userService.addUser("wyg");
// jdk的动态代理方法
JdkProxy();
}
// 简易版动态代理,主要了解了javassist的基本使用
public static UserService createProxy() throws Exception {
ClassPool classPool = new ClassPool();
// 设置classloader
classPool.appendSystemPath();
// javassist --> ASM(编辑jvm指令码)
// 1、创建一个类
CtClass userServiceImpl = classPool.makeClass("UserServiceImpl");
userServiceImpl.addInterface(classPool.get(UserService.class.getName()));
// 2、创建一个方法
CtMethod sayHello = CtNewMethod.make(CtClass.voidType, // 方法的返回类型
"sayHello", // 方法名称
new CtClass[]{classPool.get(String.class.getName())}, // 方法的参数类型
new CtClass[0], // 方法抛出的异常
"System.out.println(\"hello:\" + $1);", // 方法的具体实现
userServiceImpl);// 将该方法加入到对应的class中
userServiceImpl.addMethod(sayHello);
// 3、实例化一个对象
Class aClass = classPool.toClass(userServiceImpl);
return (UserService) aClass.getDeclaredConstructor().newInstance();
}
// 1.支持所有接口代理
// 2.按常规方式传递实现
static int count = 0;
/**
*
* @param calInterface 接口类
* @param invocationHandler 调用方法的辅助类
* @param <T> 返回类型
* @return
* @throws Exception
*/
public static <T> T perfectCreateProxy(Class<T> calInterface, InvocationHandler invocationHandler) throws Exception {
// 1.创建一个类
ClassPool classPool = new ClassPool();
classPool.appendSystemPath();
CtClass impl = classPool.makeClass("$proxy" + count++);
impl.addInterface(classPool.get(calInterface.getName()));
// 2.添加属性handler对象
CtField field = CtField.
make("public com.wyg.learn_javassist.TestJavassist.InvocationHandler handler = null;", impl);
impl.addField(field);
// 传递方法名称和参数,s为方法名,args为参数
String src = "return ($r)this.handler.invoke(\"%s\", $args);";
String voidSrc = "this.handler.invoke(\"%s\", $args);";
// 3.创建这个接口下的所有方法实现
for (Method method : calInterface.getMethods()) {
CtClass returnType = classPool.get(method.getReturnType().getName());
String name = method.getName();
CtClass[] parameters = toCtClass(classPool, method.getParameterTypes());
CtClass[] errors = toCtClass(classPool, method.getExceptionTypes());
String srcImpl = method.getReturnType().equals(Void.class) ? voidSrc : src;
CtMethod newMethod = CtNewMethod.make(returnType, name, parameters, errors, String.format(srcImpl, name), impl);
impl.addMethod(newMethod);
}
// 生成字节码
byte[] bytes = impl.toBytecode();
// 类加载到当前的ClassLoader中
Class aClass = classPool.toClass(impl);
Object o = aClass.getDeclaredConstructor().newInstance();
aClass.getField("handler").set(o, invocationHandler);
return (T) o;
}
// 将普通的类转换成CtClass
private static CtClass[] toCtClass(ClassPool classPool, Class[] classes) {
return Arrays.stream(classes).map(c -> {
try {
return classPool.get(c.getName());
} catch (NotFoundException e) {
throw new RuntimeException();
}
}).collect(Collectors.toList()).toArray(new CtClass[0]);
}
// 我们自己的InvocationHandler对象
public interface InvocationHandler {
Object invoke(String methodName, Object args[]);
}
public class InvocationHandlerImpl implements InvocationHandler {
@Override
public Object invoke(String methodName, Object[] args) {
System.out.println("test");
return null;
}
}
// 我们要代理的接口
public interface UserService {
void sayHello(String name);
void addUser(String name);
}
// 使用jdk的动态代理来代理我们的实现类
public static void JdkProxy() {
ClassLoader classLoader = ProxyFactory.class.getClassLoader();
Class<?>[] interfaces = new Class[]{ UserService.class };
java.lang.reflect.InvocationHandler handler = ((proxy, method, args) -> {
System.out.println("hello wyg");
return null;
});
UserService service = (UserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
service.sayHello("wyg");
}
}
动态代理的使用
动态代理有增强型动态代理和链接型动态代理:
- 增强型代理:aop、日志打印、声明式事务、监控等,这些都在原有类的基础上增加了一定的功能
- 链接型代理:远程调用(如rpc)、Mybatis的mapper映射,链接型动态代理帮助我们简化了访问某个类的步骤
参考
文章首发于个人博客,欢迎访问啊!风在哪个人博客