模拟实现Java模拟实现Java动态代理底层逻辑,以期更深入理解Java动态代理。Java动态代理能够实现不编写代理类,也能够获得代理类及其实例。下面以实现一个接口TestService为例,逐步深入Java动态代理底层逻辑。
我们都知道java代码要运行需要大略需要进行这样的工作:
这样就实现了一次编译,到处运行。
普通的代理,是通过编写Java源码的方式来实现代理;但是动态代理,则是借助反射,动态编辑.class字节码实现代理,并没有Java源码的产生。由此可见,动态代理底层逻辑的目标就是动态编辑.class字节码:
普通代理和动态代理UML图:
这里借助Javassist,实现.class的编辑。简单介绍Javassist。Javassist是一个用于编写字节码的库,能够做到在JVM加载一个类的时候修改该类,还能做到在运行期间定义一个新类。Javassist提供两个等级的API:源码(source)级和字节码(bytecode)级:源码级API,是根据Java语言设计,可以在没有字节码规范的知识前提下,进行.class文件的编辑;字节码级API就是直接编辑字节码文件。Javassist官方网站:http://www.javassist.org/。
下面使用eclipse+jdk1.8,尝试实现动态代理底层逻辑。
普通的实现
先看普通的实现,也就是编写Java源码的实现。为了方便,将所有代码写在一个.java文件中。
public class ProxyFactory {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(); // 外部类对象
ProxyFactory.TestServiceImpl testServiceImpl = proxyFactory.new TestServiceImpl(); // 使用外部类对象创建内部类对象
testServiceImpl.sayHello("张三");
}
// 普通实现
class TestServiceImpl implements TestService {
@Override
public void sayHello(String name) {
// 自己编写的代理逻辑。接口/被代理类不关系代理逻辑的实现,由我们自己编写
System.out.println("hello:" + name);
}
}
// TestService接口
public interface TestService {
void sayHello(String name);
}
}
执行结果:
hello:张三
动态代理1.0
以上可见,普通的实现,需要自己编写源码。接下来借助Javassist,看看写死了实现TestService接口的动态代理。为了方便查看代码,所有的异常均通过throw抛出,实际工作中不建议这么做。
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
public class ProxyFactory {
public static void main(String[] args)
throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
TestService proxy = createProxy(); // 根据createProxy()获取代理类对象
proxy.sayHello("张三"); // 运行代理类对象的方法
}
/*
* javasist工具类:生成TestService的代理类对象
* 1.创建一个代理类
* 2.创建一个方法
* 3.实例化该类对象
*/
public static TestService createProxy()
throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException {
ClassPool classPool = new ClassPool(); // 类池
classPool.appendSystemPath(); // 加载当前类的ClassLoader
// 1.
CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建代理类(此处写死代理了名为TestServiceImpl)
class1.addInterface(classPool.get(TestService.class.getName())); // 将接口加入代理类
// 2.
// 根据信息生成代理(此处为重写)的方法sayHello()
// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
// "{System.out.println(\"hello:\"+1);}"就是{System.out.println("hello:"+name);}
CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
new CtClass[] { classPool.get(String.class.getName()) }, null, "{System.out.println(\"hello:\"+1);}",
class1);
class1.addMethod(sayHello); // 将重写的方法加入代理类中
// 3.
Class aClass = classPool.toClass(class1); // 将CtClass转换为Class
return (TestService) aClass.newInstance(); // 实例化代理类对象并返回(此处只能是TestService)
}
// 接口
public interface TestService {
void sayHello(String name);
}
}
执行结果不变:
hello:张三
1.0的问题
从代码可以看出,这样写出来的代码,不仅只能实现TestService接口的代理,而且代理逻辑也只能在 createProxy() 方法中写,还是写在一个被调用方法的参数列表内,不可改变且十分容易出错。那么,为了实现能够代理任意的类,我们是否可以将 createProxy() 方法的返回类型,由TestService改为为泛型,将被代理类作为方法参数传递?为了能够根据需要改变代理逻辑,能否也将代理逻辑作为方法参数传递?
动态代理2.0
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
public class ProxyFactory1 {
public static void main(String[] args)
throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
// TestService proxy = createProxy(TestService.class, "{System.out.println(\"hello:\"+1);}"); // 利用反射获取接口的信息,下同
// proxy.sayHello("张三");
TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+1);}");
proxy2.sayHello("李四");
}
/*
* javasist工具类:生成代理类对象。方法参数依次为:被代理类/接口,代理逻辑
* 1.创建一个代理类
* 2.创建一个方法
* 3.实例化该类对象
*/
public static <T> T createProxy(Class<T> classInterface, String src)
throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException {
ClassPool classPool = new ClassPool(); // 类池
classPool.appendSystemPath(); // 加载当前类的ClassLoader
// 1.
CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建一个代理类
class1.addInterface(classPool.get(classInterface.getName())); // 添加被代理接口/类的方法到代理类中
// 2.
// 根据信息生成代理(此处为重写)的方法sayHello()
// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);
class1.addMethod(sayHello); // 添加方法到代理类中
// 3.
Class aClass = classPool.toClass(class1); // 将代理类由CtClass转换为Class
return (T) aClass.newInstance(); // 实例化代理类并返回对象
}
// 接口。下同
public interface TestService {
void sayHello(String name);
}
public interface TestService2 {
void sayHello(String name);
void sayHello2(String name);
}
}
2.0存在的问题
这样就解决1.0的两个问题了。但还有别的问题:
1.代理类一次只能执行一个,否则会编译不通过。因为我们在代码中写死了代理类的类名,会导致代理类名称重复:
CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建一个代理类,此处代码写死了代
// 理类的名字为TestServiceImpl
hello:张三
Exception in thread "main" javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of sun/misc/LauncherAppClassLoader): attempted duplicate class definition for name: "TestServiceImpl"
at javassist.ClassPool.toClass(ClassPool.java:1170)
at javassist.ClassPool.toClass(ClassPool.java:1113)
at javassist.ClassPool.toClass(ClassPool.java:1071)
at lsb.ProxyFactory1.createProxy(ProxyFactory1.java:46)
at lsb.ProxyFactory1.main(ProxyFactory1.java:17)
Caused by: java.lang.LinkageError: loader (instance of sun/misc/LauncherAppClassLoader): attempted duplicate class definition for name: "TestServiceImpl"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at javassist.ClassPool.toClass2(ClassPool.java:1183)
at javassist.ClassPool.toClass(ClassPool.java:1164)
... 4 more
2.代理类中的方法名也是写死为“sayHello”:
// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);
这就会导致我们无法让代理类重写 TestService2() 接口中除了 sayHello() 以外的方法,因为无法找到 sayHello() 以外的方法:
TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+$1);}");
proxy2.sayHello2("李四"); // 试图重写TestService2的sayHello2(...)方法
Exception in thread "main" java.lang.AbstractMethodError: TestServiceImpl.sayHello2(Ljava/lang/String;)V
at lsb.ProxyFactory1.main(ProxyFactory1.java:18)
3.代理类重写方法的参数列表也被写死了,只有一个 String name:
// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);
其次,由于Java的多态,若更改接口方法的参数列表,sanHello() 方法就是另外一个方法了,此时再运行,将会产生同样的错误:
TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+$1);}");
proxy2.sayHello2("李四", 1); // 试图重写TestService2的sayHello(String name, int id)
public interface TestService2 {
void sayHello(String name, int id); // 增加参数int id
void sayHello2(String name);
}
Exception in thread "main" java.lang.AbstractMethodError: TestServiceImpl.sayHello2(Ljava/lang/String;)V
at lsb.ProxyFactory1.main(ProxyFactory1.java:18)
4.代理逻辑的编码和1.0并无区别,效率依然底下且易读性差。
动态代理3.0
为了解决上面的4个问题,将2.0更改为3.0:
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
public class ProxyFactory3 {
static int count = 0; // 作为生成的代理类后缀,避免重名
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NotFoundException,
CannotCompileException, IllegalArgumentException, NoSuchFieldException, SecurityException {
// 匿名类实现 InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService 接口
TestService proxy1 = createProxy(TestService.class, new InvocationHandler() {
@Override
public Object invoke(String methodName, Object[] args) {
// 需要程序员自己写的代理逻辑
System.out.println("hello:" + args[0]);
return null;
}
});
proxy1.sayHello("张三");
// 匿名类实现 InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService2 接口
TestService2 proxy2 = createProxy(TestService2.class, new InvocationHandler() {
@Override
public Object invoke(String methodName, Object[] args) {
System.out.println("hello:" + args[0]);
return null;
}
});
proxy2.sayHello("李四");
proxy2.sayHello2("王五");
proxy2.sayHello3("赵六", 1);
// 非匿名类实现InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService 接口
ProxyFactory3 proxyFactory3 = new ProxyFactory3();
Handler handler = proxyFactory3.new Handler();
TestService proxy3 = createProxy(TestService.class, handler);
proxy3.sayHello("孙七");
}
/*
* 创建代理对象:方法参数依次为:代理的方法/接口、handler
* 1.创建一个代理类
* 2.添加一个InvocationHandler类型的属性,用以保存增强代码
* 3.创建接口下的所有方法实现
* 4.实例化该代理类对象
*/
public static <T> T createProxy(Class<T> classInterface, InvocationHandler handler)
throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException,
IllegalArgumentException, NoSuchFieldException, SecurityException {
ClassPool classPool = new ClassPool(); // 类池
classPool.appendSystemPath(); // 加载当前类的ClassLoader
// src分为void和return两种。该字符串为代理类最后执行的invoke()方法代码
String src = "return (r)this.handler.invoke(\"%s\",args);";
String voidSrc = "this.handler.invoke(\"%s\",args);";
// 1.创建一个代理类
CtClass impl = classPool.makeClass("proxy" + count++); // 代理类impl,加count后缀防止类重名
impl.addInterface(classPool.get(classInterface.getName())); // 添加被代理接口/类的方法到代理类中
// 2.添加一个InvocationHandler类型的属性,用以保存增强代码
// 编译"public lsb.ProxyFactory3.InvocationHandler
// handler=null;",代码这里用了全权限定名
// 这样就可以创建一个InvocationHandler接口的空对象handler,并将其加入代理类impl
// 相当于代理类中声明了一个成员变量 handler:public InvocationHandler handler = null;
// 这样代理类就可以存储该方法传递过来的参数 handler(createProxy(..., InvocationHandler
// handler))
CtField field = CtField.make("public lsb.ProxyFactory3.InvocationHandler handler=null;", impl);
impl.addField(field); // 将上一句代码创建的属性(变量)加入代理类
// 3.创建接口下的所有方法实现
// 循环被代理接口/类的方法,分别获取方法信息,添加到代理类中
for (Method method : classInterface.getMethods()) {
CtClass returnType = classPool.get(method.getReturnType().getName()); // 方法类型
String methodName = method.getName(); // 方法名
CtClass[] parameters = toCtClass(classPool, method.getParameterTypes()); // 方法参数
CtClass[] errors = toCtClass(classPool, method.getExceptionTypes()); // 方法的异常(try-catch)
// 判断代理逻辑是否需要return
String srcImpl = "";
if (method.getReturnType().equals(Void.class)) {
srcImpl = voidSrc;
} else {
srcImpl = src;
}
// 根据信息生成对应的方法
CtMethod newMethod = CtNewMethod.make(returnType, methodName, parameters, errors, srcImpl, impl);
impl.addMethod(newMethod); // 将方法添加到代理类中
}
// 4.实例化该代理类对象
Class clazz = classPool.toClass(impl); // 将CtClass转换为Class。将代理类加载到当前ClassLoader中
Object object = clazz.newInstance(); // 实例化
clazz.getField("handler").set(object, handler); // 将handler赋值到代理类中
return (T) object; // 返回最终获得的代理类对象
}
// 接口InvocationHandler
public interface InvocationHandler {
// invoke(...)方法,用来接受被代理方法的名称及参数列表
// 实现该方法时,重写的代码即是代理逻辑。所以该方法用来调用代理之后的方法
// 例子见下个方法
Object invoke(String methodName, Object args[]);
}
// InvocationHandler接口实现例子(正常实现,在main方法中的实现为匿名类方式)
public class Handler implements InvocationHandler {
@Override
public Object invoke(String methodName, Object[] args) {
// 自己写的增强代码,InvocationHandler接口不关心增强代码是什么
System.out.println(args[0] + "执行了增强代码!");
return null;
}
}
// 工具方法:将Class[]转换为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(e);
}
}).collect(Collectors.toList()).toArray(new CtClass[0]);
}
// 被代理接口。下同
public interface TestService {
void sayHello(String name);
}
public interface TestService2 {
String sayHello(String name);
void sayHello2(String name);
void sayHello3(String name, int id);
}
}
运行结果:
hello:张三
hello:李四
hello:王五
hello:赵六
孙七执行了增强代码!
关于匿名类的直接理解:
可以看到,上面的代码中,在方法 createProxy(Class
// 接口InvocationHandler
public interface InvocationHandler {
Object invoke(String methodName, Object args[]);
}
TestService proxy = createProxy(TestService.class,
public class handler implements InvocationHandler {
@Override
public Object invoke(String methodName, Object[] args) {
// 自己写的增强代码,InvocationHandler接口不关心增强代码是什么
System.out.println("执行了增强代码!");
return null;
}
}
);
proxy.sayHello("张三");
当然实际上不能这样说,只不过简单粗暴地如此理解,可以比较快地得到一个感性直观的理解,从而避免在不必要的方面纠结,而耽误了对动态代理的理解。后面会针对内部类、匿名类等专门写一篇文章。
附虚拟代理类简单的类UML关系图: