如何实现动态代理 - 动态代理底层原理精讲

2022-06-07 21:35:08 浏览数 (1)

在编程体系中,AOP切面技术,框架底层源码都离不开动态代理的影子。那么究竟动态代码的功能是如何实现的呢?今天本篇就此问题展开动态代理底层源码逻辑的讲解。

思路分析:

我们知道一个类,要想让它真正的参与运算,需要经过如下几个步骤:

a.编写.java文件;

b.编译成字节码.class文件;

c.使用类加载器加载到jvm中。

以上步骤便是我们平时编写类到产生作用的过程,基于此逻辑原理,同样的我们也可以按此步骤去动态的创建我们的类,也就是本篇要讲到的代理类。实现逻辑思路如下:

a.依据真实对象,动态的拼接.java代码的内容;

b.将.java代码以字符流的形式写入到磁盘;

c.使用类加载器加载到jvm中(此处编译和类加载器同步执行)。

好了,废话不多说,直接上代码:

代码语言:javascript复制
package com.luban.proxy;

import com.sun.jndi.toolkit.url.UrlUtil;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class ProxyUtil {
    /**
     * @Author GONGWENXUE
     * @Description //TODO 创建动态代理类
     * @version: v1.8.0
     * @Date 11:25 2020/8/12
     * @param target 真实对象
     **/
    public static Object newInstance(Object target){
        Object proxy=null;
        //获取真实对象的接口(默认取第一个)
        Class targetInf = target.getClass().getInterfaces()[0];
        //获取方法集合
        Method methods[] =targetInf.getDeclaredMethods();
        String line="n";
        String tab ="t";
        //接口名
        String infName = targetInf.getSimpleName();
        //动态拼接.java文件字符串内容
        String content ="";
        //包名
        String packageContent = "package com.google;" 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("log");" line
                           tab tab "target." methodName "(" paramsContent ");" line
                           tab "}" line;
        }

        //拼接完整.java内容
        content = packageContent   importContent   clazzFirstLineContent   filedContent   constructorContent   methodContent   "}";
        //创建文件对象,准备写入到磁盘
        File file = new File("d:\com\google\$Proxy.java");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            //将.java文件写入到文件中
            FileWriter fw = new FileWriter(file);
            fw.write(content);
            fw.flush();
            fw.close();

            //编译.java文件 JavaCompiler是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);
            //执行编译任务(得到.class文件)
            t.call();
            fileMgr.close();

            //打开网络连接
            URL[] urls = new URL[]{new URL("file:D:\\")};
            //使用类加载器加载文件
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("com.google.$Proxy");
            //得到构造器
            Constructor constructor = clazz.getConstructor(targetInf);
            //生成代理类
            proxy = constructor.newInstance(target);
            //clazz.newInstance();
            //Class.forName()
        }catch (Exception e){
            e.printStackTrace();
        }
        return proxy;
    }
}

综上代码中比较难理解的是动态编译类JavaCompiler 和文件管理器StandardJavaFileManager。不了解的可以简单理解成这是java的固定语法规则,使用他们就能动态的编译任何的.java文件。

真实对象的写法:

接口:

代码语言:javascript复制
package com.luban.dao;

public interface UserDao {
    public void query();
    public void query(String p);
}

实现类:

代码语言:javascript复制
public class UserDaoImpl implements UserDao{
    public void query(){
        System.out.println("假装查询数据库");
    }
}

测试生成代理类:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        UserDaoproxy = (UserDao) ProxyUtil.newInstance(new UserDaoImpl());
        proxy.query();
    }
}

运行结果:

运行测试类会你会发现在执行真实对象的打印之前,打印了代理对象的数据,这是由于我们在动态拼接方式methodContent时,先执行了自己的增强逻辑就那些打印,后调用真实的方法。

生成的代理对象:

虽然以上的代理实现了动态代理的功能,但仔细观察会发现还是有些问题的:增强逻辑是写死的打印,并未能动态增强。那么怎么解决这个问题呢?还有JDK的动态代理底层原理也是通过这种方式实现的吗?

本公众后后期为您揭晓答案!敬请关注!谢谢!

0 人点赞