`
nikoloss
  • 浏览: 32916 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

动态代理的动态编译实现

阅读更多
    人说,善于总结才会成长。回顾这个帐号闲置了快两年了,还是一片空白。工作了也有那么一段时间了,期间很多人给予过我帮助,我也帮助过很多人。由于喜欢研究稀奇古怪的玩意,所以也做了一些稀奇古怪的成果,共享出来,他或许没有什么实用价值但他也许能给你带去灵感。
    接触过动态代理的且不说接触过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 {
	@CutPoint
	public 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) 
@Documented
public @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) 
@Documented
public @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()+"(";
				else
					src +="      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指定被代理方法。是不是非常直观,简便,初学者也可以去使用它。
分享到:
评论

相关推荐

    java静态代理和动态代理详解

    Java中提供了一个java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现动态代理。代理类在运行时会根据被代理接口自动生成,并且可以通过InvocationHandler接口对方法进行增强。

    Java实现的反向代理程序(编译版)

    使用Java编写的反向代理程序,通过简单的参数配置即可实现某些特定站点的反向代理,并在此过程中改变一些站点的特定行为。例如:允许特点站点跨域访问被代理的站点,或者屏蔽被代理站点识别请求访问客户端的类型...

    jdk动态代理模式的分析与底层实现

    基于java的jdk动态代理, 比较了静态代理与动态代理的区别,以及动态代理的底层实现,反编译class文件 jdk动态代理和cglib的区别

    cgLib与JDK动态代理的用法

    代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 按照代理的创建时期,代理类可以...

    你必须会的 JDK 动态代理和 CGLIB 动态代理

    静态代理就是在程序运行之前,代理类字节码.class就已编译好,通常一个静态代理类也只代理一个目标类,代理类和目标类都实现相同的接口。 接下来就先通过 demo 进行分析什么是静态代理,当前创建一个 Animal 接口,...

    交叉编译和交叉调试工具的研究与实现

    之后阐述了GDB交叉调试器和远程串行协议并引入了调试代理的概念,分别研究、分析了两种调试代理的技术实现途径和方法。最后,详细的说明了如何 将交叉编译工具和交叉调试工具移植到Windows平台上,设计并实现了两种...

    一种基于原始接口编译远程调用代理存根生成器的研究与实现.pdf

    一种基于原始接口编译远程调用代理存根生成器的研究与实现.pdf

    live555流媒体 动态端口 转发代理

    在别人编译的 live.2013.11.15_VS2012 代码基础上修改代理服务那模块,将代理端口参数化,实现 流媒体代理固定端口为 554或8554的限制。

    Java实现的反向代理程序说明文档

    该资源主要包含“Java编写的反向代理程序”相关的使用说明文档和演示文档【通过简单的参数配置即可实现某些特定站点的反向代理,并在此过程中改变一些站点的特定行为。】 编译版资源下载地址:...

    Spring3注解

    AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为代表;而动态代理则以 Spring AOP 为代表。其中静态代理是指使用 AOP 框架提供的命令进行编译...

    跨语言IP代理池,Python实现

    跨语言高性能IP代理池,Python实现。 注意:请运行程序前先更新一下抓取代理的爬虫 运行环境 Python 3.6 (请务必保证Python的版本在3.6以上,否则异步检验无法使用。) Redis Redis官网并没有提供Windows的安装版...

    用VB来实现快速修改IE代理服务器的源码

    VB 修改 代理 服务器地址 端口 IE ...用VB来实现代理服务器的源码 快速设置或修改IE代理服务器地址和端口 设置代理服务器、修改代理服务器 在windows 7和vb 5.0上运行通过,里面含有编译后的exe运行程序

    Java实现的反向代理程序(使用到的JAR包)

    该资源是“Java实现的反向代理程序(编译版)”使用到的一些公共JAR包,主要包含“commons-text-1.4.jar”和“javax.servlet_1.0.0.0_2-5.jar”这两个JAR包,共有兴趣的同道人下载。

    AOP(面向切面)的C#例子VS2012

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,...

    C# .Net动态调用webService实现思路及代码

    动态调用web服务将执行以下步骤:获取WSDL/生成客户端代理类代码/设定编译参数/编译代理类/生成代理实例,并调用方法,很详细的,感兴趣的你可不要错过了哈

    代理服务器源码

    代理服务器源码,实现了http代理,多线程,编译后可直接运行,无需安装

    一个VB代理服务器及可用代理检测程序.rar

    一个VB代理服务器及可用代理检测程序,自带有代理服务器地址检测模块,可根据用户提供和系统默认的代理服务器地址,检查找到可用的代理地址,程序界面设计美观,代码开源,编译顺利。程序界面设计基于PNG贴图原理...

    利用C#实现AOP常见的几种方法详解

    AOP面向切面编程(Aspect Oriented Programming),是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。下面这篇文章主要给大家介绍了关于利用C#实现AOP常见的几种方法,需要的朋友可以参考借鉴,...

    基础深化和提高-java反射技术

    动态代理:通过反射可以创建动态代理对象,用于实现特定的代理逻辑。 框架和工具:在很多框架和工具中,如Spring框架、JUnit测试框架等,都广泛使用了反射技术,用于实现灵活的配置和扩展。 虽然反射技术提供了很...

    一款实现了消息推送协议 MQTT v3.1 的开源消息代理软件,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之

    一款实现了消息推送协议 MQTT v3.1 的开源消息代理软件,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信变得简单,比如现在应用广泛的低功耗传感器,手机、嵌入式计算机、微型控制...

Global site tag (gtag.js) - Google Analytics