JDK的动态代理相信大家都使用过,但是大家知道JDK动态代理实现的过程吗?有学习过源码吗?
今天,参照JDK动态代理的源码,仿照写了一个我自己的动态代理(乞丐版,haaaa…),相信看完此篇文章,你对JDK动态代理会有个新的理解。
-----------------------分割线------------------------
首先说明,如果你是对JDK动态代理不熟的同学,请先去复习一下;如果你是想深入学习JDK动态代理源码的大牛,该文章也不适合你哦。
首先,JDK动态代理中最重要的两个类是:
InvocationHandler接口:我们需要写一个类,实现这个接口,重写 public Object invoke(Object proxy, Method method, Object[] args)
,生成的代理类会调用这个实现类的invoke方法,我们可以在invoke方法里执行目标方法。
Proxy类:调用Proxy类的静态方法public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
,可以获取到我们想要的代理对象。
今天的手写,我们也会写一个自己的InvocationHandler接口和Proxy类,希望在API上,做到高仿。
整体手写思路:根据传进来的实现接口,我们构建一个JAVA类的代码字符串,并将字符串写入一个临时的文件中,动态编译这个文件,并动态加载到JVM中来,最后new一个对象返回给用户。
当然,正版的JDK动态代理不需要我这么麻烦,能跳过前面的步骤,直接生成calss文件的byte[ ],然后将其加载到JVM中来,减少了非常多的IO,性能上会高不少。
下面是业务代码,我需要对UserServiceImpl 进行代理
package cn.java.test;
/**
* @Classname UserServiceImpl
* @Description: 这是业务接口的实现类,我们需要对这个类进行代理
* @date 2020/3/25 8:58
*/
public class UserServiceImpl implements UserService {
@Override
public String query() {
System.out.println("假装查询了数据");
return "这是数据";
}
@Override
public void add(String str) {
System.out.println("假装新增了:" + str);
}
}
UserServiceImpl 实现的接口,
package cn.java.test;
/**
* @Classname UserService
* @Description: 这是我们的业务接口
* @date 2020/3/25 8:57
*/
public interface UserService {
String query();
void add(String str);
}
然后仿照正版动态代理,写一个MyInvocationHandler和实现此接口的业务类UserServiceHandler
package cn.java.proxy;
import java.lang.reflect.Method;
/**
* @author mwl
* @Classname MyInvocationHandler
* @Description:
* @date 2020/3/25 8:54
*/
public interface MyInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
package cn.java.test;
import cn.java.proxy.MyInvocationHandler;
import java.lang.reflect.Method;
/**
* @author mwl
* @Classname UserServiceHandler
* @Description: 被代理后的方法都会调用到此类的invoke方法里,在invoke里可以执行目标方法
* @date 2020/3/25 9:02
*/
public class UserServiceHandler implements MyInvocationHandler {
private UserService target;
public UserServiceHandler(UserService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("--->通过代理类执行了此方法UserServiceHandler.invoke()<---");
return method.invoke(target, args);
}
}
为了便于我们构建动态的java代码字符串,先手写一个代理类出来,方便参考:
package cn.java.test;
import cn.java.proxy.MyInvocationHandler;
import cn.java.proxy.MyProxy;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
/**
* 这个是动态生成的代理类示例,可以作为参考,JDK动态代理生成的代理类也会默认继承Proxy,
* 将InvocationHandler的实现类保存到父类里。这也是为什么JDK动态代理不能基于继承的原因(JAVA是单继承的嘛)。
*/
public class $Proxy extends MyProxy implements UserService {
private static Method m0;//query
private static Method m1;//add
public $Proxy(MyInvocationHandler h) {
super(h);
}
static {
try {
m0 = Class.forName("cn.java.test.UserService").getMethod("query", (Class[]) null);
m1 = Class.forName("cn.java.test.UserService").getMethod("add", Class.forName("java.lang.String"));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public String query() {
try {
return (String) super.h.invoke(this, m0, (Object[]) null);
} catch (Throwable e) {
//不管业务接口上有没有抛出异常,这里统一捕捉,将Throwable包装为运行时异常抛出
throw new UndeclaredThrowableException(e);
}
}
public void add(String str) {
try {
h.invoke(this, m1, new Object[]{str});
} catch (Throwable e) {
//不管业务接口上有没有抛出异常,这里统一捕捉,将Throwable包装为运行时异常抛出
throw new UndeclaredThrowableException(e);
}
}
}
接下来要做的事就简单了,我们依据上面的参考代码,动态的构建出java代码的字符串就好了。创建一个MyProxy类,将生成代码的逻辑放在newProxyInstance方法里
package cn.java.proxy;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
/**
* @author mwl
* @Classname MyProxy
* @Description:
* @date 2020/3/25 8:54
*/
public class MyProxy {
//生成的代理对象的所在包名
private static final String BASE_PACKAGE = "com.sun";
//生成的临时文件的所在地址
private static final String BASE_DIRECTORY = "D:\\\\";
//记录类的名称,防止重名
private static Integer ClassNum = 0;
protected MyInvocationHandler h;
protected MyProxy(MyInvocationHandler h) {
this.h = h;
}
/**
* 此方法原理是根据传入的参数先构建出java代码的文本,再将其写入磁盘,再动态编译,然后加载进JVM,
* new 个代理对象出来返回。
* 真实的jdk动态代理会跳过前面的步骤,直接生成class文件的byte[],直接加载进JVM,减少了磁盘IO的操作
*
* @param loader
* @param interfaces
* @param h
* @return
* @throws IllegalArgumentException
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
MyInvocationHandler h)
throws IllegalArgumentException {
String className = "$Proxy" + ClassNum++;
//动态生成代理的类的java代码字符串
String javaContext = getJavaContext(className, interfaces);
//将字符串写入临时的java文件中;
File file = writeToFile(className, javaContext);
//将java文件编译成class文件
compilerJavaFile(file);
//将动态生成的代理类加载进JVM
Class aClass = loadJavaFile(loader, className);
//创建代理对象
return newInstance(aClass, h);
}
/**
* 动态生成代理的类的java代码字符串
*
* @param className
* @param interfaces
* @return
*/
private static String getJavaContext(String className, Class<?>[] interfaces) {
//生成字符串的java代码
//写出到文件
String tab = "\t";
String line = "\n";
//构建类头部信息
String s0 = "package " + BASE_PACKAGE + ";" + line;
String s1 = "import cn.java.proxy.MyInvocationHandler;" + line +
"import cn.java.proxy.MyProxy;" + line +
"import java.lang.reflect.Method;" + line +
"import java.lang.reflect.UndeclaredThrowableException;" + line;
//构建类定义
String s2 = "public class " + className + " extends MyProxy";
if (interfaces.length > 0) {
s2 += " implements ";
}
for (int i = 0; i < interfaces.length; i++) {
Class<?> anInterface = interfaces[i];
s2 += anInterface.getTypeName();
if (i != interfaces.length - 1) {
s2 += ",";
}
}
s2 += "{";
//构建成员变量--代理方法
String s3 = "";
Integer mNum = 0;
List<MethodInfo> methodInfos = new ArrayList<>();
for (Class<?> anInterface : interfaces) {
Method[] methods = anInterface.getMethods();
for (int i = 0; i < methods.length; i++) {
String methodName = "m" + mNum++;
s3 += tab + "private static Method " + methodName + ";" + line;
methodInfos.add(new MethodInfo(methodName, methods[i], anInterface.getTypeName()));
}
}
//构建成员变量-InvocationHandler
String s4 = tab + "public " + className + "(MyInvocationHandler h) {" + line +
tab + tab + "super(h);" + line + tab + "}";
//构建静态代码块
String s5 = line + tab + "static {" + line +
tab + tab + "try {" + line;
for (MethodInfo value : methodInfos) {
s5 += tab + tab + tab + value.getMethodName() + " = Class.forName(\"" + value.getInterfaceName() + "\").getMethod(\"" + value.getMethod().getName() + "\", ";
Class<?>[] parameterTypes = value.getMethod().getParameterTypes();
if (parameterTypes == null || parameterTypes.length == 0) {
s5 += "(Class[]) null";
} else {
for (int i = 0; i < parameterTypes.length; i++) {
s5 += "Class.forName(\"" + parameterTypes[i].getTypeName() + "\")";
if (i != parameterTypes.length - 1) {
s5 += ",";
}
}
}
s5 += ");" + line;
}
s5 += tab + tab + "} catch (NoSuchMethodException e) {" + line +
tab + tab + "} catch (ClassNotFoundException e) {" + line +
tab + tab + "}" + line +
tab + "}";
//构建代理方法方法体
String s6 = line;
for (MethodInfo methodInfo : methodInfos) {
Method method = methodInfo.getMethod();
s6 += tab + "public " + method.getReturnType().getTypeName() + " " + method.getName() + "(";
Class<?>[] parameterTypes = method.getParameterTypes();
String scTemp = "new Object[]{}";
if (parameterTypes != null && parameterTypes.length > 0) {
for (int i = 0; i < parameterTypes.length; i++) {
s6 += parameterTypes[i].getTypeName() + " var" + i;
scTemp = scTemp.replaceAll("}", "var" + i + "}");
if (i != parameterTypes.length - 1) {
scTemp = scTemp.replaceAll("}", ",}");
s6 += ",";
}
}
} else {
scTemp = "null";
}
s6 += "){" + line +
tab + tab + "try {" + line +
tab + tab + tab + (method.getReturnType().getName().equals("void") ? "" : "return (" + method.getReturnType().getTypeName() + ")") + " super.h.invoke(this, " + methodInfo.getMethodName() + "," + scTemp + ");" + line +
tab + tab + "} catch (Throwable e) {" + line +
tab + tab + tab + "throw new UndeclaredThrowableException(e);" + line +
tab + tab + "}" + line +
tab + "}" + line;
}
//构建结尾符号
String s7 = "}";
//拼接类文本
return s0 + line + s1 + line + s2 + line + s3 + line + s4 + line + s5 + line + s6 + line + s7;
}
/**
* 将字符串写入临时的java文件中
*
* @param className
* @param javaContext
* @return
*/
private static File writeToFile(String className, String javaContext) {
String filePath = BASE_DIRECTORY + BASE_PACKAGE.replace(".", "\\") + "\\" + className + ".java";
File file = new File(filePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
try (FileWriter fileWriter = new FileWriter(file)) {
fileWriter.write(javaContext);
fileWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
/**
* 将java文件编译成class文件
*
* @param file
*/
private static void compilerJavaFile(File file) {
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager sjfm = jc.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units = sjfm.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = jc.getTask(null, sjfm, null, null, null, units);
task.call();
try {
sjfm.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将动态生成的代理类加载进JVM
*
* @param loader
* @param className
*/
private static Class loadJavaFile(ClassLoader loader, String className) {
try {
loader = new URLClassLoader(new URL[]{new URL("file:" + BASE_DIRECTORY)});
Class<?> aClass = loader.loadClass(BASE_PACKAGE + "." + className);
return aClass;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 创建代理对象
*
* @param aClass
* @return
*/
private static Object newInstance(Class aClass, MyInvocationHandler h) {
try {
Constructor constructor = aClass.getConstructor(MyInvocationHandler.class);
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 用来封装代理类信息的内部类
*/
private static class MethodInfo {
private String methodName;
private Method method;
private String interfaceName;
public MethodInfo(String methodName, Method method, String interfaceName) {
this.methodName = methodName;
this.method = method;
this.interfaceName = interfaceName;
}
public String getMethodName() {
return methodName;
}
public Method getMethod() {
return method;
}
public String getInterfaceName() {
return interfaceName;
}
}
}
再来写一个测试类:
package cn.java.test;
import cn.java.proxy.MyProxy;
/**
* @author mwl
* @Classname TestWindow
* @Description:
* @date 2020/3/25 8:57
*/
public class TestWindow {
public static void main(String[] args) {
//获取代理类
UserService proxy = (UserService) MyProxy.newProxyInstance(
TestWindow.class.getClassLoader(),
new Class[]{UserService.class},
new UserServiceHandler(new UserServiceImpl()));
//测试
String query = proxy.query();
System.out.println("----------------分割线---------------");
proxy.add(query);
}
}
控制台输出结果:
--->通过代理类执行了此方法UserServiceHandler.invoke()<---
假装查询了数据
----------------分割线---------------
--->通过代理类执行了此方法UserServiceHandler.invoke()<---
假装新增了:这是数据
完美~实现了和正版动态代理一样的效果!当然,正版还会有多很多安全验证之类的代码,这里就给简化掉啦,最后强调一遍,JDK动态代理的主要逻辑和手写版不一样的地方在于,他会直接生成class文件的二进制流,直接加载到JVM中,中间涉及到很多字节码技术和native方法,有兴趣的同学可以去看看正版的源码。