手写JDK动态代理

环境:OpenJDK(Zulu 8.58.0.13-CA-macos-aarch64)
需求:我们常用AOP做各种切面业务,AOP的实现依赖于JDK动态代理和cglib,关于JDK动态代理的实现原理,我们可以自己做个简单实现来理解。

一、准备工作

首先提供出来简单的接口HelloService,如下,

package com.szh.proxy.service;

public interface HelloService {
    
    

	public void say();
	public String say(String word, String mode);
}

及其实现类HelloServiceImpl,如下,

package com.szh.proxy.service.impl;

import com.szh.proxy.service.HelloService;

public class HelloServiceImpl implements HelloService {
    
    

	@Override
	public void say() {
    
    
		System.out.println("hello ");
	}

	@Override
	public String say(String word, String mode) {
    
    
		String result = "hello2 " + word + " " + mode;
		System.out.println(result);
		return result;
	}
}

二、静态代理的简单实现

代理模式的意义无非就是,通过代理对象代替目标对象去做事,在此过程中可以对目标对象要做的事情进行业务增强。
这里通过实现接口HelloService,并在成员变量中包含HelloService类型的目标对象,即可简单代理,实现业务增强(在业务方法前后分别打印一些日志)。如下,是一个日志增强的代理类LogStaticProxy

package com.szh.proxy.jdkStatic;

import com.szh.proxy.service.HelloService;

/**
 * 静态代理
 */
public class LogStaticProxy implements HelloService {
    
    

    private HelloService targetObject;

    public LogStaticProxy(HelloService targetObject) {
    
    
        this.targetObject = targetObject;
    }

    @Override
    public void say() {
    
    
        System.out.println("我是 JDK 静态代理");
        System.out.println("准备");
        targetObject.say();
        System.out.println("完成");
    }

    @Override
    public String say(String word, String mode) {
    
    
        System.out.println("我是 JDK 静态代理");
        System.out.println("准备");
        String result = targetObject.say(word, mode);
        System.out.println("完成");
        return result;
    }
}

三、JDK动态代理的简单实现

针对静态代理的问题比较明显,LogStaticProxy只代理了HelloService这类目标对象。
倘若业务中有UserServiceOrderService等也需要做这样的日志增强处理,可能需要改造静态代理类LogStaticProxy了,让它动态化,做到对所有接口一律代理。
当然,我们可以使用JDK动态代理的代码实现模式,即利用接口InvocationHandlerProxy.newProxyInstance做到。如下,

package com.szh.proxy.jdkDynamic;

import com.szh.proxy.service.HelloService;
import com.szh.proxy.service.impl.HelloServiceImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class HelloInterfaceServiceProxy implements InvocationHandler {
    
    

	private Object target;

	public HelloInterfaceServiceProxy(Object target) {
    
    
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
		System.out.println("我是 JDK 动态代理");
		Object result = null;
		System.out.println("准备");
		result = method.invoke(target, args);
		System.out.println("完成");
		return result;
	}

	public static void main(String[] args) {
    
    
		// 目标对象
		HelloService target = new HelloServiceImpl();
		// 代理对象
		Object proxyObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
				new HelloInterfaceServiceProxy(target));
		HelloService proxy = (HelloService) proxyObj;
		proxy.say();
		proxy.say("2022", "happy");
	}
}

这里如果不利用这些和其他第三方类库,如何自己实现。
根据静态代理类LogStaticProxy的经验,设想要是能够做到对不同的目标接口,各自生成各自对应的一份LogStaticProxy就达到动态化了。所以设想如下这样三步走:

  1. 自动生成动态代理类的java源文件
  2. 自动编译生成的源文件得到class字节码文件
  3. 加载字节码文件到JVM以生成代理对象

3.1、自动生成动态代理类的java源文件

第一步,目标比较简单,就是通过目标对象的信息,拼接对应代理类的源代码,形同LogStaticProxy.java的内容。
分析其内容,大致包括package语句、import语句、代理类定义语句、成员变量定义语句、构造函数、目标接口的方法实现语句等。主要用到字符串拼接、反射和文件操作的基础知识。

3.2、自动编译生成的源文件得到class字节码文件

第二步,对第一步生成的源代码文件进行编译。主要用到jdk中JavaCompiler的编译方式。
需要注意的是,JavaCompiler存在于jdk而不存在于jre中,所以对一些运行环境只有jre的不可用。

