动态代理学习(一)自己动手模拟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

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


/

相关文章
|
4月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
1月前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
71 4
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
38 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
27 1
|
4月前
|
Java API 开发者
Jdk动态代理为啥不能代理Class?
该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。
Jdk动态代理为啥不能代理Class?
|
4月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
312 0
|
4月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
211 0
|
3月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
382 3
|
4天前
|
NoSQL 关系型数据库 MySQL
Linux安装jdk、mysql、redis
Linux安装jdk、mysql、redis
72 7
|
4月前
|
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应用打下基础。
66 1