读书人

动态署理的动态编译实现

发布时间: 2012-12-21 12:03:49 作者: rapoo

动态代理的动态编译实现
人说,善于总结才会成长。回顾这个帐号闲置了快两年了,还是一片空白。工作了也有那么一段时间了,期间很多人给予过我帮助,我也帮助过很多人。由于喜欢研究稀奇古怪的玩意,所以也做了一些稀奇古怪的成果,共享出来,他或许没有什么实用价值但他也许能给你带去灵感。
接触过动态代理的且不说接触过Spring也应该接触过Proxy,那么Proxy里面又是怎么一回事呢。我不太喜欢Proxy的使用方式,感觉不直观,于是我打算自己来弄了。
首先对一个已有类的方法进行代理,我又不想去改动这个类让它实现什么接口,让它继承什么父类。那么我们先来写一个类,他有一个需要被代理的方法,为了增加一点难度这个方法带返回值和参数。

   package myproxy.realsub;public class RealSubject {   public String greeting(String name){      System.out.println("How are u? "+name);      return "Fine!";   }}

现在我们要对这个方法进行代理为这个方法前后加上其他的业务逻辑。
一前一后,所以我们暂且定为before()和after()吧。于是制定了一个标准接口
package myproxy.proxy.ii;public interface Interceptor {public void before();public void after();}

完美了,接下来我们只需要做一个代理类它只需要实现这两个方法,这两个方法就是要在greeting()方法执行前后执行的。
package myproxy.realsub;public class Interceptor implements myproxy.proxy.ii.Interceptor{long begin;long end;public void before() {System.out.println("On the street, a guy looks familiar.");begin=System.currentTimeMillis();}public void after() {end=System.currentTimeMillis();System.out.println("your boring conversation takes:"+(end-begin)+"ms");}}

好了,问题来了,或许我的RealSubject有很多方法,但我不希被全部代理,现在怎么办?这很有考虑的必要,实际当中我需要分别为不同的方法代理不同的实现,不是吗?OK,我想大家最容易想到的就是配置文件了,的确我就是被大量的配置文件整的头疼不已,难道我们没有更加直观的方法了吗?接触了一点spring觉得annotation就可以充当很不错的配置文件,于是我打算使用它来做配置。于是这么又有了思路。
package myproxy.realsub;import myproxy.proxy.annotation.CutPoint;import myproxy.proxy.annotation.SetProxy;@SetProxy("myproxy.realsub.Interceptor")public class RealSubject {@CutPointpublic String greeting(String name){System.out.println("How are u? "+name);return "Fine!";}public void hello(){System.out.println("hello");}}

SetProxy定义的就是我的代理实现类。CutPoint就是需要代理的标记。
这样就说明我的greeting方法需要被代理。
OK,我们在把这两个annotation的接口做出来。
CutPoint如下
package myproxy.proxy.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME) @Documentedpublic @interface CutPoint {}

SetProxy如下
package myproxy.proxy.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME) @Documentedpublic @interface SetProxy {String value();}

