【动态代理】动态代理Proxy_04

我们继续上一篇总结。

上篇我们说到,怎么让before()和after()中的内容也让客户灵活指定?

不管怎么样,我们现在需要一个这样的东西:可以动态指定对方法进行处理的指令。

我们创建一个方法调用的处理器,用来对任意方法进行自定义的处理:

package cn.edu.hpu.proxy;

import java.lang.reflect.Method;

//方法调用的处理器
public interface InvocationHandler {
	//你只要给我一个Method方法,我就能对Method进行处理
	//做处理的方式是由实现它的子类来决定
	//参数:Object o,Method m,即invoke()内要调用对象Object o的Method m方法
	public void invoke(Object o,Method m);
}

我们再写一个时间方法的处理类,实现InvocationHandler接口:

package cn.edu.hpu.proxy;

import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler{

	private Object target;

	public TimeHandler(Object target) {
		super();
		this.target=target;
	}

	//参数是一个方法,我们可以在invoke里在Method方法执行的前后加处理逻辑
	@Override
	public void invoke(Object o,Method m){
		long start=System.currentTimeMillis();
		System.out.println("开始时间:"+start+"ms");
		try {
			m.invoke(target);//执行target类的m方法
		} catch (Exception e) {
			e.printStackTrace();
		}
		long end=System.currentTimeMillis();
		System.out.println("运行时间:"+(end-start)+"ms");
	}

}

这里我们写了一个时间处理的方法调用的处理器

下面就来看我们之前的那段动态代码如何生成。先回顾一下之前的Proxy代码:

package cn.edu.hpu.proxy;

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.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;

public class Proxy {

	//这个方法用来产生新的代理类
	//参数:告诉程序使用实现哪个接口的动态代理
	public static Object newProxyInstance(Class infce) throws Exception{
		String methodStr="";
		String rt="\r\n";

		//使用了Java的反射机制中的getMethods();方法来得到一个类的所有方法
		Method[] methods=infce.getMethods();
		for (Method m:methods) {
			methodStr += rt+"    @Override"+rt+
				"    public void "+m.getName()+"() {"+rt+
				"        this.before();"+ rt +
				"	     t."+m.getName()+"();"+ rt +
				"	     this.after();"+ rt +
				"    }"; //返回值通过反射机制也可以拿到,但是比较麻烦,我们这里暂时用void代替
		}

		String src=
			"package cn.edu.hpu.proxy;"+ rt +

		"public class TankTimeProxy implements "+infce.getName()+"{"+ rt +
		"    Moveable t;"+ rt +
		"    long start;"+ rt +
		"    long end;"+ rt +

		"    public TankTimeProxy(Moveable t) {"+ rt +
			"        super();"+ rt +
			"        this.t = t;"+ rt +
		"    }"+ rt +

		"    public void before(){"+ rt +
			"        start=System.currentTimeMillis();"+ rt +
			"        System.out.println(\"开始时间:\"+start+\"ms\");"+ rt +
		"    }"+

		"    public void after(){"+ rt +
			"        end=System.currentTimeMillis();"+ rt +
			"        System.out.println(\"运行时间:\"+(end-start)+\"ms\");"+ rt +
		"    }"+ rt +

		methodStr+ rt +

		"}";

		//拿到当前项目的根目录:System.getProperty("user.dir"));
		String fileName=System.getProperty("user.dir")
						+"/src/cn/edu/hpu/proxy/TankTimeProxy.java";

		//我们把src的源码写入自己创建的File文件中去
		File f=new File(fileName);
		FileWriter fw=new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();

		//编译(getSystemJavaCompiler()拿到系统默认的Java编译器,其实就是javac)
		JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();
		//需要一个FileManager,用它来管理文件(参数1:诊断的监听器,参数2、3国际化)
		StandardJavaFileManager fileMgr=
			compiler.getStandardFileManager(null, null, null);
		//通过FileManager找到TankTimeProxy文件,然后放到一个Iterable中
		//Iterable是一个数组,用它可以进行迭代
		Iterable units=fileMgr.getJavaFileObjects(fileName);
		//参数:(输出位置,文件管理器对象,监听器,编译的时候指定的参数,用到那些class文件,需要编译哪些文件)
		CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units);
		//进行编译
		t.call();
		fileMgr.close();