3.3、加载字节码文件到JVM以生成代理对象

最后一步,利用类加载器加载第二步编译好的字节码文件,利用反射在JVM中创造出目标对象对应的代理对象实例。

3.4、JDK动态代理工具类

这里贴出简单日志增强静态代理类LogStaticProxy的动态版,如下,

package com.szh.proxy.util;

import javax.tools.JavaCompiler;
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Locale;

/**
 * JDK动态代理工具类,简单模仿对接口的动态代理<br>
 * 1、自动生成动态代理类的java源文件<br>
 * 2、自动编译生成的源文件得到class字节码文件<br>
 * 3、加载字节码文件到JVM生成代理对象
 */
public class ProxyUtil {
    
    

    private static final String lineSeparator = System.getProperty("line.separator");

    /**
     * 三步走生成代理对象
     * @param target 目标对象
     * @return 代理对象
     */
    public static Object newProxyInstance(Object target) throws IOException, ClassNotFoundException,
            NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    
    
        // step1:生成java文件
        String content = "";
        /**
         * package com.szh.proxy;
         */
        String packageContent = "package com.szh.proxy;";
        // 获取目标对象的接口,暂只考虑一个接口
        Class targetInterface = target.getClass().getInterfaces()[0];
        String targetInterfaceName = targetInterface.getName();
        String targetInterfaceSimpleName = targetInterface.getSimpleName();
        /**
         * import com.szh.proxy.service.HelloService;
         */
        String importContent = "import " + targetInterfaceName + ";";
        String proxyClassName = "$Proxy"; // 注意代理类名需要保证唯一
        String classContent = "public class " + proxyClassName + " implements " + targetInterfaceSimpleName + " {" + lineSeparator;
        /**
         * private HelloService targetObject;
         */
        String fieldContent = "\tprivate " + targetInterfaceSimpleName + " targetObject;" + lineSeparator;
        String constructContent = "\tpublic $Proxy(" + targetInterfaceSimpleName + " targetObject) {" + lineSeparator
                + "\t\tthis.targetObject = targetObject;" + lineSeparator
                + "\t}" + lineSeparator;
        // 代理类中实现目标对象接口的源代码
        String methodsContent = "";
        Method[] declaredMethods = targetInterface.getDeclaredMethods();
        /**
         * @Override
         * public void say(String word) {
         *     System.out.println("我是 JDK 静态代理");
         *     System.out.println("准备");
         *     targetObject.say(word);
         *     System.out.println("完成");
         * }
         */
        for (Method method : declaredMethods) {
    
    
            String methodName = method.getName(); // 方法名
            Class returnType = method.getReturnType(); // 返回类型
            Class[] parameterTypes = method.getParameterTypes();
            String argsContent = "";
            String argsNameContent = "";
            int i = 0;
            for (Class paramType : parameterTypes) {
    
    
                String paramTypeName = paramType.getSimpleName();
                String argName = "arg" + i; // 每个形参名
                argsContent += (paramTypeName + " " + argName + ", "); // 方法形参列表
                argsNameContent += (argName + ", "); // 实参名列表
                i++;
            }
            if (i > 0) {
    
    
                argsContent = argsContent.substring(0, argsContent.lastIndexOf(", "));
                argsNameContent = argsNameContent.substring(0, argsNameContent.lastIndexOf(", "));
            }
            String invokeContent = "";
            String resultContent = "";
            if (Void.TYPE.equals(returnType)) {
    
    
                invokeContent = "\ttargetObject." + methodName + "(" + argsNameContent + ");";
            } else {
    
    
                invokeContent = "\t" + returnType.getSimpleName() + " result = targetObject." + methodName + "(" + argsNameContent + ");";
                resultContent = "return result;";
            }
            methodsContent += "\t@Override" + lineSeparator
                    + "\tpublic " + returnType.getSimpleName() + " " + methodName + "(" + argsContent + ") {" + lineSeparator
                    + "\t\tSystem.out.println(\"我是 JDK 动态代理\");" + lineSeparator // 代理逻辑,简单实现先暂时写死
                    + "\t\tSystem.out.println(\"准备\");" + lineSeparator
                    + "\t" + invokeContent + lineSeparator
                    + "\t\tSystem.out.println(\"完成\");" + lineSeparator;
            if (Void.TYPE.equals(returnType)) {
    
    
                methodsContent += "\t}" + lineSeparator;
            } else {
    
    
                methodsContent += "\t\t" + resultContent + lineSeparator;
                methodsContent += "\t}" + lineSeparator;
            }
        }
        content = packageContent + lineSeparator
                + importContent + lineSeparator
                + classContent + lineSeparator
                + fieldContent + lineSeparator
                + constructContent + lineSeparator
                + methodsContent + lineSeparator
                + "}";
        String sourceFilePath = "/Users/songzehao/Desktop/com/szh/proxy/" + proxyClassName + ".java";
        File sourceFile = new File(sourceFilePath);
        File dir = new File("/Users/songzehao/Desktop/com/szh/proxy");
        if (!dir.exists()) dir.mkdirs();
        if (!sourceFile.exists()) sourceFile.createNewFile();
        FileWriter fileWriter = new FileWriter(sourceFile);
        fileWriter.write(content);
        fileWriter.flush();
        fileWriter.close();

