前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何实现动态代理 - 动态代理底层原理精讲

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

作者头像
Java深度编程
发布2022-06-07 21:35:08
4710
发布2022-06-07 21:35:08
举报
文章被收录于专栏:Java深度编程Java深度编程

在编程体系中,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的动态代理底层原理也是通过这种方式实现的吗?

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

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-16,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 Java深度编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com