模拟JDK动态代理实现

JDK动态代理

在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层技术。

JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。而Proxy为InvocationHandler实现类动态创建一个复合某一接口的代理的实例。

JDK动态代理其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类完成对目标对象的代理。这点也是与CGLIB代理最大不同之处,CGLIB代理是针对类生成代理。

JDK动态代理的使用

要想模拟JDK动态代理的实现,首先要明白它的内在机理,知道它对外开放的接口方法。借用上篇文章中记录JDK动态代理时的简单示例,仅包括两个类:TimeHandler类和Test类,来回顾下JDK动态代理的使用。

package com.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 *  JDK动态代理步骤:
 *   1创建一个实现InvocationHandler的类,必须实现invoke方法
 *   2.创建被代理的类和接口
 *   3.调用Proxy的静态方法,创建一个代理类
 *   4.通过代理调用方法.
 *
 */
public class TimeHandler implements InvocationHandler {

    private Object object;

    public TimeHandler(Object object) {
        super();
        //代理对象
        this.object = object;
    }

    /**
     * proxy: 代理对象
     * method: 被代理对象的方法
     * args:方法的参数
     * return:  调用方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        long startTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶");

        method.invoke(object);

        long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms");
        return null;
    }

}
package com.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import com.proxy.Car;
import com.proxy.Moveable;

public class Test {

    public static void main(String[]args){
        Car car = new Car();
        InvocationHandler h = new TimeHandler(car);
        Class<?> cls = car.getClass();
        /**
         * loader:类加载器
         * interfaces:实现的接口
         * h InvocationHandler
         */
        Moveable  m = (Moveable) Proxy.newProxyInstance(
                cls.getClassLoader(), cls.getInterfaces(), h);
        m.move();
    }
}

运行结果:
这里写图片描述

JDK动态代理实现思路

如以上Test类中,通过Proxy的newProxyInstance()方法动态产生一个代理。模拟JDK动态代理实现流程,同样要自定义一个Proxy类并在类中实现产生动态代理的方法,这也是实现JDK动态代理的关键点和主要功能。

动态代理实现步骤如下

  1. 声明一段源码(动态产生代理)
  2. 编译源码(JDK Compiler API),产生代理类
  3. 将新产生的代理类load到内存当中,产生一个新的对象即代理对象
  4. 在newProxyInstance方法中返回这个代理对象

声明一段源码(动态产生代理)
将源码定义为字符串,Proxy类中代码如下:

package com.myJDKproxy;

public class Proxy {

    public static Object newProxyInstance(){
        String rt = "\r\t";
        String str = 
         "package com.myJDKproxy;" + rt +
         "public class $Proxy0 implements Moveable{"+ rt +
         "private Moveable m;"+ rt +
         "   public $Proxy0(Moveable m) {"+ rt +
         "      super();"+ rt +
         "      this.m = m;"+ rt +
         "   }"+ rt +
         "   @Override"+ rt +
         "   public void move() {"+ rt +
         "     long startTime = System.currentTimeMillis();"+ rt +
         "     System.out.println(\"汽车开始行驶\");"+ rt +
         "     m.move();"+ rt +
         "     long endTime = System.currentTimeMillis();"+ rt +
         "     System.out.println(\"汽车行驶结束,行驶时间为:\"+(endTime-startTime)+\" ms\");"+ rt +
         "   }"+ rt +
         "}";
        return null;
    }
}

由于JDK动态代理产生的类名为$Proxy0,所以在此模仿此类名。

由于在接下来的工作中要对源码进行编译,所以要将源码写入一个java文件。
在Proxy类中增加以下代码:

String fileName = System.getProperty("user.dir")+"/bin/com/myJDKproxy/$Proxy.java";
File file = new File(fileName);
FileUtils.writeStringToFile(file, str);

其中FileUtils类需要导入包commons-io.jar,运行以上代码后可在Navigator视窗看到对应包下生成了$Proxy.java文件。

由于动态代理要实现对任意接口的任意方法进行代理,所以要对以上的源码字符串进行更改,更改思路即将接口当作参数传入newProxyInstance方法,更改之后的Proxy类如下所示:

package com.myJDKproxy;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;

import org.apache.commons.io.FileUtils;

public class Proxy {

