关闭

【动态代理】动态代理Proxy_04

标签: 设计模式动态代理ProxyProxy原理剖析
619人阅读 评论(0) 收藏 举报
分类:
我们继续上一篇总结。

上篇我们说到,怎么让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

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:989844次
    • 积分:17993
    • 等级:
    • 排名:第520名
    • 原创:815篇
    • 转载:18篇
    • 译文:0篇
    • 评论:473条
    关于我
    就职:聚项信息科技有限公司
    职位:中级Java开发工程师
    负责:上汽系统开发与维护
    院校:河南理工大学
    专业:软件工程12级
    邮箱:jackZhuCoder@126.com
    Q Q :10101000101001010111
    1101111010
    博客专栏
    最新评论