专栏首页程序员DMZSpring杂谈 | 自己动手模拟JDK动态代理

Spring杂谈 | 自己动手模拟JDK动态代理

 最近一直在学习Spring的源码,Spring底层大量使用了动态代理。所以花一些时间对动态代理的知识做一下总结,主要分为两步
  1. 1.我们自己动手模拟一个动态代理
  2. 2.对JDK动态代理的源码进行分析

场景

public interface MyService {
 void test01();
 void test02(String s);
}

public class MyServiceImpl implements MyService {

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

 @Override
 public void test02(String s) {
  System.out.println(s);
 }
}

public class Main {
 public static void main(String[] args) {
  MyServiceImpl target = new MyServiceImpl();
    }
}

我们现在要对target对象进行代理。大家可以想想,我们如何去生成这个代理对象呢?

思路

分析:
  • 我们先不考虑需要针对target生成一个代理对象,就单纯的生成一个对象来说,我们该怎么办呢?肯定是不能new的,因为我们根本没这个类。
  • 所以为了动态的生成这个对象,我们需要动态的生成一个类,也就是说动态的加载一个类到jvm中

所以我们可以这么做:

  1. 根据我们需要生成一个.java文件
  2. 动态编译成一个.class文件
  3. 拿到这个class文件后,我们通过反射获取一个对象

现在问题来了,我们需要生成的java文件该是什么样子呢?我们可以思考,如果要对这个类做静态代理我们需要怎么做?

package com.dmz.proxy;

import com.dmz.proxy.target.MyService;

/**
 * 静态代理
 */
public class StaticProxy implements MyService {

 private MyService target;

 public StaticProxy(MyService target) {
 this.target = target;
 }

 @Override
 public void test01() {
  System.out.println("proxy print log for test01");
  target.test01();
 }

 @Override
 public void test02(String s) {
  System.out.println("proxy print log for test02");
  target.test02(s);
 }
}

上面就是静态代理的代码,如果我们可以动态的生成这样的一个.java文件,然后调用jdk的方法进行编译,是不是就解决问题了呢?

实践

所以我们现在需要

  1. 拼接字符串,将上面的代码以字符串的形式拼接出来并写入到磁盘文件上,并命名为xxxx.java文件
  2. 编译.java文件,生成.class文件
  3. 加载这个class文件到JVM内存中(实际上就是方法区中),得到一个class对象
  4. 调用反射方法,class.newInstanc(.....)

代码如下:

public class ProxyUtil {
 /**
  * @param target 目标对象
  * @return 代理对象
  */
 public static Object newInstance(Object target) {
  Object proxy = null;
        // 开始拼接字符串
  Class targetInf = target.getClass().getInterfaces()[0];
  Method[] methods = targetInf.getDeclaredMethods();
  String line = System.lineSeparator();
  String tab = "\t";
  String infName = targetInf.getSimpleName();
  String content = "";
  String packageContent = "package com.dmz.proxy;" + line;
  String importContent = "import " + targetInf.getName() + ";" + line;
  String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
  String filedContent = tab + "private " + infName + " target;" + line;
  String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
    + tab + tab + "this.target =target;"
    + line + tab + "}" + line;
  String methodContent = "";
  for (Method method : methods) {
   String returnTypeName = method.getReturnType().getSimpleName();
   String methodName = method.getName();
   // Sting.class String.class
   Class args[] = method.getParameterTypes();
   String argsContent = "";
   String paramsContent = "";
   int flag = 0;
   for (Class arg : args) {
    String temp = arg.getSimpleName();
    //String
    //String p0,Sting p1,
    argsContent += temp + " p" + flag + ",";
    paramsContent += "p" + flag + ",";
    flag++;
   }
   if (argsContent.length() > 0) {
    argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
    paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
   }

   methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
     + tab + tab + "System.out.println(\"proxy print log for " + methodName + "\");" + line
     + tab + tab + "target." + methodName + "(" + paramsContent + ");" + line
     + tab + "}" + line;

  }

  content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";
  //字符串拼接结束
        
        
        // 开始生成.java文件
  File file = new File("g:\\com\\dmz\\proxy\\$Proxy.java");
  try {
   if (!file.exists()) {
    file.createNewFile();
   }
   FileWriter fw = new FileWriter(file);
   fw.write(content);
   fw.flush();
   fw.close();
  // .java文件生成结束
                     
            
        // 开始进行编译   
   JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

   StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
   Iterable units = fileMgr.getJavaFileObjects(file);

   JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
   t.call();
   fileMgr.close();
  // 编译结束,生成.class文件
            
   // 从G盘中加载class文件
   URL[] urls = new URL[]{new URL("file:G:\\\\")};
   URLClassLoader urlClassLoader = new URLClassLoader(urls);
   // 加载
   Class clazz = urlClassLoader.loadClass("com.dmz.proxy.$Proxy");
            // 加载结束
            
            // 构造代理对象
   Constructor constructor = clazz.getConstructor(targetInf);
   proxy = constructor.newInstance(target);

  } catch (Exception e) {
   e.printStackTrace();
  }
  return proxy;
 }
}

