模拟jdk动态代理(完整版)

实现思路

1: 定义一个字符串s
2:加载s利用流生成对应的java文件
3:通过类加载器加载java文件生成class文件
4:通过class生成代理对象
5:测试成功

我使用过jdk代理的场景

1:通过拦截request对象,代理其中的get参数的方法来过滤敏感词
2:通过阅读aop源码发现,底层用的也是动态代理(jdk,cglib)
3:jdk代理源码解析

jdk代理的使用

模拟之前我先聊一下jdk代理的使用,通过proxy.newProxyInstance(a,b,c)我们就可以生成一个代理对象了。

  • 参数a含义是需要被代理对象的类加载器。
  • 参数b含义是需要被代理对象所实现的所有接口。
  • 参数c含义是需要一个实现InvocationHandler接口的类对象,
    • invoke中的各个参数含义、
    1. proxy指生成的代理对象
    2. method指代理的具体方法。

在invoke里面我们可以实现我们自己的代理逻辑。这样我们就完成了对c对象的代理了,c对象实现了a接口。本文重点在于jdk代理的底层研究这里不做过多jdk代理的使用说明了

  @Test
    public void proxy(){
        c target = new c();
        System.out.println("target.hashCode: "+target.getClass());
        a proxy = (a)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
                System.out.println("jdk proxy");
                System.out.println("proxy.hashCode: "+proxy.getClass());
                return method.invoke(target);
            }
        });
        proxy.say();

    }

知道使用jdk动态代理了,我们这就来手写一个。

定义一个String s = “内容如下”

package com.zzh;
import proxy.a;
import proxy.myHandler;
import java.lang.reflect.Method;
public class myProxy implements a{
	
	public myProxy(myHandler target){
	this.h = target;
	}

	private myHandler h;
	public String say(){
	try{
Method method = Class.forName("proxy.a").getDeclaredMethod("say");

	return (String)h.invoke(method);
	}
catch (Exception e) {
            e.printStackTrace();
        }
return null;
	}
}

wc到底怎么定义能不能说清除呢。好了先定义包这一块吧,

 String packageContent = "package com.zzh;" + line;
 

接着定义导入包这一块myHandler为我们自己写的一个接口,为的是模拟原版的InvocationHandler,需要导入java的包直接写名字就行了,导入自己定义的包把包名改掉就行了

String importContent = "import " + packageName + ";" + line
                + "import " + myHandler.getClass().getName() + ";" + line
                + "import java.lang.reflect.Method;" + line;

接着定义类名,需要变动的就是targetName,这个名字可以随意取

String classContent = "public class myProxy implements " + targetName;

接着定义构造函数

String constructContent = line + tab + "public myProxy" + "(" + "myHandler target" + "){" + line +
            tab + "this.h = target;" + line + tab + "}" + line;
           

接着定义被代理对象中的所有方法,这里稍微有点长。我们先获取被代理对象中的所有方法

Method[] methods = target.getDeclaredMethods();

然后遍历这些方法,挨个定义,我们往字符串中传入一个方法需要Method method = Class.forName(“proxy.a”).getDeclaredMethod(“say”);然后再把这个方法才能被这个字符串类使用,而不是直接return (String)h.invoke(直接把method写在这);,鬼知道在编译期间method变成什么鬼名字了

  for (Method method : methods) {
            //方法的参数部分
            String argsContent = "";
            //方法的返回值
            String returnArgs = "";
            //获取代理方法的所有参数
            Class<?>[] args = method.getParameterTypes();
            //获取代理方法的返回值
            String returnType = method.getReturnType().getSimpleName();
            //遍历参数String p0 , String p1 这样拼接
            for (Class arg : args) {
                argsContent += arg.getSimpleName() + " p" + i + ",";

                returnArgs += "p" + i + ",";
                i++;
            }
            if (argsContent.length() > 0) {
                //把最后多余的逗号去掉
                argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
                returnArgs = returnArgs.substring(0, returnArgs.lastIndexOf(",") - 1);
            }
            if (!returnType.equals("void")) {
                methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
                        + "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
                        + line + line + tab + "return (String)h.invoke(method);"
                        + line + tab + "}"
                        + line + "catch (Exception e) {\n" +
                        "            e.printStackTrace();\n" +
                        "        }" + line + "return null;" + line + tab + "}";
            } else {
                methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
                        + "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
                        + line + line + tab + "(String)h.invoke(method);"
                        + line + tab + "}"
                        + line + "catch (Exception e) {\n" +
                        "            e.printStackTrace();\n" +
                        "        }" + line + "return null;" + line + tab + "}";
            }


            i++;
        }