		//把编译好的.class文件加载到内存中
		//urls指定.class文件所放的地方(使用URL还可以Load从网上传过来的类)
		URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};
		//ClassLoader指的是吧硬盘里的Java文件放到内存中的那些个类
		URLClassLoader ul=new URLClassLoader(urls);
		Class c=ul.loadClass("cn.edu.hpu.proxy.TankTimeProxy");
		System.out.println(c);

		//得到TankTimeProxy类的构造方法,构造方法的参数为Moveable类型
		Constructor ctr=c.getConstructor(Moveable.class);
		Object m=ctr.newInstance(new Tank());
		return m;

	}
}

这一块我们就可以通过实现InvocationHandler接口来完成:

package cn.edu.hpu.proxy;

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.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;

public class Proxy {

	//这个方法用来产生新的代理类
	//参数:告诉程序使用实现哪个接口的动态代理
	public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{
		String methodStr="";
		String rt="\r\n";

		//使用了Java的反射机制中的getMethods();方法来得到一个类的所有方法
		Method[] methods=infce.getMethods();
		for (Method m:methods) {
			methodStr += rt+"    @Override"+rt+
				"    public void "+m.getName()+"() {"+rt+
				"		 Method md=null;"+rt+
				"		 try{"+rt+
				"	     md="+infce.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
				"	     h.invoke(this,md);"+rt+
				"		 }catch(Exception e){"+rt+
				"        e.printStackTrace();}"+rt+
				"    }";
		}

		String src=
			"package cn.edu.hpu.proxy;"+ rt +
			"import java.lang.reflect.Method;"+ rt +

		"public class TankTimeProxy implements "+infce.getName()+"{"+ rt +
		"    cn.edu.hpu.proxy.InvocationHandler h;"+ rt +

		"    public TankTimeProxy(InvocationHandler h) {"+ rt +
			"        super();"+ rt +
			"        this.h = h;"+ rt +
		"    }"+ rt +

		methodStr+ rt +

		"}";

		//拿到当前项目的根目录:System.getProperty("user.dir"));
		String fileName=System.getProperty("user.dir")
						+"/src/cn/edu/hpu/proxy/TankTimeProxy.java";

		//我们把src的源码写入自己创建的File文件中去
		File f=new File(fileName);
		FileWriter fw=new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();

		//编译(getSystemJavaCompiler()拿到系统默认的Java编译器,其实就是javac)
		JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();
		//需要一个FileManager,用它来管理文件(参数1:诊断的监听器,参数2、3国际化)
		StandardJavaFileManager fileMgr=
			compiler.getStandardFileManager(null, null, null);
		//通过FileManager找到TankTimeProxy文件,然后放到一个Iterable中
		//Iterable是一个数组,用它可以进行迭代
		Iterable units=fileMgr.getJavaFileObjects(fileName);
		//参数:(输出位置,文件管理器对象,监听器,编译的时候指定的参数,用到那些class文件,需要编译哪些文件)
		CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units);
		//进行编译
		t.call();
		fileMgr.close();

		//把编译好的.class文件加载到内存中
		//urls指定.class文件所放的地方(使用URL还可以Load从网上传过来的类)
		URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};
		//ClassLoader指的是吧硬盘里的Java文件放到内存中的那些个类
		URLClassLoader ul=new URLClassLoader(urls);
		Class c=ul.loadClass("cn.edu.hpu.proxy.TankTimeProxy");
		System.out.println(c);

		//得到TankTimeProxy类的构造方法,构造方法的参数为Moveable类型
		Constructor ctr=c.getConstructor(InvocationHandler.class);
		Object m=ctr.newInstance(h);//之前的Tank()改为InvocationHandler

		return m;

	}
}

测试:

package cn.edu.hpu.proxy;

public class Client {
	public static void main(String[] args) throws Exception {
		Tank t=new Tank();
		InvocationHandler h=new TimeHandler(t);
		Moveable m=(Moveable)Proxy.newProxyInstance(Moveable.class,h);
		m.move();
	}
}

生成的TankTimeProxy类:

package cn.edu.hpu.proxy;
import java.lang.reflect.Method;
public class TankTimeProxy implements cn.edu.hpu.proxy.Moveable{
    cn.edu.hpu.proxy.InvocationHandler h;
    public TankTimeProxy(InvocationHandler h) {
        super();
        this.h = h;
    }