我们调用这个方法:

public class Main {
 public static void main(String[] args) {
        MyServiceImpl target = new MyServiceImpl();
        MyService o = ((MyService) ProxyUtil.newInstance(target));
        o.test01();
        o.test02("test02");
    }
}

会在我们的G盘中生成文件:

打开.java文件可以看到如下内容:

同时控制台会正常打印:

这样,我们就完成了一个简单的代理。

本文分享自微信公众号 - 程序员DMZ(programerDmz),作者:程序员DMZ

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring官网阅读(十八)AOP的核心概念

    在前面的文章中我们已经对IOC做过详细的介绍了,本文主要介绍AOP,关于其中的源码部分将在专门的源码专题介绍,本文主要涉及的是AOP的基本概念以及如何使用,本文...

    程序员DMZ
  • 你知道Spring是怎么解析配置类的吗?

    这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!

    程序员DMZ
  • Spring官网阅读(十五)Spring中的格式化(Formatter)

    从上面可以看出,这个两个接口维护了两个功能相反的方法,分别完成对String类型数据的解析以及格式化。

    程序员DMZ
  • SAP ABAP实用技巧介绍系列之 ABAP XSLT select keyword

    选择在title node的value前加上hard code的"Title:", 并将其value设置成红色:

    Jerry Wang
  • "debug assertion failed"解决方案之一(error 原因之一)

    这里是基于MFC对话框的应用程序,本来没有这个错误,删除了Edit Control控件后,出现这个错误 。出错原因是因为只在界面上删除了控件,代码还没删除干净。

    acoolgiser
  • Tomcat 6 —— Realm域管理

    本篇来源于官方文档,但不仅仅是翻译,其中不乏网上搜索的资料与自己的理解。 如有错误,请予指正。 ? 什么是Realm   首先说一下什么是Realm,可...

    用户1154259
  • Java每日一题_关于形参与实参

    1、当调用方法时,如果传入的数值为基本数据类型(包含String类型),形式参数的改变对实际参数不影响。

    Java学习
  • 37000 字 + 代码,艿艿肝的 Shiro 从入门到实战,直接收藏吃灰!

    大家好,我是艿艿,一个让你秃头的小胖子。。。 最近状态有点小好,抠脚一算, https://github.com/YunaiV/SpringBoot-Labs ...

    芋道源码
  • 案例| 品钛在美递交上市申请,金融科技to B服务的典型样本

    美国东部时间7月16日,金融科技解决方案提供商品钛(PINTEC)向美国证券交易委员会(SEC)提交了首次公开招股(IPO)申请书,承销商包括高盛、德意志银行与...

    用户1310347
  • 福利贴——爬取美女&帅哥图片的Java爬虫小程序代码

    文件夹命名是用标签缩写,如果大家看得不顺眼可以等下载完成后手动改一下,比如像有强迫症的我一样。。。

    良月柒

扫码关注云+社区

领取腾讯云代金券