JAVA动态代理模拟
所谓代理,就是有一个代理类和一个被代理类,代理类帮被代理类执行相关的动作,如房产中介帮房东代理出租房屋,当有客户租房子时,客户只要找房产中介就可以了,中介会帮房东去执行租出去这个动作。当然,中介可以在中间做一些事情,如带客户看房子,最主要的是万恶的中介会收中介费,扯远了。。。。。言归正传
代理分类
代理分为静态代理和动态代理两种,不管静态代理还是动态代理,代理类和被代理累一般都是实现同一个接口。
静态代理
所谓静态代理,就是如果被代理类的方法在执行之前或之后需要多种操作,那代理类就要写多种,如定义一个接口Flyable(飞行物),有一个fly()方法。有一个被代理类Bird实现了这个接口,现在要计算这只鸟飞行了多久,那我们就要在这只鸟飞行之前记录下时间,飞行之后记录下时间。代码如下:
package com.sf.simba.dynamicproxy.fly; /** * * @author -Simba- * */ public interface Flyable { void fly(); }
package com.sf.simba.dynamicproxy.fly; import java.util.Random; /** * 被代理类 * @author -Simba- * */ public class Bird implements Flyable { @Override public void fly() { System.out.println("The bird is flying......."); try { Thread.sleep(new Random().nextInt(2000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.sf.simba.dynamicproxy.fly; /** * 时间代理类 * 代理类和被代理类实现同一个接口 * @author -Simba- * */ public class TimeBirdProxy implements Flyable { private Flyable flyable; public TimeBirdProxy(Flyable flyable){ this.flyable = flyable; } @Override public void fly() { long start = System.currentTimeMillis(); flyable.fly(); long end = System.currentTimeMillis(); System.out.println("The Flyable has flown "+(end-start)+" millisecond"); } }
package com.sf.simba.dynamicproxy.fly; /** * 测试类 * @author -Simba- * */ public class ProxyMain { public static void main(String[] args) { Flyable bird = new Bird(); Flyable timeproxy = new TimeBirdProxy(bird); timeproxy.fly(); } }
测试结果如下:
The bird is flying.......
The Flyable has flown 1774 millisecond
结果测试可以记录时间了。
如果现在我要在被代理类Bird调用fly()方法后要记录下日志,那是不是要在写一个记录日志的代理类,那如果我要在调用之前做一下权限验证呢?那我是不是要在写一个权限验证的代理类,如果我还有更多的需求呢,那我这个代理类是不是要一直写下去?在比如,我现在要代理一个不是Flyable接口的接口但也是记录时间、记录日志、权限校验。。。。。。。。那这样就会导致类爆炸,对以后的维护带来非常的不便,那我们有没有办法让这个代理类动态生成呢?就是这个代理类不管代理什么我们都可以生成,那这样就好维护多了。
动态代理
从上面的例子中我们可以发现,静态代理会导致类爆炸,难以维护,那么如果这个代理类如果能够动态生成就好了,那么怎么样才能让我们这个代理类动态生成呢,那就是让这个代理类变成一个字符串,动态的改变这个字符串,然后动态编译成字节码,通过ClassLoad装载字节码生成二进制文件到内存,然后生成实例对象(代理对象),然后执行这个代理对象。代码如下
package com.sf.simba.dynamicproxy.fly; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; /** * 动态代理生成类 * @author -Simba- * */ public class Proxy { public static Object newProxyInstance() throws Exception{ String rn = "\r\n"; String classStr = "package com.sf.simba.dynamicproxy.fly; " +rn+ "import com.sf.simba.dynamicproxy.fly.Flyable;" +rn+ " public class TimeBirdProxy implements Flyable { " + rn+ " private Flyable flyable; " + rn+ " public TimeBirdProxy(Flyable flyable){ " + rn+ " this.flyable = flyable; " + rn+ " } " + rn+ " @Override " + rn+ " public void fly() { " + rn+ " long startTime = System.currentTimeMillis(); " + rn+ " flyable.fly(); " + rn+ " long endTime = System.currentTimeMillis(); " + rn+ " System.out.println(\" TimeBirdProxy: The Flyable has flown \" + (endTime - startTime)+\" millisecond\"); " + rn+ " } " + rn+ "\n" + rn+ "}"; //定义动态文件生成到哪里叫什么名字 String fileName = System.getProperty("user.dir")+"/src/com/sf/simba/dynamicproxy/fly/TimeBirdProxy.java"; File file = new File(fileName); FileWriter fw = new FileWriter(file); //把字符串写入文件 fw.write(classStr); fw.flush(); fw.close(); //获得编译器 JavaCompiler complier = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = complier.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName); CompilationTask task = complier.getTask(null, fileManager, null, null, null, units); //编译,生成字节码 task.call(); fileManager.close(); URL[] url = new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src/")}; //加载字节码 URLClassLoader classloder = new URLClassLoader(url); Class<?> loadClass = classloder.loadClass("com.sf.simba.dynamicproxy.fly.TimeBirdProxy"); //获得代理对象构造方法 Constructor<?> constructor = loadClass.getConstructor(Flyable.class); //生成代理对象实例 Object newInstance = constructor.newInstance(new Bird());//new Bird()为被代理对象 System.out.println(newInstance); return newInstance; } }测试类如下:
package com.sf.simba.dynamicproxy.fly; /** * 测试类 * @author -Simba- * */ public class ProxyMain { public static void main(String[] args) throws Exception { //Flyable bird = new Bird(); Flyable timeproxy = (Flyable)Proxy.newProxyInstance(); timeproxy.fly(); } }
测试结果:
com.sf.simba.dynamicproxy.fly.TimeBirdProxy@100a15d
The bird is flying.......
TimeBirdProxy: The Flyable has flown 767 millisecond
从上面这个例子可以看出,我不需要这个TimeBirdProxy类了,这个类被Proxy类自动生成了,但这样只能生成时间的代理类,如果要生成日志的代理类我们还是要改字符串,这明显不是我们想要的,所以我们要动态生成fly这个方法,然后在执行。我们需要这样一个接口,接口里的方法能够动态执行被代理类的方法,而代理类持有这个接口的引用,在调用fly()这个方法时调用这个接口里的方法,我们定义这个接口为InvocationHandler,方法为Invoke,这里有点绕,容易晕,先上一个类图。
代码如下:
package com.sf.simba.dynamicproxy.fly; import java.lang.reflect.Method; /** * 动态代理类持有此接口的引用 * @author -Simba- * */ public interface InvocationHandler { public void invoke(Object o,Method m); }
时间处理类如下:
package com.sf.simba.dynamicproxy.fly; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 时间记录处理类 * @author -Simba- * */ public class TimeHandler implements InvocationHandler { /** * 这个target为被代理类 */ private Object target; private long starttime; private long endtime; public TimeHandler(Object target){ this.target = target; } public void pre(){ starttime = System.currentTimeMillis(); } /** * 为简单起见,假设被代理对象的方法没有参数,在JDK中的动态代理中是有参数的 */ @Override public void invoke(Object o, Method m) { pre();//方法调用前的操作 try { m.invoke(target, null); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } end();//方法调用后的操作 System.out.println(" TimeBirdProxy: The Flyable has flown " + (endtime - starttime)+" millisecond"); } public void end(){ endtime = System.currentTimeMillis(); } }
package com.sf.simba.dynamicproxy.fly; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; /** * 动态代理生成类 * @author -Simba- * */ public class Proxy { public static Object newProxyInstance(InvocationHandler handler) throws Exception{ String rn = "\r\n"; String classStr = "package com.sf.simba.dynamicproxy.fly; " +rn+ "import com.sf.simba.dynamicproxy.fly.Flyable;" +rn+ "import com.sf.simba.dynamicproxy.fly.InvocationHandler;" +rn+ " public class TimeBirdProxy implements Flyable { " + rn+ " private InvocationHandler handler; " + rn+ " public TimeBirdProxy(InvocationHandler handler){ " + rn+ " super(); " + rn+ " this.handler = handler; " + rn+ " } " + rn+ " @Override " + rn+ " public void fly() { " + rn+ "try { "+rn+ " java.lang.reflect.Method md = Flyable.class.getMethod(\"fly\");"+rn+ " handler.invoke(this,md);"+rn+ "}catch(Exception e){" +rn+ " e.printStackTrace();" +rn+ "}"+rn+ " } " + rn+ "\n" + rn+ "}"; System.out.println("动态生成的代理类为:\n"+classStr); //定义动态文件生成到哪里叫什么名字 String fileName = System.getProperty("user.dir")+"/src/com/sf/simba/dynamicproxy/fly/TimeBirdProxy.java"; File file = new File(fileName); FileWriter fw = new FileWriter(file); //把字符串写入文件 fw.write(classStr); fw.flush(); fw.close(); //获得编译器 JavaCompiler complier = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = complier.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName); CompilationTask task = complier.getTask(null, fileManager, null, null, null, units); //编译,生成字节码 task.call(); fileManager.close(); URL[] url = new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src/")}; //加载字节码 URLClassLoader classloder = new URLClassLoader(url); Class<?> loadClass = classloder.loadClass("com.sf.simba.dynamicproxy.fly.TimeBirdProxy"); //获得代理对象构造方法 Constructor<?> constructor = loadClass.getConstructor(InvocationHandler.class); //生成代理对象实例 Object newInstance = constructor.newInstance(handler);//new Bird()为被代理对象 System.out.println("-------------------生成的对象类为:"+newInstance.getClass().getName()); return newInstance; } }
测试类如下:
package com.sf.simba.dynamicproxy.fly; /** * 测试类 * @author -Simba- * */ public class ProxyMain { public static void main(String[] args) throws Exception { Flyable bird = new Bird(); InvocationHandler handler = new TimeHandler(bird); Flyable timeproxy = (Flyable)Proxy.newProxyInstance(handler); timeproxy.fly(); } }生成的结果如下:
动态生成的代理类为:
package com.sf.simba.dynamicproxy.fly;
import com.sf.simba.dynamicproxy.fly.Flyable;
import com.sf.simba.dynamicproxy.fly.InvocationHandler;
public class TimeBirdProxy implements Flyable {
private InvocationHandler handler;
public TimeBirdProxy(InvocationHandler handler){
super();
this.handler = handler;
}
@Override
public void fly() {
try {
java.lang.reflect.Method md = Flyable.class.getMethod("fly");
handler.invoke(this,md);
}catch(Exception e){
e.printStackTrace();
}
}
}
-------------------生成的对象类为:com.sf.simba.dynamicproxy.fly.TimeBirdProxy
The bird is flying.......
TimeBirdProxy: The Flyable has flown 1824 millisecond
通过动态生成的代理类我们可以看出,在代理类的fly()方法中,并没有去执行Bird的fly()方法(持有的引用有Flyable改为了InvocationHandler),而是通过反射机制获取Flyable的fly对应的Method,把Method作为参数传给InvocationHandler的invoke方法,然后执行InvocationHandler的invoke方法,而在这个invoke方法又去掉Flyable的fly对应的反射的invoke方法,在调用这个invoke方法前已经先把Bird这个对象传入,并在调用之前或之后加入时间的计算。如果要生成日志代理对象,只有实现InvocationHandler的invoke方法,并在方法前或者后加上需要的操作。
但是这个还是不够完美,还是没有达到我们可以代理任何接口的动态代理,如果我们要实现代理任何接口的代理,那我们就要把Flyable的接口换成动态的就好。代码如下:
package com.sf.simba.dynamicproxy.fly; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; /** * 动态代理生成类 * @author -Simba- * */ public class Proxy { public static Object newProxyInstance(Class<?> infc,InvocationHandler handler) throws Exception{ String rn = "\r\n"; String methodStr = ""; //获得此接口的所有方法,即所有方法都要加代理 Method[] methods = infc.getMethods(); for(Method m : methods){ methodStr +="@Override"+rn+ "public "+m.getReturnType().getName()+" "+m.getName()+"(){"+rn+ "try { "+rn+ " java.lang.reflect.Method md = "+infc.getName()+".class.getMethod(\""+m.getName()+"\");"+rn+ " handler.invoke(this,md);"+rn+ "}catch(Exception e){" +rn+ " e.printStackTrace();" +rn+ "}"+rn+ "}"+rn; } String classStr = "package com.sf.simba.dynamicproxy.fly; " +rn+ "import com.sf.simba.dynamicproxy.fly.Flyable;" +rn+ "import com.sf.simba.dynamicproxy.fly.InvocationHandler;" +rn+ " public class TimeBirdProxy implements "+ infc.getName()+" { "+ rn + " private InvocationHandler handler;"+rn+ " public TimeBirdProxy(InvocationHandler handler){"+rn + " super();" +rn + " this.handler = handler;"+rn + " }"+rn + methodStr + rn + "}"; System.out.println("动态生成的代理类为:\n"+classStr); //定义动态文件生成到哪里叫什么名字 String fileName = System.getProperty("user.dir")+"/src/com/sf/simba/dynamicproxy/fly/TimeBirdProxy.java"; File file = new File(fileName); FileWriter fw = new FileWriter(file); //把字符串写入文件 fw.write(classStr); fw.flush(); fw.close(); //获得编译器 JavaCompiler complier = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = complier.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName); CompilationTask task = complier.getTask(null, fileManager, null, null, null, units); //编译,生成字节码 task.call(); fileManager.close(); URL[] url = new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src/")}; //加载字节码 URLClassLoader classloder = new URLClassLoader(url); Class<?> loadClass = classloder.loadClass("com.sf.simba.dynamicproxy.fly.TimeBirdProxy"); //获得代理对象构造方法 Constructor<?> constructor = loadClass.getConstructor(InvocationHandler.class); //生成代理对象实例 Object newInstance = constructor.newInstance(handler);//new Bird()为被代理对象 System.out.println("-------------------生成的对象类为:"+newInstance.getClass().getName()); return newInstance; } }
测试类:
package com.sf.simba.dynamicproxy.fly; /** * 测试类 * @author -Simba- * */ public class ProxyMain { public static void main(String[] args) throws Exception { Flyable bird = new Bird(); InvocationHandler handler = new TimeHandler(bird); Flyable timeproxy = (Flyable)Proxy.newProxyInstance(Flyable.class,handler); timeproxy.fly(); } }
生成结果如下:
动态生成的代理类为:
package com.sf.simba.dynamicproxy.fly;
import com.sf.simba.dynamicproxy.fly.Flyable;
import com.sf.simba.dynamicproxy.fly.InvocationHandler;
public class TimeBirdProxy implements com.sf.simba.dynamicproxy.fly.Flyable {
private InvocationHandler handler;
public TimeBirdProxy(InvocationHandler handler){
super();
this.handler = handler;
}
@Override
public void fly(){
try {
java.lang.reflect.Method md = com.sf.simba.dynamicproxy.fly.Flyable.class.getMethod("fly");
handler.invoke(this,md);
}catch(Exception e){
e.printStackTrace();
}
}
}
-------------------生成的对象类为:com.sf.simba.dynamicproxy.fly.TimeBirdProxy
The bird is flying.......
TimeBirdProxy: The Flyable has flown 1960 millisecond
至此,动态代理的模拟已经完成,而JDK下的动态代理比这个更加复杂,加上了参数和一个类实现多个接口的情况,并且JDK动态代理中是直接把java类生成二进制文件,而不是像本文中通过JavaComplier生成字节码然后在生成二进制加入到内存。