在手写JDK动态代理实现之前,我们先看一下我们平时是怎么用的。
举个例子,你想找对象,但是没有时间,只能委托媒婆去帮你找,代码实现如下:
// 一个统一接口
public interface Person {
// 找对象接口
void findLove();
}
// 客户类
public class Customer implements Person{
@Override
public void findLove() {
System.out.println("肤白貌美大长腿");
}
}
package jdkProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 媒婆类
public class JDKMeipo implements InvocationHandler {
// 被代理的对象,把引用保存下来.
private Object target;
public Object getInstance(Object target) throws Exception{
this.target = target;
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(this.target, args);
after();
return null;
}
private void before(){
System.out.println("媒婆出征,确认需求");
System.out.println("开始物色");
}
private void after(){
System.out.println("物色结束,开始办事");
}
}
package proxy;
import jdkProxy.JDKMeipo;
public class Test {
public static void main(String[] args) {
try{
Person obj = (Person)new JDKMeipo().getInstance(new Customer());
System.out.println(obj.getClass());
obj.findLove();
}catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
大家可以把这四个方法复制到idea中跑一下就知道了,在执行我们想要执行的findLove方法时,会去执行JDKMeipo的invoke方法,然后在里面加一些其他操作,再由塔来调用我们真正要执行的方法。在这里面我没有加过多注释,因为我们接下来会自己手写一个,一步步来捋逻辑。
public static void main(String[] args) {
try{
// 1:千里之行,始于足下,从此开始。
Person obj = (Person) new GPMeipo().getInstance(new Customer());
System.out.println(obj.getClass());
obj.findLove();
}catch (Exception e) {
e.printStackTrace();
}
}
在1中,我们用了GPMeipo类的getInstance方法传入被代理的类,返回代理类,我们看看这个类的逻辑
package proxy;
import java.lang.reflect.Method;
public class GPMeipo implements GPInvocationHandler{
// 被代理的对象,把引用保存下来
private Object target;
/**
* 2:返回代理类
* @param target 被代理的类
* @return 代理类
* @throws Exception
*/
public Object getInstance(Object target) throws Exception{
this.target = target;
Class<?> clazz = target.getClass();
return GPProxy.newProxyInstance(new GPClassLoader(), clazz.getInterfaces(),this);
}
/**
* 6:通过我们动态生成的java文件,我们所有被代理的方法,在被执行时候都会先来执行这个invoke方法
* @param proxy 动态拼接的.java代理类
* @param method 被代理的方法
* @param args 被代理的方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(this.target, args);
after();
return null;
}
private void before(){
System.out.println("媒婆出征,确认需求");
System.out.println("开始物色");
}
private void after(){
System.out.println("物色结束,开始办事");
}
}
其中GPInvocationHandler只是一个接口
package proxy;
import java.lang.reflect.Method;
public interface GPInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
在getInstance方法里面,我们又用到了GPProxy.newProxyInstance,传入了class加载器,代理类的方法,被代理类。我们先看看类加载器是什么东西:
package proxy;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class GPClassLoader extends ClassLoader{
private File classPathFile;
/**
* 3:初始化GPClassLoader,先只是将GPClassLoader的class文件加载进来
*/
public GPClassLoader() {
String classPath = GPClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = GPClassLoader.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(null != out) {
try{
out.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
很明显,我们在new GPClassLoader()时,只是对其属性classPathFile赋上了它这个类的class文件,为后面的findClass方法提供一个地址。
重点还是GPProxy.newProxyInstance,让我们看看这个类的逻辑:
package proxy;
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.nio.file.attribute.AclFileAttributeView;
import java.util.HashMap;
import java.util.Map;
public class GPProxy {
public static final String ln = "\r\n";
/**
* 4:获取代理类
* @param classLoader class加载类
* @param interfaces 代理类要被代理的方法
* @param h 调用处理类
* @return
*/
public static Object newProxyInstance(GPClassLoader classLoader, Class<?> [] interfaces, GPInvocationHandler h) {
try{
// 动态生成源代码.java文件
String src = generateSrc(interfaces);
// Java文件输出磁盘
String filePath = GPProxy.class.getResource("").getPath();
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
// 把生成的.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null,manager,null,null,null,iterable);
task.call();
manager.close();
// 把编译生成的.class文件加载到JVM中
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
f.delete();
// 返回字节码重组以后的新的代理对象,c是我们动态生成的类的构造方法,h就是外面的媒婆类
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 动态拼接.java文件
* @param interfaces 代理类要被代理的方法
* @return 返回.java文件
*/
private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package proxy;" + ln);
sb.append("import proxy.Person;" + ln);
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("GPInvocationHandler h;" + ln);
sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
sb.append("this.h = h;");
sb.append("}" + ln);
// 这里就是将Person类的所有方法都遍历了
for(Method m : interfaces[0].getMethods()) {
// 方法的参数
Class<?>[] params = m.getParameterTypes();
StringBuffer paramsNames = new StringBuffer();
StringBuffer paramValues = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for(int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramsNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if(i > 0 && i < params.length-1){
paramsNames.append(",");
paramClasses.append(",");
paramValues.append(",");
}
}
sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramsNames.toString() + ") {" + ln);
sb.append("try{" + ln);
sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
// 5:注意这里将GPInvocationHandler的invoke方法已经加上了,相当于所有执行方法都会去执行invoke了
sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues +"})",m.getReturnType()) +";" + ln);
sb.append("}catch(Error _ex) { }");
sb.append("catch(Throwable e){" + ln);
sb.append("throw new UndeclaredThrowableException(e);"+ln);
sb.append("}");
sb.append(getReturnEmptyCode(m.getReturnType()));
sb.append("}");
}
sb.append("}" + ln);
return sb.toString();
}
private static Map<Class, Class> mappings = new HashMap<Class, Class>();
static {
mappings.put(int.class, Integer.class);
}
private static String getReturnEmptyCode(Class<?> returnClass) {
if(mappings.containsKey(returnClass)){
return "return 0;";
}else if(returnClass == void.class){
return "";
}else {
return "return null;";
}
}
private static String getCaseCode(String code, Class<?> returnClass) {
if(mappings.containsKey(returnClass)){
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
}
return code;
}
private static boolean hasReturnValue(Class<?> clazz) {
return clazz != void.class;
}
private static String toLowerFirstCase(String src) {
char [] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
newProxyInstance方法通过动态拼接.java文件,在里面不去调用被代理类的代理方法,而是去调用代理类的invoke方法,注意看数字5的地方标明的注释,然后讲这个java文件编译成class加载到JVM中,直接调换掉了以前的,来了一波偷梁换柱,最后返回代理类。
JDK的动态代理的精髓就是利用传入的参数,动态拼接成我们想要的java文件,编译成class,加载到JVM,进行偷梁换柱,大家可能单纯的看不会太理解,建议大家如果想吃透可以把代码复制下来,按照我的数字注释顺序,自己debug一下,看看参数,应该就会理解JDK动态代理的乾坤了。
大家一定注意动态拼接java文件时,拼接的包的位置,可以参考我的包路径,如图:
本文参考:《Spring5核心原理与30个类手写实战》,自己加了一些注释帮助理解,如有侵权,联系秒删。
有疑问可以在评论区交流,作者看见会回复。
如需转载,请一定声明原处。