最后把之前定义的片段全部加起来

  content = packageContent + importContent + classContent + "{" + line + tab + constructContent
                + line + tab + "private " + myHandlerName + " h;" + methodContent + line + "}";

ok到这一步一个对应java对象的字符串已经有了,接着就是想怎么把它变成 java文件,接而变成class文件,然后编译了,接着生效了。把字符串变成java文件好办,用流操作就行了。我们定义的文件的路径名需要和我们先前定义的包名保持一致

   File file = new File("D:\\com\\zzh\\myProxy.java");

            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.flush();
            fileWriter.close();

接着编译java,生成class文件

   JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
            Iterable units = fileManager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, units);
            task.call();
            fileManager.close();

最后加载我们的calss,变现成java对象,这个对象就是我们的代理对象了

   URL[] urls = new URL[]{new URL("file:D:\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class<?> aClass = urlClassLoader.loadClass("com.zzh.myProxy");
            Constructor<?> constructor = aClass.getConstructor(myHandler.getClass());
            myProxy = constructor.newInstance(myHandler);

完整代码如下,测试没问题


package proxy;

import org.junit.Test;

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;
/**
 * @author 张帅
 * @class jdk代理实现
 */
public class proxyUtilsPlus {
    /**
     * @param target被代理的对象 myHandler被代理的逻辑
     * @method
     */
    public static Object proxy(Class target, myHadlerI myHandler) {
        String line = "\n";
        String tab = "\t";
        int i = 0;
        String content = "";

        String packageName = target.getName();
        String targetName = target.getSimpleName();
        String packageContent = "package com.zzh;" + line;
        String importContent = "import " + packageName + ";" + line
                + "import " + myHandler.getClass().getName() + ";" + line
                + "import java.lang.reflect.Method;" + line;
        String classContent = "public class myProxy implements " + targetName;
        String methodContent = "";
        String constructContent = line + tab + "public myProxy" + "(" + "myHandler target" + "){" + line +
                tab + "this.h = target;" + line + tab + "}" + line;
        String myHandlerName = proxy.myHandler.class.getSimpleName();
        Method[] methods = target.getDeclaredMethods();
        for (Method method : methods) {
            //方法的参数部分
            String argsContent = "";
            //方法的返回值
            String returnArgs = "";
            //获取代理方法的所有参数
            Class<?>[] args = method.getParameterTypes();
            //获取代理方法的返回值
            String returnType = method.getReturnType().getSimpleName();
            //遍历参数String p0 , String p1 这样拼接
            for (Class arg : args) {
                argsContent += arg.getSimpleName() + " p" + i + ",";

                returnArgs += "p" + i + ",";
                i++;
            }
            if (argsContent.length() > 0) {
                //把最后多余的逗号去掉
                argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
                returnArgs = returnArgs.substring(0, returnArgs.lastIndexOf(",") - 1);
            }
            if (!returnType.equals("void")) {
                methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
                        + "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
                        + line + line + tab + "return (String)h.invoke(method);"
                        + line + tab + "}"
                        + line + "catch (Exception e) {\n" +
                        "            e.printStackTrace();\n" +
                        "        }" + line + "return null;" + line + tab + "}";
            } else {
                methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
                        + "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
                        + line + line + tab + "(String)h.invoke(method);"
                        + line + tab + "}"
                        + line + "catch (Exception e) {\n" +
                        "            e.printStackTrace();\n" +
                        "        }" + line + "return null;" + line + tab + "}";
            }


            i++;
        }
        content = packageContent + importContent + classContent + "{" + line + tab + constructContent
                + line + tab + "private " + myHandlerName + " h;" + methodContent + line + "}";
        Object myProxy = null;

        try {
            File file = new File("D:\\com\\zzh\\myProxy.java");

            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.flush();
            fileWriter.close();
            //生成class文件
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
            Iterable units = fileManager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, units);
            task.call();
            fileManager.close();
            //加载class文件
            URL[] urls = new URL[]{new URL("file:D:\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class<?> aClass = urlClassLoader.loadClass("com.zzh.myProxy");
            Constructor<?> constructor = aClass.getConstructor(myHandler.getClass());
            myProxy = constructor.newInstance(myHandler);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return myProxy;
    }

    @Test
    public void test() {
        a proxy = (a) proxy(a.class, new myHandler(new c()));
        System.out.println("method return: " + proxy.say());

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小咸鱼的技术窝

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值