    @Override
    public void move() {
		 Method md=null;
		 try{
	     md=cn.edu.hpu.proxy.Moveable.class.getMethod("move");
	     h.invoke(this,md);
		 }catch(Exception e){
        e.printStackTrace();}
    }
}

测试结果:
class cn.edu.hpu.proxy.TankTimeProxy
开始时间:1436446021225ms
坦克正在移动中...
运行时间:8014ms

可能大家有点晕,看看运行逻辑图:

其实我们的TankTimeProxy就是JDK动态代理中的$Proxy1类

为了更贴近JDK的动态代理,我们吧原来的TankTimeProxy名字改为$proxy1,即生成的你看不见的加了前后逻辑的动态代理类。

package cn.edu.hpu.proxy;

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.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;

public class Proxy {

	//这个方法用来产生新的代理类
	//参数:告诉程序使用实现哪个接口的动态代理
	public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{
		String methodStr="";
		String rt="\r\n";

		//使用了Java的反射机制中的getMethods();方法来得到一个类的所有方法
		Method[] methods=infce.getMethods();
		for (Method m:methods) {
			methodStr += rt+"    @Override"+rt+
				"    public void "+m.getName()+"() {"+rt+
				"		 Method md=null;"+rt+
				"		 try{"+rt+
				"	     md="+infce.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
				"	     h.invoke(this,md);"+rt+
				"		 }catch(Exception e){"+rt+
				"        e.printStackTrace();}"+rt+
				"    }";
		}

		String src=
			"package cn.edu.hpu.proxy;"+ rt +
			"import java.lang.reflect.Method;"+ rt +

		"public class $Proxy1 implements "+infce.getName()+"{"+ rt +
		"    cn.edu.hpu.proxy.InvocationHandler h;"+ rt +

		"    public $Proxy1(InvocationHandler h) {"+ rt +
			"        super();"+ rt +
			"        this.h = h;"+ rt +
		"    }"+ rt +

		methodStr+ rt +

		"}";

		//拿到当前项目的根目录:System.getProperty("user.dir"));
		String fileName=System.getProperty("user.dir")
						+"/src/cn/edu/hpu/proxy/$Proxy1.java";

		//我们把src的源码写入自己创建的File文件中去
		File f=new File(fileName);
		FileWriter fw=new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();

		//编译(getSystemJavaCompiler()拿到系统默认的Java编译器,其实就是javac)
		JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();
		//需要一个FileManager,用它来管理文件(参数1:诊断的监听器,参数2、3国际化)
		StandardJavaFileManager fileMgr=
			compiler.getStandardFileManager(null, null, null);
		//通过FileManager找到TankTimeProxy文件,然后放到一个Iterable中
		//Iterable是一个数组,用它可以进行迭代
		Iterable units=fileMgr.getJavaFileObjects(fileName);
		//参数:(输出位置,文件管理器对象,监听器,编译的时候指定的参数,用到那些class文件,需要编译哪些文件)
		CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units);
		//进行编译
		t.call();
		fileMgr.close();

		//把编译好的.class文件加载到内存中
		//urls指定.class文件所放的地方(使用URL还可以Load从网上传过来的类)
		URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};
		//ClassLoader指的是吧硬盘里的Java文件放到内存中的那些个类
		URLClassLoader ul=new URLClassLoader(urls);
		Class c=ul.loadClass("cn.edu.hpu.proxy.$Proxy1");
		System.out.println(c);

		//得到TankTimeProxy类的构造方法,构造方法的参数为Moveable类型
		Constructor ctr=c.getConstructor(InvocationHandler.class);
		Object m=ctr.newInstance(h);
		return m;

	}
}

我们这么折腾半天,完成了什么工作呢?
那就是:可以对任意的对象、任意的方法,实现任意的代理。

我们下面来用一下我们自己写的Proxy,看看它是如何方便的:
首先我们写一个接口和接口的实现:

UserMgr.java:
package cn.edu.hpu.ProxyTest;

public interface UserMgr {
	void addUser();
}

UserMgrImpl.java:

package cn.edu.hpu.ProxyTest;

public class UserMgrImpl implements UserMgr{