        // step2:编译java文件(使用jdk的api,开发环境可用,但运行环境只有jre,不可用)
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.CHINA, Charset.defaultCharset());
        Iterable iterable = fileManager.getJavaFileObjects(sourceFile);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
        task.call();
        fileManager.close();

        // step3:加载class文件,返回代理对象
        URL[] urls = new URL[] {
    
    new URL("file:///Users/songzehao/Desktop/")};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class clazz = classLoader.loadClass("com.szh.proxy.$Proxy");
        // $Proxy没有无参构造,不使用clazz.newInstance(),要使用含参构造
        Constructor constructor = clazz.getDeclaredConstructor(targetInterface);
        Object proxyInstance = constructor.newInstance(target);
        return proxyInstance;
    }
}

3.5、解耦代理逻辑

上面的ProxyUtil因为未将代理逻辑解耦出来,如若以后除了这样的简单日志处理,还有别的切面代理业务,可能需要重写一份ProxyUtil,再修改里面写死的代理逻辑,不够灵活。
所以使用处理器handler的方式将代理逻辑抽离出来。

3.5.1、代理逻辑处理器

定义代理逻辑处理器接口InvokeHandler

package com.szh.proxy.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public interface InvokeHandler {
    
    

    Object getTarget(); // 返回目标对象
    Object invoke(Method method, Object... args) throws InvocationTargetException, IllegalAccessException;
}

实现简单日志切面功能的代理逻辑处理器LogHandler

package com.szh.proxy.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 增强日志处理器,内含了目标对象、调用详情(方法、参数、代理逻辑)
 */
public class LogHandler implements InvokeHandler {
    
    

    private Object target; // 目标对象

    public LogHandler(Object target) {
    
    
        this.target = target;
    }

    @Override
    public Object getTarget() {
    
    
        return target;
    }

    /**
     * 在此添加调用目标对象的业务方法并添加增强日志处理
     * @param method 目标对象的业务方法
     * @param args 目标对象业务方法的参数
     * @return 目标对象方法返回值
     */
    @Override
    public Object invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
    
    
        System.out.println("我也是 JDK 动态代理");
        System.out.println("准备");
        Object result = method.invoke(target, args);
        System.out.println("完成");
        return result;
    }
}

这样解耦出来,对其他切面逻辑的处理,只需要像上面这样实现即可。

3.5.2、解耦后的JDK动态代理工具类

ProxyUtil解耦改造为ProxyInvokeUtil

package com.szh.proxy.util;

import javax.tools.JavaCompiler;
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Locale;

/**
 * JDK动态代理工具类,在{@link com.szh.proxy.util.ProxyUtil}基础上,换种handler的方式实现invoke,
 * 以此将增强业务的处理和工具类{@link com.szh.proxy.util.ProxyUtil}解耦,
 * 因为{@link com.szh.proxy.util.ProxyUtil}中的增强业务或代理逻辑是写死的,不够灵活,
 * 这样以后若有别的切面业务,只需要写一个新切面的处理器如{@link com.szh.proxy.util.LogHandler}即可,
 * 就不用再写一个{@link com.szh.proxy.util.ProxyUtil},然后替换代理逻辑了。
 */
public class ProxyInvokeUtil {
    
    

    private static final String lineSeparator = System.getProperty("line.separator");

