上篇分析的是JDK动态代理实现原理,这篇是一个自实现的动态代理案例,这一篇我们自定义代理Proxy,代理业务需要实现的Handler接口,以及类加载器ClassLoader;最终我们以自己写的代码去生成代理类的代码,再用代理类的代码去代理执行我们的业务代码,完成一套标准的动态代理;
本博客关于Java动态代理相关内容直达链接:
上篇分析的是JDK动态代理实现原理,这个下篇是一个自实现的动态代理案例,这一篇我们自定义代理Proxy,代理业务需要实现的Handler接口,以及类加载器ClassLoader;最终我们以自己写的代码去生成代理类的代码,再用代理类的代码去代理执行我们的业务代码,完成一套标准的动态代理流程;
首先我们分析实现代理需要什么,下面是Proxy生成代理类的newProxyInstance()方法:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
一个代理类Proxy,一个ClassLoader,一个业务类实现的接口数组,一个InvocationHandler;
把这里的步骤拆分一下就是下面的两步:
1. Proxy其实就是根据传递给它的参数Class<?>[] interfaces去生成代理类$Proxy0;
2. 用ClassLoader loader去加载生成的这个代理类$Proxy0,然后返回$Proxy0实例的引用;
现在一步步来做,在Proxy中,我们大致可以细分为4步:
1. 动态生成代理类的源代码.java文件,并写入到磁盘;
2. 把生成的.java文件编译成.class文件;
3. 把编译的.class文件加载到JVM;
4. 返回动态生成的代理对象;
那么GuituDynamicProxy类完成后的代码如下(相当于Proxy):
package com.guitu18.study.proxy.guitu;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* 自实现动态代理
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 15:17
*/
public class GuituDynamicProxy {
/**
* 换行符
*/
private static final String LN = "\r\n";
/**
* 生成的代理类的名称,这里为了方便就不生成了,直接字符串简单定义一下
*/
private static final String SRC_NAME = "$GuituProxy0";
/**
* 生成的代理类的包名,同样为了测试方便直接定义成字符串
*/
private static final String PACKAGE_NAME = "com.guitu18.study.proxy.guitu";
/**
* 生成并返回一个代理对象
*
* @param guituClassLoader 自实现的类加载器
* @param interfaces 被代理类所实现的所有接口
* @param guituInvocationHandler 一个{@link GuituInvocationHandler}接口的实现
* 我们代理类对其代理的对象增强的代码写在对该接口的实现中
* {@link GuituProxy#invoke(Object, Method, Object[])}
* @return 返回生成的代理对象
*/
public static Object newProxyInstance(GuituClassLoader guituClassLoader,
Class<?>[] interfaces,
GuituInvocationHandler guituInvocationHandler) {
try {
// 1.动态生成源代码.java文件并写入到磁盘
File file = generateSrcToFile(interfaces);
// 2.把生成的.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manage.getJavaFileObjects(file);
JavaCompiler.CompilationTask task =
compiler.getTask(null, manage, null, null, null, iterable);
task.call();
manage.close();
// 3.把编译的.class文件加载到JVM
Class proxyClass = guituClassLoader.findClass(SRC_NAME);
Constructor constructor = proxyClass.getConstructor(GuituInvocationHandler.class);
// 4.返回动态生成的代理对象
return constructor.newInstance(guituInvocationHandler);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 这里仅为理解原理和学习,代码生成简单有效即可
*
* @param interfaces 被代理类所实现的所有接口
* @return 返回生成的源代码的File对象
*/
private static File generateSrcToFile(Class<?>[] interfaces) {
try {
StringBuffer sb = new StringBuffer();
sb.append("package " + PACKAGE_NAME + ";" + LN);
sb.append("import java.lang.reflect.Method;" + LN);
/**
* 实现所有接口
*/
StringBuffer interfaceStr = new StringBuffer();
for (int i = 0; i < interfaces.length; i++) {
interfaceStr.append(interfaces[i].getName());
if (interfaces.length > 1 && i < interfaces.length - 2) {
interfaceStr.append(",");
}
}
sb.append("public class " + SRC_NAME + " implements " + interfaceStr.toString() + " {" + LN);
sb.append(" GuituInvocationHandler guituInvocationHandler;" + LN);
sb.append(" public " + SRC_NAME + "(GuituInvocationHandler guituInvocationHandler) { " + LN);
sb.append(" this.guituInvocationHandler = guituInvocationHandler;" + LN);
sb.append(" }" + LN);
/**
* 实现所有接口的所有方法
*/
for (Class<?> anInterface : interfaces) {
for (Method method : anInterface.getMethods()) {
// 方法形参数组
Parameter[] parameters = method.getParameters();
// 方法方法形参,类型 名称 字符串
StringBuffer paramStr = new StringBuffer();
// 方法形参类型字符串
StringBuffer paramTypeStr = new StringBuffer();
// 方法形参名称字符串
StringBuffer paramNameStr = new StringBuffer();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
// 拼接方法形参,类型 名称
paramStr.append(parameter.getType().getName() + " " + parameter.getName());
// 拼接方法形参类型,供反射调用
paramTypeStr.append(parameter.getType().getName()).append(".class");
// 拼接方法形参名称,供反射调用
paramNameStr.append(parameter.getName());
if (parameters.length > 1 && i < parameters.length - 2) {
sb.append(", ");
paramTypeStr.append(",");
paramNameStr.append(", ");
}
}
// 生成方法
String returnTypeName = method.getReturnType().getName();
sb.append(" public " + returnTypeName + " " + method.getName() + "(" + paramStr.toString() + ") {" + LN);
sb.append(" try{" + LN);
sb.append(" Method method = " + interfaces[0].getName() +
".class.getMethod(\"" + method.getName() + "\",new Class[]{" + paramTypeStr.toString() + "});" + LN);
// 判断方法是否有返回值
if (!"void".equals(returnTypeName)) {
sb.append(" " + returnTypeName +
" invoke = (" + returnTypeName + ")this.guituInvocationHandler.invoke(this, method, new Object[]{"
+ paramNameStr.toString() + "});" + LN);
sb.append(" return invoke;" + LN);
} else {
sb.append(" this.guituInvocationHandler.invoke(this, method, null);" + LN);
}
sb.append(" }catch(Throwable e){" + LN);
sb.append(" e.printStackTrace();" + LN);
sb.append(" }" + LN);
if (!"void".equals(method.getReturnType().getName())) {
sb.append(" return null;" + LN);
}
sb.append(" }" + LN);
}
}
sb.append("}" + LN);
// 将生成的字节码写入到磁盘文件
String path = GuituDynamicProxy.class.getResource("").getPath();
System.out.println(path);
File file = new File(path + SRC_NAME + ".java");
FileWriter fw = new FileWriter(file);
fw.write(sb.toString());
fw.flush();
fw.close();
return file;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
在上面的步骤中,我们先生成了代理类,然后使用JavaCompiler将其编译成class文件,接着用类加载器将class文件加载到内存,这里用到了类加载器ClassLoader;
我们自定义类加载器需要继承ClassLoader类,重写findClass(String name)方法,代码如下(相当于ClassLoader):
package com.guitu18.study.proxy.guitu;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 自实现的类加载器
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 15:51
*/
public class GuituClassLoader extends ClassLoader {
private File classPathFile;
/**
* 构造方法,创建生成的文件
*/
public GuituClassLoader() {
this.classPathFile = new File(GuituClassLoader.class.getResource("").getPath());
}
/**
* 获取字节码对象
*
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = GuituClassLoader.class.getPackage().getName() + "." + name;
if (classPathFile != null) {
File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
}
return defineClass(className, out.toByteArray(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
接着就是接口GuituInvocationHandler如下(相当于InvocationHandler):
package com.guitu18.study.proxy.guitu;
import java.lang.reflect.Method;
/**
* 代理类需要实现该接口,重写invoke方法
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 15:18
*/
public interface GuituInvocationHandler {
/**
* 代理类对业务增强时需要实现该方法,动态代理最终调用的是该方法的实现
*
* @param proxy 生成的代理类
* @param method 代理的方法
* @param args 代理的方法形参
* @return 返回代理执行后的结果
*/
Object invoke(Object proxy, Method method, Object[] args);
}
有了这三样东西,我们就可以使用它们编写我们的动态代理了,跟上篇使用JDK动态代理时一样的使用方式,只不过使用的全都是我们自己写的代码了:
package com.guitu18.study.proxy.guitu;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 代理类
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 16:01
*/
public class GuituProxy implements GuituInvocationHandler {
private Object target;
/**
* 获取代理对象
*
* @param object 被代理对象
* @return 返回代理类
*/
public Object getInstance(Object object) {
try {
this.target = object;
return GuituDynamicProxy.newProxyInstance(new GuituClassLoader(), object.getClass().getInterfaces(), this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 代理执行前后的业务逻辑,该方法由生成的代理类调用
*
* @param proxy 代理对象
* @param method 代理执行的方法
* @param args 代理执行的方法形参
* @return 返回代理方法执行的结果,返回的Object对象由生成的代理类根据代理方法的返回值进行强转
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
System.out.println("Guitu动态代理,代理执行前...");
Object invoke = null;
invoke = method.invoke(this.target, args);
System.out.println("执行后...");
return invoke;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
至此一套自实现的JDK动态代理就完成了,这中间很多过程直接使用的简化操作,JDK动态代理的源码比这个要复杂的多,此篇主要为了强化理解JDK动态代理思想;
具体的步骤分析和流程说明我在上面代码的注释中已经写的非常详细了,这里就不做过多说明了;这里面稍微复杂一点的就是动态的生成代理类源代码这个步骤,这里需要非常细心,毕竟使用字符串拼接代码,丝毫不能出错;其他的流程只要明白了原理其实很容易;
下面简单贴上测试代码:
package com.guitu18.study.proxy.guitu;
import com.guitu18.study.proxy.Persion;
import com.guitu18.study.proxy.ZhangKuan;
/**
* 自实现动态代理测试类
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 16:13
*/
public class GuituProxyTest {
public static void main(String[] args) {
Persion instance = (Persion) new GuituProxy().getInstance(new ZhangKuan());
String love = instance.findLove("肤白貌美大长腿");
System.out.println(love);
instance.findWord();
}
}
测试中的业务接口Persion和业务类ZhangKuan这里就不贴了,和上篇的代码一模一样;
执行结果如下:
Guitu动态代理,代理执行前...
肤白貌美大长腿
执行后...
叶青我爱你
Guitu动态代理,代理执行前...
我想找月薪15-25k的工作
执行后...
JDK动态代理深入分析到这里就结束了,Java学习还有很长的路要走,2019继续努力,再接再厉!