玖叶教程网

前端编程开发入门

java动态代理:如何手写一个jdk动态代理

很多java优秀框架都应用了java里的高级特性,比如java动态代理等。

为了更好地方便大家理解 我后续将会写的文章内容,咱们先来理解下这些特性。

秉承先简后难的原则,这篇文章,先简单手写一个java动态代理。

这是什么意思? 买房子找中介,那么中介就是代理人,卖房人就是被代理的人。

java代理中涉及两个名词:代理对象(增强后的对象)以及目标对象(被代理的对象)

先了解一下静态代理的实现方式

一 java静态代理

1 新建一个 目标对象的接口类 ObjService,接口方法 echoUserName

   /**
 * 目标对象接口。
 */
public interface ObjService {
    void echoUserName(String name);
}

2 新建一个 实现类,ObjServiceImpl

      /**
 * 目标对象实现类。
 */
public class ObjServiceImpl  implements ObjService{
    @Override
    public void echoUserName(String name) {
        System.out.println( "hello,"+name);
    }
}

3 目标对象有了,接下来,就是创建一个 同样实现ObjService接口的 代理类 LogProxyServiceImpl,在构造方法中传入要代理的目标对象。在方法中加入增强的代码逻辑。

/**
 * 日志代理类
 */
public class LogProxyServiceImpl implements ObjService{
    private ObjService objService;