    /**
     * 三步走生成代理对象
     * @param invokeHandler 增强处理器(已经内含了目标对象)
     * @return 代理对象
     */
    public static Object newProxyInstance(InvokeHandler invokeHandler) throws IOException, ClassNotFoundException,
            NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    
    
        // step1:生成java文件
        String content = "";
        /**
         * package com.szh.proxy;
         */
        String packageContent = "package com.szh.proxy;";
        // 获取目标对象的接口,暂只考虑一个接口
        Object target = invokeHandler.getTarget();
        Class targetInterface = target.getClass().getInterfaces()[0];
        String targetInterfaceName = targetInterface.getName();
        String targetInterfaceSimpleName = targetInterface.getSimpleName();
        /**
         * import com.szh.proxy.util.InvokeHandler;
         * import com.szh.proxy.service.HelloService;
         * import java.lang.reflect.Method;
         */
        String importContent = "import com.szh.proxy.util.InvokeHandler;" + lineSeparator
                + "import " + targetInterfaceName + ";" + lineSeparator
                + "import java.lang.reflect.Method;";
        String proxyClassName = "$Proxy"; // 注意代理类名需要保证唯一
        String classContent = "public class " + proxyClassName + " implements " + targetInterfaceSimpleName + " {" + lineSeparator;
        /**
         * private InvokeHandler invokeHandler;
         */
        String fieldContent = "\tprivate InvokeHandler invokeHandler;" + lineSeparator;
        String constructContent = "\tpublic $Proxy(InvokeHandler invokeHandler) {" + lineSeparator
                + "\t\tthis.invokeHandler = invokeHandler;" + lineSeparator
                + "\t}" + lineSeparator;
        // 代理类中实现目标对象接口的源代码
        String methodsContent = "";
        Method[] declaredMethods = targetInterface.getDeclaredMethods();
        /**
         * @Override
         * public void say(String word) {
         *     // 通过业务接口获取该方法里对应的method
         *     Method method = HelloService.class.getDeclaredMethod("say");
         *     invokeHandler.invoke(method, word);
         * }
         */
        for (Method method : declaredMethods) {
    
    
            String methodName = method.getName(); // 方法名
            Class returnType = method.getReturnType(); // 返回类型
            Class[] parameterTypes = method.getParameterTypes();
            String argsContent = "";
            String argsNameContent = "";
            String classTypesContent = "";
            int i = 0;
            for (Class paramType : parameterTypes) {
    
    
                String paramTypeName = paramType.getSimpleName();
                String argName = "arg" + i; // 每个形参名
                argsContent += (paramTypeName + " " + argName + ", "); // 方法形参列表
                argsNameContent += (argName + ", "); // 实参名列表
                classTypesContent += paramType.getName() + ".class, ";
                i++;
            }
            if (i > 0) {
    
    
                argsContent = argsContent.substring(0, argsContent.lastIndexOf(", "));
                argsNameContent = argsNameContent.substring(0, argsNameContent.lastIndexOf(", "));
                classTypesContent = classTypesContent.substring(0, classTypesContent.lastIndexOf(", "));
            } else {
    
    
                // 方法无参
                argsNameContent = "(Object[]) null";
                classTypesContent = "(Class[]) null";
            }
            // 暂时catch所有异常
            String tryContent = "\t\ttry {";
            String catchContent = "\t\t} catch (Exception e) {" + lineSeparator + "\t\t\tSystem.err.println(e);" + lineSeparator + "\t\t}";
            String invokeContent = "\t\t\tMethod method = " + targetInterfaceSimpleName + ".class.getDeclaredMethod(\""
                    + methodName + "\", " + classTypesContent + ");" + lineSeparator;
            if (!Void.TYPE.equals(returnType)) {
    
    
                invokeContent = tryContent + lineSeparator + invokeContent;
                invokeContent = "\t\t" + returnType.getSimpleName() + " result = null;" + lineSeparator + invokeContent;
                invokeContent += "\t\t\tresult = (" + returnType.getSimpleName() + ") invokeHandler.invoke(method, " + argsNameContent + ");" + lineSeparator;
                invokeContent += catchContent + lineSeparator;
                invokeContent += "\t\treturn result;" + lineSeparator;
            } else {
    
    
                invokeContent = tryContent + lineSeparator + invokeContent;
                invokeContent += "\t\t\t" + "invokeHandler.invoke(method, " + argsNameContent + ");" + lineSeparator;
                invokeContent += catchContent + lineSeparator;
            }
            methodsContent += "\t@Override" + lineSeparator
                    + "\tpublic " + returnType.getSimpleName() + " " + methodName + "(" + argsContent + ") {" + lineSeparator
                    + invokeContent + "\t}" + lineSeparator;
        }
        content = packageContent + lineSeparator
                + importContent + lineSeparator
                + classContent + lineSeparator
                + fieldContent + lineSeparator
                + constructContent + lineSeparator
                + methodsContent + lineSeparator
                + "}";
        String sourceFilePath = "/Users/songzehao/Desktop/com/szh/proxy/" + proxyClassName + ".java";
        File sourceFile = new File(sourceFilePath);
        File dir = new File("/Users/songzehao/Desktop/com/szh/proxy");
        if (!dir.exists()) dir.mkdirs();
        if (!sourceFile.exists()) sourceFile.createNewFile();
        FileWriter fileWriter = new FileWriter(sourceFile);
        fileWriter.write(content);
        fileWriter.flush();
        fileWriter.close();