	@Override
	public void addUser() {
		System.out.println("1:插入记录到user表");
		System.out.println("2:做日志在另外一张表");
	}

}

类似于JavaEE的东西,先不管,我们在addUser()执行了2个操作(由于没有连数据库,直接打印相当于操作了),现在,我们想控制这两个操作(1和2操作)是否同时完成,这叫控制"transaction"(或理解为方法前后做日志)。

原来需要在代码前后加代码,现在我们这么做:
创建一个新的类,叫TransactionHandler,让他去实现我们自己的InvocationHandler

package cn.edu.hpu.ProxyTest;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import cn.edu.hpu.proxy.InvocationHandler;

public class TransactionHandler implements InvocationHandler{

	private Object target;

	public TransactionHandler(Object target){
		super();
		this.target=target;
	}

	@Override
	public void invoke(Object o, Method m) {
		System.out.println("Transaction Start");
		try {
			m.invoke(target);
		}catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("Transaction Commit");
	}

}

我们实现了一个Transaction的处理器。

测试:

package cn.edu.hpu.ProxyTest;

import cn.edu.hpu.proxy.InvocationHandler;
import cn.edu.hpu.proxy.Proxy;

public class Client {
	public static void main(String[] args) throws Exception {
		UserMgr userMgr=new UserMgrImpl();
		InvocationHandler h=new TransactionHandler(userMgr);
		UserMgr u=(UserMgr)Proxy.newProxyInstance(UserMgr.class, h);
		u.addUser();
	}
}

测试结果:
Transaction Start
1:插入记录到user表
2:做日志在另外一张表
Transaction Commit

我两个代理之间也是可以叠加的,可以相互替换的,而且是可插拔的,什么时候不想用哪个代理了,就在配置文件中去掉就行了。这就是动态代理的巨大作用。这就是为什么Spring可以去管理AOP,去管理Transaction,还可以管理其他各种各样的内容。

我们与JDK的动态代理比较一下,JDK的动态代理由两个类构成,第一个叫Proxy:
常用方法:
newProxyInstance(ClassLoader loder,Class<?>[] interfaces,nvocationHandler h)
第一个参数指明需要用哪种ClassLoader把代理类的对象load到内存中。我们自己写的Proxy用
的是URLClassLoad。后面两个参数和我们自己写的newProxyInstance一样。

再看JDK的InvocationHandler:
是一个接口,里面有一个方法:
invoke(Object proxy,Method method,Object[] args)
前两个参数和我们自己写的InvocationHandler一样,最后一个参数指的是调用Method方法时所需要的参数。

动态代理还有一个好处,就是当一个被代理类中有许多的方法需要加前后逻辑,我们仍然只需要按照刚刚的步奏先创建被代理类和InvocationHandler,然后作为参数传进Proxy.newProxyInstance();中即可了。如果按照第一篇总结我们写的那个聚合的,要自己亲手加代码。

动态代理有什么用?事务处理,权限控制,AOP,安全,日志,可以在不该变代码的情况下插入其他功能。

至此,动态代理的设计模式已经总结完毕,大家可以自己动手操作一下,与JDK自己的动态代理作比较,思路会更加清晰。同时感谢马士兵老师的视频教程。

转载请注明出处:http://blog.csdn.net/acmman/article/details/46828251

时间: 2024-07-30 13:07:04

【动态代理】动态代理Proxy_04的相关文章

java 静态代理 动态代理深入学习_java

一.代理模式 代理模式是常用的java设计模式,特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等. 代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务. 按照代理的创建时期,代理类可以分为两种: 静态代理:由程序员创建或特定工具自动生成源代码再对其编译.在程序运行前代理类的.class文件就已经存在了. 动态代理:在

SilverLight企业应用框架设计【四】实体层设计+为客户端动态生成服务代理(自己实现RiaService)

题外话: 对不住各位,本打算年前把这个系列写完,结果由于杂务缠身一直推到年后 我特别痛恨我自己!我觉得不但对不起各位!也对不起自己. 最近烦躁不安,不能专心向学.也不知道如何是好. -- 好吧,言归正传 说个前提条件: 此项目虽然使用了silverlight 4.0 但是服务端只能在dotNet3.5下运行 这也是我们为什么自己实现riaService的原因 实体层设计 由于有这个限制条件,我们设计的实体层也有所区别 如下图为实体层的程序集(只有MenuM实体类,其他实体类未加入.) 下面来看一