    /**
     * 将 目标对象 通过构造函数的方式 加到代理类里
     * @param objService
     */
    public LogProxyServiceImpl(ObjService objService){
        this.objService = objService;
    }
    @Override
    public void echoUserName(String name) {
      //增强的逻辑
        System.out.println("log log log  start $#34;);
        objService.echoUserName(name);
        System.out.println("log log log end $#34;);
    }
}

4 测试一下结果

 public class ProxyTest {
    public static void main(String[] args) {
        // 创建目标对象
            ObjService objService = new ObjServiceImpl();
            //为对象加代理类
            ObjService logProxy = new LogProxyServiceImpl(objService);
            logProxy.echoUserName("小码哥");
    }
}

结果如下。代理成功了。

可以看到 每实现一个对象的代理类,都得新建一个,如果需要记录执行时间,那只能再加一个 执行时间的代理类。所以一个对象有多个代理类,多个对象呢,类就泛滥了。

  public static void main(String[] args) {
        // 创建目标对象
            ObjService objService = new ObjServiceImpl();
            //为对象加日志代理类
            ObjService logProxy = new LogProxyServiceImpl(objService);
            //再加入时间代理类
            ObjService timeProxy = new TimeProxyServiceImpl(logProxy);
            timeProxy.echoUserName("小码哥");
    }

二 java动态代理

大家可以想象一下,执行一个java的方法,需要做哪些工作呢?

第一,需要新建一个java文件;第二,编译成.class文件 ;第三,新建获取对象;最后运行。

动态生成java代理类,也是需要这么做的。接下来,咱们就来通过动态代理的方式来实现上述ObjService目标对象的日志代理类。

下面是重点,大家打起十二分精神。

1 新建一个 动态代理生成类DynamicProxy, 方法名称 newProxyInstance

     public  static  Object  newProxyInstance(Object target) throws Exception {
            // 获取目标对象的接口,接口可以多个。这里简单点,取第一个
            Class targetInfo = target.getClass().getInterfaces()[0];
            // (1)通过目标对象 构建代理类文件,返回文件地址
            File file = getProxyFile(target,targetInfo);

            //(2)编译上面的java文件
            compile(file);

            //(3)通过类加载器加载类,并构建代理对象
            URL[] urls = new URL[]{new URL("file:d:\\\\tmp\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class cl = urlClassLoader.loadClass("com.example.proxy.$Proxy");
            Constructor constructor = cl.getConstructor(targetInfo);
            Object result = constructor.newInstance(target);
            return  result;
        } 

2 通过目标对象 构建代理类文件,返回文件地址。getProxyFile方法的代码如下,里面是通过反射的方式获取的。

private static File getProxyFile(Object target,Class targetInfo) throws Exception{
            String content = ""; //java文件的内容。
            String packageContent = "package com.example.proxy;"; //文件包名
            //接口名   ObjService
            String targetInfoName = targetInfo.getSimpleName();
            // import  ObjService 路径
            String importContent = "import " + targetInfo.getName() + ";";
            //声明代理类名$Proxy,并实现 ObjService接口
            String classContent = "public class $Proxy implements " + targetInfoName + "{";
            //  声明目标对象   private ObjService target
            String fieldContent = "private " + targetInfoName + " target;";
            //构造方法.          public  $Proxy(ObjService target){ this.target = target}
            String constructContent = "public $Proxy(" + targetInfoName + " target){" +
                    "this.target = target;" +
                    "}";
            //方法内容
            String methodsContent = "";
            //获取ObjService接口的所有方法
            Method[] methods = targetInfo.getDeclaredMethods();
            for(Method method:methods){
                //方法名  echoUserName
                String methodName = method.getName();
                //返回类型 void
                Class returnType = method.getReturnType();
                //参数类型 这里是String
                Class<?>[] paramTypes = method.getParameterTypes();
                String argsContent = "";
                String argsNames = "";
                int i=0;
                for(Class<?> paramType:paramTypes){
                    String simpleName = paramType.getSimpleName();
                    argsContent += simpleName+" a"+i+",";
                    argsNames+= "a"+i+",";
                    i++;
                }
                if(argsContent.length()>0){
                    //去掉最后的 逗号
                    argsContent = argsContent.substring(0,argsContent.length()-1);
                    argsNames = argsNames.substring(0,argsNames.length()-1);
                }
                //生成文件
                methodsContent += "public  "+returnType+" "+methodName+"("+argsContent+"){"
                        +"System.out.println(\"log log log  start $$\");"
                        +"target."+methodName+"("+argsNames+");"
                        +"System.out.println(\"log log log end $$\");"
                        +"}";
            }
            //把所有的组件内容 合起来
            content+=packageContent+importContent+classContent+fieldContent+constructContent+methodsContent+"}";
            //生成 $Proxy.java文件。
            File file = new File("D:\\tmp\\com\\example\\proxy\\$Proxy.java");
            if(!file.exists()){
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.flush();
            fileWriter.close();
            return file;
        }

3 编译java文件。代码如下。这里用系统自带的,不太建议使用,cglib 有更好的方式,大家可以先了解下这个。

private  static void compile(File file) throws Exception{
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null,null);
            Iterable units = fileManager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null,fileManager,null,null,null,units);
            task.call();
            fileManager.close();
        }

经过上面两步,我们这里生产的是这样的文件。

4 通过类加载器 URLClassLoader 加载 class文件。

URL[] urls = new URL[]{new URL("file:d:\\\\tmp\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class cl = urlClassLoader.loadClass("com.example.proxy.$Proxy");
            Constructor constructor = cl.getConstructor(targetInfo);
            Object result = constructor.newInstance(target);
            return  result;

5 修改测试代码如下。

public class ProxyTest {
    public static void main(String[] args)  throws Exception{
        // 创建目标对象
        ObjService objService = new ObjServiceImpl();
//            //为对象加日志代理类
//            ObjService logProxy = new LogProxyServiceImpl(objService);
//            //再加入时间代理类
//            ObjService timeProxy = new TimeProxyServiceImpl(logProxy);
        //使用动态代理生成类.
        ObjService proxy = (ObjService) DynamicProxy.newProxyInstance(objService);
        proxy.echoUserName("小码哥");
    }
}

运行结果如下。符合预期。

log log log  start $$
hello,小码哥
log log log end $$

上面我们尽量用最简单的方式 写了一个 java动态代理类。真正用于生产实践 里面还有很多难点需要考虑处理。但万变不离其宗。

喜欢的小伙伴多多 关注 点赞 转发。后续会提供更多的技术文章。也希望大家多给点意见。咱们共同进步。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言