        // step2:编译java文件(使用jdk的api,开发环境可用,但运行环境只有jre,不可用)
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.CHINA, Charset.defaultCharset());
        Iterable iterable = fileManager.getJavaFileObjects(sourceFile);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
        task.call();
        fileManager.close();

        // step3:加载class文件,返回代理对象
        URL[] urls = new URL[] {
    
    new URL("file:///Users/songzehao/Desktop/")};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class clazz = classLoader.loadClass("com.szh.proxy.$Proxy");
        // $Proxy没有无参构造,不使用clazz.newInstance(),要使用含参构造
        Constructor constructor = clazz.getDeclaredConstructor(InvokeHandler.class);
        Object proxyInstance = constructor.newInstance(invokeHandler);
        return proxyInstance;
    }
}

为了主线思路简洁起见,未处理很多优化点,如对目标对象指定接口进行代理,以及生成的代理类对返回值为基本类型或自定义类型的处理,以及对异常的处理等等。

3.6、测试一下

写个测试方法跑一下,

package com.szh.proxy;

import com.szh.proxy.service.HelloService;
import com.szh.proxy.service.impl.HelloServiceImpl;
import com.szh.proxy.util.LogHandler;
import com.szh.proxy.util.ProxyInvokeUtil;
import com.szh.proxy.util.ProxyUtil;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class TestMyProxy {
    
    

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException,
            InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
        HelloService targetObject = new HelloServiceImpl();
        LogHandler logHandler = new LogHandler(targetObject);
//        HelloService proxyInstance = (HelloService) ProxyUtil.newProxyInstance(targetObject);
        HelloService proxyInstance = (HelloService) ProxyInvokeUtil.newProxyInstance(logHandler);
        proxyInstance.say();
        proxyInstance.say("2022", "happy");
    }
}

下面输出符合预期,

我也是 JDK 动态代理
准备
hello 
完成
我也是 JDK 动态代理
准备
hello2 2022 happy
完成

再查看下动态生成的$Proxy代理类文件,
在这里插入图片描述

查看一下解耦后的$Proxy.java的内容,

package com.szh.proxy;
import com.szh.proxy.util.InvokeHandler;
import com.szh.proxy.service.HelloService;
import java.lang.reflect.Method;
public class $Proxy implements HelloService {
    
    

	private InvokeHandler invokeHandler;

	public $Proxy(InvokeHandler invokeHandler) {
    
    
		this.invokeHandler = invokeHandler;
	}

	@Override
	public void say() {
    
    
		try {
    
    
			Method method = HelloService.class.getDeclaredMethod("say", (Class[]) null);
			invokeHandler.invoke(method, (Object[]) null);
		} catch (Exception e) {
    
    
			System.err.println(e);
		}
	}
	@Override
	public String say(String arg0, String arg1) {
    
    
		String result = null;
		try {
    
    
			Method method = HelloService.class.getDeclaredMethod("say", java.lang.String.class, java.lang.String.class);
			result = (String) invokeHandler.invoke(method, arg0, arg1);
		} catch (Exception e) {
    
    
			System.err.println(e);
		}
		return result;
	}

}

以上。

猜你喜欢

转载自blog.csdn.net/songzehao/article/details/122267119