    public static Object newProxyInstance(Class infec) throws Exception{
        String rt = "\r\t";
        String methodStr = "";
        for(Method m:infec.getMethods()){
            methodStr += 
                     "   @Override"+ rt +
                     "   public void " + m.getName() + "(){"+ rt +
                     "     long startTime = System.currentTimeMillis();"+ rt +
                     "     System.out.println(\"汽车开始行驶\");"+ rt +
                     "     m."+ m.getName() + "();"+ rt +
                     "     long endTime = System.currentTimeMillis();"+ rt +
                     "     System.out.println(\"汽车行驶结束,行驶时间为:\"+(endTime-startTime)+\" ms\");"+ rt +
                     "   }";
        }
        String str = 
         "package com.myJDKproxy;" + rt +
         "public class $Proxy0 implements " +infec.getName()+ " {"+ rt +
         "private " +infec.getName()+ " m;"+ rt +
         "   public $Proxy0(" +infec.getName()+ " m) {"+ rt +
         "      super();"+ rt +
         "      this.m = m;"+ rt +
         "   }"+ rt +
         methodStr + rt +
         "}";
        String fileName = System.getProperty("user.dir")+"/bin/com/myJDKproxy/$Proxy0.java";
        File file = new File(fileName);
        FileUtils.writeStringToFile(file, str);
        return null;
    }
}

Test测试类中将接口参数传入:

package com.myJDKproxy;

public class Test {
    public static void main(String []args) throws Exception{
        Proxy.newProxyInstance(Moveable.class);
    }
}

运行后检查生成的$Proxy.java文件是否正确。

编译源码(JDK Compiler API),产生代理类
编译即相当于我们在黑窗口时用的javac命令,在程序中编译的代码如下:

//获取编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //文件管理者
        StandardJavaFileManager fileMgr = 
                compiler.getStandardFileManager(null, null, null);
        //获取文件
        Iterable units = fileMgr.getJavaFileObjects(fileName);
        //编译任务
        CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        //进行编译
        t.call();

        fileMgr.close();

测试运行成功后可发现在新生成的代理类java文件同目录下生成了对应的class文件,即$Proxy0.class。

将新产生的代理类load到内存当中,产生一个新的对象即代理对象

关键代码如下:

        //load到内存
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("com.myJDKproxy.$Proxy0");
        //创建代理对象
        Constructor ctr = c.getConstructor(infec);
        return ctr.newInstance(new Car());

在Test类中测试是否创建成功:

package com.myJDKproxy;

public class Test {
    public static void main(String []args) throws Exception{
        Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
        m.move();
    }
}

运行结果:
这里写图片描述

可以看到通过使用自己定义的Proxy.newProxyInstance方法成功返回了代理对象,与一开始的时候使用JDK动态代理得到同样的结果。以下为自定义Proxy类的全部代码:

package com.myJDKproxy;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;

public class Proxy {

    public static Object newProxyInstance(Class infec) throws Exception{
        String rt = "\r\t";
        String methodStr = "";
        for(Method m:infec.getMethods()){
            methodStr += 
                     "   @Override"+ rt +
                     "   public void " + m.getName() + "(){"+ rt +
                     "     long startTime = System.currentTimeMillis();"+ rt +
                     "     System.out.println(\"汽车开始行驶\");"+ rt +
                     "     m."+ m.getName() + "();"+ rt +
                     "     long endTime = System.currentTimeMillis();"+ rt +
                     "     System.out.println(\"汽车行驶结束,行驶时间为:\"+(endTime-startTime)+\" ms\");"+ rt +
                     "   }";
        }
        String str = 
         "package com.myJDKproxy;" + rt +
         "public class $Proxy0 implements " +infec.getName()+ " {"+ rt +
         "private " +infec.getName()+ " m;"+ rt +
         "   public $Proxy0(" +infec.getName()+ " m) {"+ rt +
         "      super();"+ rt +
         "      this.m = m;"+ rt +
         "   }"+ rt +
         methodStr + rt +
         "}";
        //产生代理类的Java文件
        String fileName = System.getProperty("user.dir")+"/bin/com/myJDKproxy/$Proxy0.java";
        File file = new File(fileName);
        FileUtils.writeStringToFile(file, str);

        /**
         * 编译
         */
        //获取编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //文件管理者
        StandardJavaFileManager fileMgr = 
                compiler.getStandardFileManager(null, null, null);
        //获取文件
        Iterable units = fileMgr.getJavaFileObjects(fileName);
        //编译任务
        CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        //进行编译
        t.call();
        fileMgr.close();


        //load到内存
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("com.myJDKproxy.$Proxy0");
        //创建代理对象
        Constructor ctr = c.getConstructor(infec);
        return ctr.newInstance(new Car());
    }
}

目前这个Proxy类实现了对任意接口任意方法的代理,但是这只不过完成了一半,因为其中代理的逻辑是写死的,只能实现时间记录。这就需要继续改进,在JDK动态代理中是通过InvocationHandler来实现灵活改变代理逻辑的,继续模仿,我们自定义一个接口也叫做InvocationHandler。

package com.myJDKproxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

    public void invoke(Object o,Method m);

}

这个接口需要的功能就是要实现对方法代理逻辑的灵活增改,传入参数为需要代理的对象和方法。然后创建一个具体的代理类实现InvocationHandler 接口,在中具体实现代理逻辑:

package com.myJDKproxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {
    //被代理对象
    private Object target;

    public TimeHandler(Object target) {
        super();
        this.target = target;
    }
    @Override// 参数o为代理对象
    public void invoke(Object o, Method m) {
        try {
            long startTime = System.currentTimeMillis();
            System.out.println("汽车开始行驶");
            m.invoke(target);
            long endTime = System.currentTimeMillis();
            System.out.println("汽车行驶结束,行驶时间为:" + (endTime - startTime) + " ms");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

定义好对方法的事务处理器后,在Proxy类中引入使用。Proxy类修改后完整代码如下:

package com.myJDKproxy;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;

public class Proxy {

    public static Object newProxyInstance(Class infec,InvocationHandler h) throws Exception{
        String rt = "\r\t";
        String methodStr = "";
        for(Method m:infec.getMethods()){
            methodStr += 
                     "   @Override"+ rt +
                     "   public void " + m.getName() + "(){"+ rt +
                     "   try{ "+ rt +
                     "   Method md = " + infec.getName() + ".class.getMethod(\""
                                       + m.getName() + "\");" + rt +
                     "   h.invoke(this,md);" + rt +
                     "   }catch(Exception e){e.printStackTrace();}" + rt +
                     "   }";
        }
        String str = 
         "package com.myJDKproxy;" + rt +
         "import com.myJDKproxy.InvocationHandler;"+ rt +
         "import java.lang.reflect.Method;"+ rt +
         "public class $Proxy0 implements " +infec.getName()+ " {"+ rt +
         "private InvocationHandler h;"+ rt +
         "   public $Proxy0(InvocationHandler h) {"+ rt +
         "      super();"+ rt +
         "      this.h = h;"+ rt +
         "   }"+ rt +
         methodStr + rt +
         "}";
        //产生代理类的Java文件
        String fileName = System.getProperty("user.dir")+"/bin/com/myJDKproxy/$Proxy0.java";
        File file = new File(fileName);
        FileUtils.writeStringToFile(file, str);

        /**
         * 编译
         */
        //获取编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //文件管理者
        StandardJavaFileManager fileMgr = 
                compiler.getStandardFileManager(null, null, null);
        //获取文件
        Iterable units = fileMgr.getJavaFileObjects(fileName);
        //编译任务
        CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        //进行编译
        t.call();
        fileMgr.close();


        //load到内存
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("com.myJDKproxy.$Proxy0");
        //创建代理对象
        Constructor ctr = c.getConstructor(InvocationHandler.class);
        return ctr.newInstance(h);
    }
}

测试类如下:

package com.myJDKproxy;

public class Test {
    public static void main(String []args) throws Exception{
        Car car = new Car();
        InvocationHandler h = new TimeHandler(car);
        Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class,h);
        m.move();
    }
}

测试运行后结果如下:
这里写图片描述

上述代码中并没有贴出Car类的代码,以下补出:

package com.myJDKproxy;

import java.util.Random;

public class Car implements Moveable{
    @Override
    public void move() {

        try{
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("行驶中");
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

到此就完成了对JDK动态代理的简单模拟,大致思路就是首先声明一段源码,这段源码用来动态产生代理类。然后把源码转换生成为Java文件,然后对这个Java文件进行编译,生成对应的class文件。之后呢,再用类加载器加载到内存中,并生成代理对象,返回之。

在使用时需要先创建代理的具体逻辑即事务处理器InvocationHandler,在事务处理器可以实现时间记录功能或日志功能。然后将写好的事务处理作为参数传入Proxy的newProxyInstance的方法,这样返回的就是我们需要的代理对象。

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
问题导读: 1.zookeeper在kafka的作用是什么? 2.kafka中几乎不允许对消息进行“随机读写”的原因是什么? 3.kafka集群consumer和producer状态信息是如何保存的? 4.partitions设计的目的的根本原因是什么? 一、入门     1、简介     Kafka is a distributed,partitioned,replicated commit logservice。它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是JMS规范的实现。kaf

struts2文件上传 - 2016-06-01 18:06:36

文件上传         文件上传几乎是每个web应用实现的一个必须模块。文件上传的实现需要将表单元素属性enctype的值设置为multipart/form-data,使表单数据以二进制编码的方式提交。在接收此请求的Servlet中使用二进制流来获取内容,就可以取得上传文件的内容,从而实现文件的上传。 上传原理 在struts2中进行文件上传时,先需要将Form表单的enctype属性进行重新设置,该属性的取值就是决定表单数据的编码方式,有如下三个可选值:        1)application/x-

Portal架构相关技术汇总 - 2016-06-01 18:06:21

一、什么是Portal? Portal技术强调以用户为中心,简而言之就是整合现有企业中遗留的各种系统,使之有统一的入口,实现信息的集中访问。 二、Portal中主要应用的功能 1)SSO—Single Sign-On 主要开源SSO协议有 ①Jasig CAS CAS 就是 Central Authentication Service(中央认证服务)的意思,CAS 实际上这是一种 SSO 协议。 ②OpenID OpenID 的创建基于这样一个概念:我们可以通过 URI (又叫 URL 或网站地址)来认证

互联网产品灰度发布 - 2016-06-01 18:06:21

互联网产品灰度发布   关于2016年5月15日, DevOps成都站|架构与运维峰会活动总结 1. 前言 2 2. 灰度发布定义 5 3. 灰度发布作用 5 4. 灰度发布步骤 5 5. 灰度发布测试方法 6 6. 灰度发布引擎 6 7. 灰度发布常见问题 8 7.1. 以偏概全 8 7.1.1. 问题特征: 8 7.1.2. 解决方案: 8 7.2. 知识的诅咒 9 7.2.1. 问题特征: 9 7.2.2. 解决方案: 9 7.3. 发布没有回头路可走 9 7.3.1. 问题特征: 9 7.3.2.

负载均衡的那些算法们 - 2016-06-01 17:06:14

上周发了问卷,想了解一下大家对老王有没有什么建议,然后好多朋友都投了票,想了解编程技术和服务器架构的干货,所以接下来会先聊聊编程和架构相关的算法,然后大概在 6 月下旬会跟大家聊聊面试那些事儿(老王到目前大约参加了几百次的面试,可以从面试官的角度来聊聊不一样的面试)。老王聊技术有个特点,就是绝不假大空,只求贴地飞行。所以,聊的东西一定会跟实际有关联,大家在平时也有可能用得着。   今天跟大伙儿聊的是负载均衡相关的一些算法。老王在百度的时候(估计是 5-6 年前),写过一个通用的基础库(不知道现在还有没有部

动态代理实现Spring Aop - 2016-06-01 17:06:54

引言 我们在前两篇文章中,都为这篇做了铺垫,我们现在来做这样一件事情,在业务逻辑中添加Aop的非业务逻辑。 AopClinetTest: package com.tgb.client;import com.tgb.config.BeanFactory;import com.tgb.config.ClassPathXmlApplicationContext;import com.tgb.dao.UserDao;import com.tgb.domain.User;/** * AOP效果测试* @ClassN
背景 问题说明 分析 LeNet5参数 MNIST程序参数 遗留问题 小结 背景 之前 博文 中关于CNN的模型训练功能上是能实现,但是研究CNN模型内部结构的时候,对各个权重系数 w ,偏差 b 的shape还是存在疑惑,为什么要取1024,为什么取7*7*64,最近找到了一些相关资料,对这个问题有了新的理解,下面和大家分享一下。 问题说明 # Input Layer x = tf.placeholder( 'float' ,[ None , 784 ])y_ = tf.placeholder( 'fl
在具体介绍本文内容之前,先给大家看一下Hadoop业务的整体开发流程: 从Hadoop的业务开发流程图中可以看出,在大数据的业务处理过程中,对于数据的采集是十分重要的一步,也是不可避免的一步,从而引出我们本文的主角—Flume。本文将围绕Flume的架构、Flume的应用(日志采集)进行详细的介绍。 (一)Flume架构介绍 1、Flume的概念 flume是分布式的日志收集系统,它将各个服务器中的数据收集起来并送到指定的地方去,比如说送到图中的HDFS,简单来说flume就是收集日志的。 2、Event

Linux服务器安全配置 - 2016-06-01 17:06:30

众所周知,网络安全是一个非常重要的课题,而服务器是网络安全中最关键的环节。Linux被认为是一个比较安全的Internet服务器,作为一种开放源代码操作系统,一旦Linux系统中发现有安全漏洞,Internet上来自世界各地的志愿者会踊跃修补它。然而,系统管理员往往不能及时地得到信息并进行更正,这就给黑客以可乘之机。相对于这些系统本身的安全漏洞,更多的安全问题是由不当的配置造成的,可以通过适当的配置来防止。服务器上运行的服务越多,不当的配置出现的机会也就越多,出现安全问题的可能性就越大。对此,下面将介绍一些
=================================== (接上文:《 架构设计:系统间通信(32)——其他消息中间件及场景应用(下2) 》) 5-7、解决方案三:非侵入式方案 以上两种方案中为了让业务系统能够集成日志采集功能,我们或多或少需要在业务系统端编写一些代码。虽然通过一些代码结构的设计,可以减少甚至完全隔离这些代码和业务代码的耦合度,但是毕竟需要业务开发团队花费精力对这些代码进行维护,业务系统部署时业务对这些代码的配置信息做相应的调整。 这里我们再为读者介绍一种非侵入式的日志采集方