动态字段名-linq 字段名动态改变 动态添加数据

问题描述 linq 字段名动态改变 动态添加数据 我遇到的问题是: 我要添加的一张表的字段名是动态的,也就是说A网页调用A数据表,B网页调用B数据表.我现在希望写一个基类,来完成这两个表的添加数据操作,而不是采用 表名A.字段名a = 值; 表名A.字段名b = 值; 表名B.字段名c = 值; 表名B.字段名d = 值;的方式进行赋值.我希望的格式为: 表名(是个变量).字段名(是个变量)= 值.谢谢! 解决方案 http://www.cnblogs.com/gmtyt/archive/201

asp.net开发类似于qq空间中我的动态,好友动态 的数据库设计

问题描述 asp.net开发类似于qq空间中我的动态,好友动态的数据库设计,比如好友动态中,有好友发表了心情,日志,分享了某某日志,照片,转载了某某日志,心情,相片,好友与某某成为好友关系,好友参加某某活动等 解决方案 解决方案二:该回复于2011-11-07 10:22:51被版主删除解决方案三:首先用户产生了动态,放到动态表feed里,里面有动态的产生者,动态的内容,动态的APPID等.同时,把这条动态的ID放入队列queue里,也就是我们常说的消息队列,队列可以是memory表,可以是me

YII动态模型(动态表名)支持分析_php实例

本文分析了YII动态模型(动态表名)支持机制.分享给大家供大家参考,具体如下: 给YII 框架增加动态模型支持 Yii框架中的数据模型使用静态机制,如果要使用模型方式操作某张数据表,就必须得事先创建数据表对应的模型类(位于 protected/models 目录下),这种方式,在有的情况下给我们的工作带来了一些不便,如仅仅将数据表进行显示,或者数据表是动态生成的,或者要实现数据表模型中的读写分离,(如数据写入与数据呈现逻辑可能定义到不同的模型中,以提高性能,如前后台的分离). 为解决这个问题,经

jQuery的动态表格动态表单

这个例子就是通过jquery对dom进行操作,达到动态的实现HTML变换的问题. 动态表格动态表单中的Jquery代码 <script type="text/javascript" src="/include/jquery/jquery-1.1.3.1.pack.js"></script> <script language="javascript"> $("#addjobline").css

java 代理模式(静态代理+动态代理)

静态代理: ISubject: /** * @author com.tiantian * @version 创建时间:2012-11-20 下午1:49:29 */ public interface ISubject { public void request(); } RealSubject(真实角色): /** * @author com.tiantian * @version 创建时间:2012-11-20 下午1:51:37 */ public class RealSubject imp

Merlin的魔力: 动态事件监听器代理

所有 Swing 组件都是 JavaBeans 组件.它们有一系列的 setter 和 getter 方法,这些方法的类似于 void setXXX(类型名) 和 Type getXXX() .关于这些方法没有什么特别之处,并且正如所预期的,它们遵循 JavaBeans 的属性命名规范.我们今天要讨论的是JavaBeans 组件的一个方面,即一对监听器方法 addXXXListener (XXXListener name) 和 removeXXXListener (XXXListener nam

ASP.NET实现对相似页面的后台代码的抽象及动态GridView动态列数据绑定

以下通过一个ASP.NET的Demo,希望能使您加深对多态的理解. 现在的需求是这样子(当然该需求 是借助于最近的项目中碰到的问题),在该系统中的流程管理中,有两个页面,一个显示的是我本人发起的审 批列表,另一个是等待我进行审批的列表,他们的查询以及列表显示和查看审批历史等均一致,唯一不同的是 待审批还有一个可执行审批动作的一列,但是不同的人或者在不同的应用(我在这里假设该系统是有多个应用 的复杂系统)里面获取到列表的列是不一样的,所以需要使用动态列,当然这些数据来源我在这里不赘述,为 便于举例

mysql c# 代理-使用代理上网情况下c#如何连接到外网mysql进行查询

问题描述 使用代理上网情况下c#如何连接到外网mysql进行查询 开发环境:vs2010语言:c# 网络环境:使用代理上网由于公司设置不能直接上网,只能通过设置代理进行访问网络.请问c#中如何写mysql连接语句