(关于annotation的知识在此就不做描述了,大家有什么不明白的就百度一下吧!)
OK,走到了这一步,我想大家累积的疑惑就越来越多了。
Interceptor的两个方法怎么注入到greeting前后呢?
下面我们就要实现这个重头戏功能了。
大家不妨想象一下构思一下,我要怎么做才能在程序运行到greeting的时候停下来转而执行我Interceptor的方法。我的思路是有这么一个类class $Proxy,它里面有两个对象既有RealSubject rs=new RealSubject(),又有Interceptor ic=new Interceptor(),然后$Proxy执行它自己的greeting方法,它的greeting方法里面就是这么调用的
ic.before();
rs.greeting();
ic.after();
OK,有了这个思路我们就得去实现它,这个$Proxy类不必真实存在,它完全是根据我们的需要才会出现的,所以我们得想法让它动态的诞生出来,这就意味着我们得动态创造一个类出来,天!你肯定会问,用程序写程序?Bingo!你回答对了,这就是我接下来要干的。JAVA promote API 里面提供了一个类JavaCompiler,它就是JAVA编译器,我们可以使用它去编译一段源码,那么现在我们的问题似乎就集中在怎么样去拼凑这段源码了,OK,有了这个思路我们就得去实现它。
一个类$Proxy,里面有两个对象,被代理对象和代理对象,一个和被代理方法同名同参数同返回值的方法(在此也就是greeting),还有什么问题?还有一个问题!就是这个类是动态拿到的假设我们通过一个工厂类成功的动态创造了这个$Proxy类,比如RealSubject rs=Factory.get("RealSubject");你能够去rs.greeting();这么调用吗?仔细想想,很显然不行,你怎么能把$Proxy类转化成RealSubject类呢?所以这个$Proxy类还需要继承RealSubject。这就是$Proxy的大致结构了,OK,有了这个思路我们就得去实现它!
这个ProxyFactory如下
package myproxy.proxy.proxy;import java.io.PrintWriter;import java.io.StringWriter;import java.lang.reflect.Method;import java.net.URI;import java.net.URL;import java.net.URLClassLoader;import java.util.Arrays;import javax.tools.Diagnostic;import javax.tools.DiagnosticCollector;import javax.tools.JavaCompiler;import javax.tools.JavaFileObject;import javax.tools.SimpleJavaFileObject;import javax.tools.ToolProvider;import javax.tools.JavaCompiler.CompilationTask;import myproxy.proxy.annotation.CutPoint;import myproxy.proxy.annotation.SetProxy;public class ProxyFactory {    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();    public Object getProxyInstance(String className) {Object forReturn=null;try {Class<?> clazz = Class.forName(className);String interceptor=null;String interceptor_interface=null;if(clazz.isAnnotationPresent(SetProxy.class)){SetProxy sp=clazz.getAnnotation(SetProxy.class);interceptor=sp.value();interceptor_interface=Class.forName(interceptor).getInterfaces()[0].getName();}else throw new RuntimeException("No interceptor class annotation found!");Method[] methods = clazz.getDeclaredMethods();StringWriter writer = new StringWriter();PrintWriter out = new PrintWriter(writer);String src=null;src="public class $Proxy extends "+className+" {"+"\r\n   "+className + " obj=new " + className + "();"+"\r\n   " +interceptor_interface+" ict = new "+interceptor+"();"+"\r\n";for (Method m : methods) {src += "   public " + m.getReturnType().getName() + " " + m.getName() + "(";int paraslength=m.getParameterTypes().length;for (int i = 0; i < paraslength; i++) {src += m.getParameterTypes()[i].getName() + " arg" + i + ",";}if(src.lastIndexOf(",")==src.length()-1)src = src.substring(0, src.length() - 1);src += "){"+"\r\n";if (m.isAnnotationPresent(CutPoint.class))src += "      ict.before();"+"\r\n";if(!m.getReturnType().getName().equals("void"))src += "      "+m.getReturnType().getName()+" re = obj."+m.getName()+"(";elsesrc +="      obj."+m.getName()+"(";for(int i=0;i<paraslength;i++){src+="arg"+i+",";}if(src.lastIndexOf(",")==src.length()-1)src=src.substring(0,src.length()-1);src+=");"+"\r\n";if (m.isAnnotationPresent(CutPoint.class))src+="      ict.after();"+"\r\n";if(!m.getReturnType().getName().equals("void"))src+="      return re;"+"\r\n";src+="\r\n"+"   }"+"\r\n";}src+="}";out.println(src);out.close();    JavaFileObject file = new JavaSourceFromString("$Proxy", writer.toString());    Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);    CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);        boolean success = task.call();    for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {      System.out.println(diagnostic.getCode());      System.out.println(diagnostic.getKind());      System.out.println(diagnostic.getPosition());      System.out.println(diagnostic.getStartPosition());      System.out.println(diagnostic.getEndPosition());      System.out.println(diagnostic.getSource());      System.out.println(diagnostic.getMessage(null));    }    if (success) {      try {URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/")};URLClassLoader ul=new URLClassLoader(urls);Class<?> cla=ul.loadClass("$Proxy");forReturn=cla.newInstance();      } catch ( Exception e) {        e.printStackTrace();      }    }} catch (Exception e) {e.printStackTrace();}return forReturn;}}class JavaSourceFromString extends SimpleJavaFileObject {final String code;JavaSourceFromString(String name, String code) {super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);this.code = code;}public CharSequence getCharContent(boolean ignoreEncodingErrors) {return code;}}

最主要的是那个src,也就是我们拼凑的$Proxy类的源码!好好去体会一下吧。关于JAVA编译器的内容在此不讲!
package myproxy.test;import myproxy.proxy.proxy.ProxyFactory;import myproxy.realsub.RealSubject;public class Test {public static void main(String[] args) {//RealSubject rs = new RealSubject();RealSubject rsproxy = (RealSubject)new ProxyFactory().getProxyInstance("myproxy.realsub.RealSubject");//rs.greeting("billy");String res=rsproxy.greeting("Billy");System.out.println(res);//rsproxy.hello();}}

看看效果吧

On the street, a guy looks familiar.
How are u? Billy
your boring conversation takes:0ms
Fine!


总结:怎么样是不是比Proxy的使用更加直观,你不用去实现什么handler,你的本身的那个类也不用去实现接口,在使用过程中你只需要写一个代理类,before,after两个方法。通过annotation指定被代理方法。是不是非常直观,简便,初学者也可以去使用它。

读书人网 >编程

热点推荐