首页 > 其他分享 >模拟jdk动态代理(完整版)

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

时间:2024-01-20 22:01:49浏览次数:31  
标签:String jdk tab import line 完整版 method 模拟 target


实现思路

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());

    }
}


标签:String,jdk,tab,import,line,完整版,method,模拟,target
From: https://blog.51cto.com/u_16414043/9346331

相关文章

  • Python实战:selenium模拟浏览器运行,获取软科网站2023中国大学排名
    Python实战:selenium模拟浏览器运行,获取软科网站2023中国大学排名在爬取一些加密的网页时,可以使用selenium模拟浏览器运行,再从网页中提取想要的数据。使用的库本文使用到的Python库有:selenium、bs4、pandas使用selenium解决网页的反爬使用bs4对html网页进行解析和提取数据......
  • spring--JDK动态代理的实现原理
    JDK动态代理的实现原理涉及到Java的反射机制。它允许在运行时动态创建一个代理类,这个代理类实现了一组接口,并将所有方法调用转发到一个InvocationHandler实例。下面是JDK动态代理的实现原理的详细步骤:定义接口:首先,定义一个或多个接口,这些接口声明了需要被代理的方法。......
  • spring--JDK动态代理和CGLIB代理的区别
    JDK动态代理和CGLIB代理是Java中常用的两种动态代理实现方式,它们各有特点和适用场景:JDK动态代理:JDK动态代理是基于接口的代理方式,它使用Java反射机制来创建代理对象,并要求目标对象实现一个或多个接口。在代理过程中,JDK动态代理会创建一个实现了目标对象所有接口的代......
  • FZOJ省选模拟构造合集
    FZOJ省选模拟构造合集FZOJ5404[2023NOIP前模拟]机械之心/CF600FEdgecoloringofbipartitegraph小Y拥有着机械之心。小Y拥有\(k\)种不同的心脏,排列在一个\(n×m\)的网格中。我们用\(c_{i,j}\)​代表第\(i\)行第\(j\)列的心脏的类型:\(1.\)如果\(c_{i,j}......
  • 《Java并发实现原理:JDK源码剖析》PDF
    《Java并发实现原理:JDK源码剖析》全面而系统地剖析了JavaConcurrent包中的每一个部分,对并发的实现原理进行了深刻的探讨。全书分为8章,第1章从最基础的多线程知识讲起,理清多线程中容易误解的知识点,探究背后的原理,包括内存重排序、happen-before、内存屏障等;第2~8章,从简单到复杂,逐......
  • 《Java并发实现原理:JDK源码剖析》PDF
    《Java并发实现原理:JDK源码剖析》全面而系统地剖析了JavaConcurrent包中的每一个部分,对并发的实现原理进行了深刻的探讨。全书分为8章,第1章从最基础的多线程知识讲起,理清多线程中容易误解的知识点,探究背后的原理,包括内存重排序、happen-before、内存屏障等;第2~8章,从简单到复杂,逐......
  • [Java SE/JDK] Map之重定义key对象的hash值
    0序言项目上有个场景:数据源连接池需要对key对象的hash值重写,保证通过相同的关键属性(datasourceName)值去重不同的对象。publicabstractclassAbstractDatabaseConnectorKeyedObjectPool<KextendsDataSource,VextendsAbstractConnector>1重写Map的key对象的hash值......
  • azul zule jdk(jvm) 8 support g1gc
    [WhichGarbageCollectorsareavailableinaparticularAzulZuluversion?–Azul](https://support.azul.com/hc/en-us/articles/360057827811-Which-Garbage-Collectors-are-available-in-a-particular-Azul-Zulu-version-) zulujdk8javaoptions*[Command-Line......
  • JDK9 - VarHandle小记
    说在前面在开始之前,有必要点明一下虽只字未提但贯穿全文的核心,从而知道我们使用某些API的目的是什么:VarHandle/Unsafe提供了比volatile关键字更弱的变量访问方式,合理地利用它们可以让我们程序可以在符合运行预期的话情况下提高性能,这里的“弱”指的是约束更少。所谓约束,举个例子......
  • 洛谷题单指南-模拟和高精度-P1098 [NOIP2007 提高组] 字符串的展开
    原题链接:https://www.luogu.com.cn/problem/P1098题意解读:题目本身是一道模拟题,但是细节点较多,要拿100分,有以下注意点:1、-号两个需要同时为小写字母或者数字,才进行填充2、-号左边>=右边,直接输出-3、对待填充的内容的处理,可以先看是否填充*;小写字母和数字的填充都是前一位asci......