模拟JDK动态代理实现
2016-06-01 19:46
627 查看
JDK动态代理
在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层技术。
JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。而Proxy为InvocationHandler实现类动态创建一个复合某一接口的代理的实例。
JDK动态代理其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类完成对目标对象的代理。这点也是与CGLIB代理最大不同之处,CGLIB代理是针对类生成代理。
JDK动态代理的使用
要想模拟JDK动态代理的实现,首先要明白它的内在机理,知道它对外开放的接口方法。借用上篇文章中记录JDK动态代理时的简单示例,仅包括两个类:TimeHandler类和Test类,来回顾下JDK动态代理的使用。
运行结果:
JDK动态代理实现思路
如以上Test类中,通过Proxy的newProxyInstance()方法动态产生一个代理。模拟JDK动态代理实现流程,同样要自定义一个Proxy类并在类中实现产生动态代理的方法,这也是实现JDK动态代理的关键点和主要功能。
动态代理实现步骤如下
声明一段源码(动态产生代理)
编译源码(JDK Compiler API),产生代理类
将新产生的代理类load到内存当中,产生一个新的对象即代理对象
在newProxyInstance方法中返回这个代理对象
声明一段源码(动态产生代理)
将源码定义为字符串,Proxy类中代码如下:
由于JDK动态代理产生的类名为$Proxy0,所以在此模仿此类名。
由于在接下来的工作中要对源码进行编译,所以要将源码写入一个java文件。
在Proxy类中增加以下代码:
其中FileUtils类需要导入包commons-io.jar,运行以上代码后可在Navigator视窗看到对应包下生成了$Proxy.java文件。
由于动态代理要实现对任意接口的任意方法进行代理,所以要对以上的源码字符串进行更改,更改思路即将接口当作参数传入newProxyInstance方法,更改之后的Proxy类如下所示:
Test测试类中将接口参数传入:
运行后检查生成的$Proxy.java文件是否正确。
编译源码(JDK Compiler API),产生代理类
编译即相当于我们在黑窗口时用的javac命令,在程序中编译的代码如下:
测试运行成功后可发现在新生成的代理类java文件同目录下生成了对应的class文件,即$Proxy0.class。
将新产生的代理类load到内存当中,产生一个新的对象即代理对象
关键代码如下:
在Test类中测试是否创建成功:
运行结果:
可以看到通过使用自己定义的Proxy.newProxyInstance方法成功返回了代理对象,与一开始的时候使用JDK动态代理得到同样的结果。以下为自定义Proxy类的全部代码:
目前这个Proxy类实现了对任意接口任意方法的代理,但是这只不过完成了一半,因为其中代理的逻辑是写死的,只能实现时间记录。这就需要继续改进,在JDK动态代理中是通过InvocationHandler来实现灵活改变代理逻辑的,继续模仿,我们自定义一个接口也叫做InvocationHandler。
这个接口需要的功能就是要实现对方法代理逻辑的灵活增改,传入参数为需要代理的对象和方法。然后创建一个具体的代理类实现InvocationHandler 接口,在中具体实现代理逻辑:
定义好对方法的事务处理器后,在Proxy类中引入使用。Proxy类修改后完整代码如下:
测试类如下:
测试运行后结果如下:
上述代码中并没有贴出Car类的代码,以下补出:
到此就完成了对JDK动态代理的简单模拟,大致思路就是首先声明一段源码,这段源码用来动态产生代理类。然后把源码转换生成为Java文件,然后对这个Java文件进行编译,生成对应的class文件。之后呢,再用类加载器加载到内存中,并生成代理对象,返回之。
在使用时需要先创建代理的具体逻辑即事务处理器InvocationHandler,在事务处理器可以实现时间记录功能或日志功能。然后将写好的事务处理作为参数传入Proxy的newProxyInstance的方法,这样返回的就是我们需要的代理对象。
在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动态代理的关键点和主要功能。
动态代理实现步骤如下
声明一段源码(动态产生代理)
编译源码(JDK Compiler API),产生代理类
将新产生的代理类load到内存当中,产生一个新的对象即代理对象
在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的方法,这样返回的就是我们需要的代理对象。
相关文章推荐
- Java 正则表达式
- Java中instanceof关键字解析
- JAVA学习之——Spring 注释 @Autowired 和@Resource 的区别
- SpringMVC
- JDK升级到1.7后 com.sun.image.codec.jpeg JPEGImageEncoder不存在
- Hibernate+Spring整合时报错
- 深入理解java中HelloWorld的执行流程
- myeclipse优化设置
- 深入学习java并发编程:内存模型(一)基础
- 字符串全排列 java实现
- Java RMI远程接口调用介绍与使用
- struts2实现批量上传和打包批量下载
- 学习笔记:Quartz初印象
- java学习
- Java中有关Null的9件事
- eclipse 断点进不去 显示 source not found
- 详解Java的MyBatis框架中的缓存与缓存的使用改进
- Java中的String
- 深入Java关键字null
- Webservice入门简单实例 教程