动态代理学习(一)自己动手模拟JDK动态代理

简介: 动态代理学习(一)自己动手模拟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文件后,我们通过反射获取一个对象

image.png

现在问题来了,我们需要生成的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盘中生成文件:

image.png

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

image.png

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


/

相关文章
|
2月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
8天前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
14 1
|
2月前
|
Java API 开发者
Jdk动态代理为啥不能代理Class?
该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。
Jdk动态代理为啥不能代理Class?
|
2月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
137 0
|
2月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
146 0
|
4月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
29 0
|
4月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
39 0
|
1月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
191 3
|
2月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
51 1
|
2月前
|
Oracle Java 关系型数据库
Mac安装JDK1.8
Mac安装JDK1.8
585 4