模拟JDK动态代理 ; 自己动手模拟实现java动态代理

来源:互联网 发布:传奇dbc数据库 编辑:程序博客网 时间:2024/05/22 03:52

大家在看java设计模式之 代理模式这篇文章的时候, 可以发现动态代理无非就是以下四个步骤,我们完全可以自己模拟实现。因为java的class文件格式是公开的,只要最终生成的class格式正确并且可以加载到JVM中我们就可以正常使用啦。
1. 创建代理类的源码;
2. 对源码进行编译成字节码;
3. 将字节码加载到内存;
4. 实例化代理类对象并返回给调用者;

使用聚合模式实现静态代理

本质上,动态代理是在程序运行过程中创建生成一个类并且将它加载到JVM中,通过上面的实现步骤,他是把额外的代码(spring中叫切面)植入到被代理类(方法)中以后合成一个类。与静态代理的实现是一样的.

下面这段代码就是聚合模式实现的静态代理:

public interface HelloWorld {    void sayHello(String name);}
public class HelloWorldImpl implements HelloWorld {    @Override      public void sayHello(String name) {          try {            Thread.sleep(1500);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("我是RealSubject。 Hello " + name);    }  }  
/** * Created by liubenlong on 2017/2/4. * 聚合模式实现静态代理 */public class HelloWorldProxy implements HelloWorld {    private HelloWorldImpl helloWorld;    public HelloWorldProxy(HelloWorldImpl helloWorld){        this.helloWorld = helloWorld;    }    @Override    public void sayHello(String name) {        System.out.println("before  log...");        helloWorld.sayHello(name);        System.out.println("end  log...。该方法总共执行时间是10毫秒。");    }}

测试代码:

HelloWorldProxy helloWorldProxy = new HelloWorldProxy(new HelloWorldImpl());helloWorldProxy.sayHello("zhangsan");

结果:

before  log...我是RealSubject。 Hello zhangsanend  log...。该方法总共执行时间是10毫秒。

使用JDK自带的JavaCompiler

必须jdk6及以上

假设我们已经组装好了代码(其实就是上面那段静态代理的代码),我们就可以将其加载到JVM中来使用了。

这里说的组装代码的过程在JDK中是使用反射来完成的。

package com.lbl.proxy;import org.apache.commons.io.FileUtils;import javax.tools.JavaCompiler;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import java.io.File;import java.lang.reflect.Constructor;import java.net.URL;import java.net.URLClassLoader;public class Proxy {    private static final String  RT = "\r\n";    public static Object newProxyInstance(Class classObj) throws Exception{        //声明一段源码        String sourceCode =        "package com.lbl.proxy;" + RT +        "/**" + RT +        " * Created by liubenlong on 2017/2/4." + RT +        "* 聚合模式实现静态代理" + RT +        "*/" + RT +        "public class $Proxy1 implements HelloWorld {" + RT +        "   private HelloWorld helloWorld;" + RT +        "   public $Proxy1(HelloWorld helloWorld){" + RT +        "       this.helloWorld = helloWorld;" + RT +        "   }" + RT +        "   @Override" + RT +        "   public void sayHello(String name) {" + RT +        "       System.out.println(\"before  log...\");" + RT +        "       helloWorld.sayHello(name);" + RT +        "       System.out.println(\"end  log...。该方法总共执行时间是10毫秒。\");" + RT +        "   }" + RT +        "}";        String filename = System.getProperty("user.dir") + "/src/main/java/com/lbl/proxy/$Proxy1.java";        File file = new File(filename);        //使用org.apache.commons.io.FileUtils.writeStringToFile()将源码写入磁盘        //编写到处,可以运行一下程序,可以在当前目录中看到生成的.java文件        FileUtils.writeStringToFile(file,sourceCode);        //获得当前系统中的编译器        JavaCompiler complier = ToolProvider.getSystemJavaCompiler();        System.out.println(complier.getClass().getName());        //获得文件管理者        StandardJavaFileManager fileMgr = complier.getStandardFileManager(null, null, null);        Iterable its = fileMgr.getJavaFileObjects(filename);        //编译任务        JavaCompiler.CompilationTask task = complier.getTask(null, fileMgr, null, null, null, its);        //开始编译,执行完可在当前目录下看到.class文件        task.call();        fileMgr.close();        //load到内存        URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};        URLClassLoader urlClassLoader = new URLClassLoader(urls);        Class cls = urlClassLoader.loadClass("com.lbl.proxy.$Proxy1");        //生成代理类对象        Constructor ct = cls.getConstructor(HelloWorld.class);//找一个参数类型是HelloWorldImpl.class的构造方法        return ct.newInstance(new HelloWorldImpl());    }}class test{    public static void main(String[] args) throws Exception {        HelloWorld proxyObj = (HelloWorld) Proxy.newProxyInstance(HelloWorld.class);        proxyObj.sayHello("zhangsan");    }}

代码的含义上面注释已经写得很清楚了。运行一下,可以看到在项目目录下出现两个类:$Proxy1.java$Proxy1.class
这里写图片描述

输出结果:

com.sun.tools.javac.api.JavacToolbefore  log...我是RealSubject。 Hello zhangsanend  log...。该方法总共执行时间是10毫秒。

深度模拟

上面的模拟代码中,只能支持实现了HelloWOrld接口的类。那么怎么才能实现与接口无关,支持任何接口,并且将植入的代码独立出来呢?也就是把生成代理类的代码独立出来与业务无关,这也满足设计模式中的低耦合、职责单一原则。

要想支持任何接口,即生成代理代码的类与实际的接口无关。这个比较简单,因为我们的类都是实现了接口的,而接口中有哪些方法和参数,都是可以通过反射获取到的,那么我们只需要将其实现的接口传递过去即可。

那么怎么将具体的和业务相关的代理逻辑代码抽离出来呢?前面提到了反射,反射可以通过Method调用某个方法,那么我们就可以在调用方法前后做一些事情了。

通过上面的简单分析可以发现,关键点在于反射。我们可以把接口和代理实现作为参数传递进去就好啦。

我们要做的就是把接口中定义的方法通过反射的方式获取到,进行生成代码。并且将其内部实现转移到Handler中去。Handler类是具体的要植入的代码类。就是把生成的下面这段代码修改掉:

@Override   public void sayHello(String name) {       System.out.println("before  log...");       helloWorld.sayHello(name);       System.out.println("end  log...。该方法总共执行时间是10毫秒。");   }

代理可以有多种多样,怎么取写代码呢?每当出现多种不同的实现的时候我们就应该考虑到java的多态了。我们把代理抽象出一个接口MyInvocationHandler,里面只有一个方法,该方法用于对被代理对象的调用。

类图如下
这里写图片描述

import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * Created by liubenlong on 2017/2/4. */public interface MyInvocationHandler {    public Object invoke(Method method, Object o, Object ... params) throws InvocationTargetException, IllegalAccessException;}

我们写两个实现类:日志代理类和计时代理类:

import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * Created by liubenlong on 2017/2/4. */public class MyLogProxy implements MyInvocationHandler {    @Override    public Object invoke(Method method, Object o, Object ... params) throws InvocationTargetException, IllegalAccessException {        System.out.println("aa");        Object invoke = method.invoke(o, params);        System.out.println("bb");        return invoke;    }}
package com.lbl.proxy;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * Created by liubenlong on 2017/2/4. */public class MyTimeProxy implements MyInvocationHandler {    @Override    public Object invoke(Method method, Object o, Object ... params) throws InvocationTargetException, IllegalAccessException{        long begin = System.currentTimeMillis();        Object invoke = method.invoke(o, params);        System.out.println(method.getName() + " 执行总共耗时:"+(System.currentTimeMillis() - begin) + "毫秒。");        return invoke;    }}

修改后的代理生成类:

package com.lbl.proxy;import org.apache.commons.io.FileUtils;import javax.tools.JavaCompiler;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import java.io.File;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import java.util.concurrent.atomic.AtomicLong;/** * */public class Proxy2 {    private static final String  RT = "\r\n";    public static final AtomicLong proxyNum = new AtomicLong(0);    /**     * @param interfaceObj  参数接口     * @return     * @throws Exception     */    public static Object newProxyInstance(Class interfaceObj, Object o, MyInvocationHandler handler) throws Exception{        Method[] methods = interfaceObj.getMethods();        StringBuilder b = new StringBuilder();        for (Method method : methods) {            int parameterCount = method.getParameterCount();            Class<?>[] parameterTypes = method.getParameterTypes();            StringBuilder paramStr = new StringBuilder();            StringBuilder paramClasses = new StringBuilder();            for(int i = 0 ; i < parameterCount ; i ++){                paramStr.append(parameterTypes[i].getName()).append(" ").append("arg").append(i).append(",");                paramClasses.append(parameterTypes[i].getName()).append(".class,");            }            b.append("   @Override" + RT )                    .append("   public void "+method.getName()+"(" + paramStr.substring(0, paramStr.length()-1) + ") {" + RT)//这里我们假设所有方法都没有参数                    .append("try {")                    .append("       Method method = HelloWorld.class.getMethod(\""+method.getName()+"\", "+paramClasses.substring(0, paramClasses.length()-1)+");")                    .append("       handler.invoke(method, obj, arg0);" + RT )                    .append("} catch (Exception e) {e.printStackTrace();}")                    .append("   }" + RT)            ;        }        long num = proxyNum.getAndIncrement();        //声明一段源码        String sourceCode =                "package com.lbl.proxy;" + RT +                "import java.lang.reflect.Method;" + RT +                        "/**" + RT +                        " * Created by liubenlong on 2017/2/4." + RT +                        "* 聚合模式实现静态代理" + RT +                        "*/" + RT +                        "public class $Proxy"+num+" implements " + interfaceObj.getName() + " {" + RT +                        "   private "+interfaceObj.getName()+" obj;" + RT +                        "   private MyInvocationHandler handler;" + RT +                        "   public $Proxy" + num +"("+interfaceObj.getName()+" obj, MyInvocationHandler handler){" + RT +                        "       this.obj = obj;" + RT +                        "       this.handler = handler;" + RT +                        "   }" + RT +                        b.toString() +                        "}";        String filename = System.getProperty("user.dir") + "/src/main/java/com/lbl/proxy/$Proxy" + num + ".java";        File file = new File(filename);        //使用org.apache.commons.io.FileUtils.writeStringToFile()将源码写入磁盘        //编写到处,可以运行一下程序,可以在当前目录中看到生成的.java文件        //并不是一定要生成文件,如果可以直接生成二进制代码load到JVM中,则不必要那么麻烦生成文件了        FileUtils.writeStringToFile(file,sourceCode);        //获得当前系统中的编译器        JavaCompiler complier = ToolProvider.getSystemJavaCompiler();        System.out.println(complier.getClass().getName());        //获得文件管理者        StandardJavaFileManager fileMgr = complier.getStandardFileManager(null, null, null);        Iterable its = fileMgr.getJavaFileObjects(filename);        //编译任务        JavaCompiler.CompilationTask task = complier.getTask(null, fileMgr, null, null, null, its);        //开始编译,执行完可在当前目录下看到.class文件        task.call();        fileMgr.close();        //load到内存        URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};        URLClassLoader urlClassLoader = new URLClassLoader(urls);        Class cls = urlClassLoader.loadClass("com.lbl.proxy.$Proxy" + num);        //生成代理类对象        Constructor ct = cls.getConstructor(interfaceObj, MyInvocationHandler.class);//找一个参数类型是HelloWorldImpl.class的构造方法        return ct.newInstance(o, handler);    }}class test2{    public static void main(String[] args) throws Exception {        HelloWorld proxyObj = (HelloWorld) Proxy2.newProxyInstance(HelloWorld.class, new HelloWorldImpl(), new MyTimeProxy());        proxyObj.sayHello("zhangsan");    }}

运行一下,我们查看一下输出结果:

com.sun.tools.javac.api.JavacTool我是RealSubject。 Hello zhangsansayHello 执行总共耗时:1502毫秒。

然后看一下代码生成的代理类

import java.lang.reflect.Method;/** * Created by liubenlong on 2017/2/4.* 聚合模式实现静态代理*/public class $Proxy0 implements com.lbl.proxy.HelloWorld {   private com.lbl.proxy.HelloWorld obj;   private MyInvocationHandler handler;   public $Proxy0(com.lbl.proxy.HelloWorld obj, MyInvocationHandler handler){       this.obj = obj;       this.handler = handler;   }   @Override   public void sayHello(java.lang.String arg0) {        try {                   //最关键的两行代码            Method method = HelloWorld.class.getMethod("sayHello", java.lang.String.class);                   handler.invoke(method, obj, arg0);        } catch (Exception e) {            e.printStackTrace();        }    }}

到此为止,代理类可以永远不需要改变了。

每当需要新的代理需求,则编写一个实现了MyInvocationHandler的类就行,然后调用也相当的方便,只需要修改对应的这一行代码的参数即可:HelloWorld proxyObj = (HelloWorld) Proxy2.newProxyInstance(HelloWorld.class, new HelloWorldImpl(), new MyTimeProxy());

0 0
原创粉丝点击