模拟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());
- 模拟JDK动态代理 ; 自己动手模拟实现java动态代理
- 模拟JDK动态代理实现
- 模拟实现JDK动态代理
- JDK 动态代理 模拟
- 模拟JDK动态代理
- 模拟JDK动态代理
- 模拟JDK动态代理
- 模拟JDK动态代理类的实现
- JAVA动态代理 ps:模拟jdk
- 模拟java动态代理
- JAVA动态代理模拟
- Java动态代理三--模拟AOP实现
- 由微见著,模拟JDK动态代理的实现1
- 模拟JDK动态代理-独立逻辑代码
- 模拟JDK动态代理(JAVA设计模式)
- java实现JDK动态代理
- Java动态代理三——模拟AOP实现
- Java动态代理三——模拟AOP实现
- c# 工作两年了感觉自己已经是一个废人了
- Android AdapterViewFlipper
- iOS 8开发入门--序言(2)
- Sublime Text 3快捷键排行榜
- noip初赛复习(全)(转)
- 模拟JDK动态代理 ; 自己动手模拟实现java动态代理
- 获取某年的某天是第几周
- 在windows上创建完美的不规则窗口
- yum -y upgrade 和 yum -y update 区别
- 普及练习场之交叉模拟
- JavaSE_7th_权限控制
- P3378 堆
- 输入一个字符串,同时输入帧头和帧尾
- Add Two Numbers