菜鸟笔记
提升您的技术认知

java设计模式-ag真人游戏

阅读 : 539

一、什么是动态代理

在静态代理(static proxy)模式中,代理类都是真实存在的,由程序员提前创建好的java类,是静态的,每一个代理类在编译之后都会生成一个.class字节码文件,静态代理类所实现的接口和所代理的方法早在编译期都已被固定了。

动态代理(dynamic proxy)则不同:动态代理使用字节码动态生成和加载技术,在系统运行时动态地生成和加载代理类。

与静态代理相比,动态代理有以下优点:首先,无需再为每一个真实主题写一个形式上完全一样的代理类,假如抽象主题接口中的方法很多的话,为每一个接口方法写一个代理方法也很麻烦,同样地,如果后期抽象主题接口发生变动,则真实主题和代理类都要修改,不利于系统维护;其次,动态代理可以让系统根据实际需要来动态创建代理类,同一个代理类能够代理多个不同的真实主题类,并且可以代理多个不同的方法。

从jdk 1.3版本开始,java语言提供了对动态代理的支持,在java中实现动态代理机制,需要用到 java.lang.reflect 包中的 invocationhandler 接口和 proxy 类,我们先来看看java的api帮助文档是怎么样对这两个类进行描述的:

invocationhandler 

invocationhandler is the interface implemented by the invocation handler of a proxy instance. 
each proxy instance has an associated invocation handler. when a method is invoked on a proxy instance, the 
method invocation is encoded and dispatched to the invoke method of its invocation handler.
invocationhandler 是代理实例的调用处理程序实现的接口。
每个代理实例都具有一个与之关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程
序的 invoke() 方法。

invoke() 方法形式如下:  

object invoke(object proxy,method method,object[] args) throws throwable

invocationhandler 接口只包含invoke()这唯一一个方法,该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。invoke()方法包含三个参数,其中第一个参数proxy表示代理类的实例,第二个参数method表示需要代理的方法,第三个参数args表示代理方法的参数数组。 

proxy 

proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass 
of all dynamic proxy classes created by those methods. 
proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

proxy提供给我们的静态方法有以下四个:  

//返回指定代理实例的调用处理程序。
static invocationhandler getinvocationhandler(object proxy) 
//返回代理类的 java.lang.class 对象,并向其提供类加载器和接口数组。
static class	getproxyclass(classloader loader, class[] interfaces) 
//当且仅当指定的类通过 getproxyclass 方法或 newproxyinstance 方法动态生成为代理类时,返回 true。
static boolean	isproxyclass(class cl) 
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
//方法中的 classloader loader 参数用来指定动态代理类的类加载器,class[] interfaces用来指定动态代理类要实现的接口。
//invocationhandler h 用来指定与即将生成的动态代理对象相关联的调用处理程序
static object newproxyinstance(classloader loader, class[] interfaces, invocationhandler h) 

下面我们以为数据库增加日志记录功能(为简单起见,我们仅记录下所有操作的执行时间及操作执行的结果)为例来看一看如何使用这两个类实现动态代理:

为了使演示更清晰,在此先定义两个简单类,一个user类和一个document类分别表示数据库中的用户记录和文档记录,其代码如下。  

public class user {
    // 用户在数据库中的id
    private long id;
    // 用户的姓名
    private string name;
    public user(long id, string name) {
        super();
        this.id = id;
        this.name = name;
    }
    public long getid() {
        return id;
    }
    public void setid(long id) {
        this.id = id;
    }
    public string getname() {
        return name;
    }
    public void setname(string name) {
        this.name = name;
    }
}
public class document {
    // 文档在数据库中的id
    private long id;
    // 文档的标题
    private string title;
    public document(long id, string title) {
        super();
        this.id = id;
        this.title = title;
    }
    public long getid() {
        return id;
    }
    public void setid(long id) {
        this.id = id;
    }
    public string gettitle() {
        return title;
    }
    public void settitle(string title) {
        this.title = title;
    }
}

另外还需定义一个database类用来扮演数据库的功能,在此为简单起见,将数据库中的用户记录和文档记录分别存储在一个map中,代码如下。 

import java.util.hashmap;
import java.util.map;
public class database {
	private static map usermap = null;
	private static map documentmap = null;
	// 用来记录当前登陆用户信息
	private static user currentuser = null;
	private database() {
		// 数据初始化,为数据库中增加几条用户记录。。。
		usermap = new hashmap();
		usermap.put(20160708l, new user(20160708l, "燕凌娇"));
		usermap.put(20160709l, new user(20160709l, "姬如雪"));
		usermap.put(20160710l, new user(20160710l, "百里登风"));
		// 数据初始化,为数据库中增加几条文档记录。。。
		documentmap = new hashmap();
		documentmap.put(30160708l, new document(30160708l, "c  常用算法手册"));
		documentmap.put(30160709l, new document(30160709l, "深入理解android内核设计思想"));
		documentmap.put(30160710l, new document(30160710l, "java从入门到放弃"));
	}
	public user getcurrentuser() {
		return currentuser;
	}
	public void setcurrentuser(user currentuser) {
		database.currentuser = currentuser;
	}
	public map getusermap() {
		return usermap;
	}
	public map getdocumentmap() {
		return documentmap;
	}
	public static database getdatabaseinstance() {
		return databaseholder.database;
	}
	public static class databaseholder {
		private static database database = new database();
	}
}

数据库布置完成了,接下来开始写动态代理相关代码了,为了与静态代理进行比较,在此我们来创建两个接口(抽象主题角色)。 

public interface userdao {
    // 登陆数据库,为了演示方便将字符串作为执行结果返回
    string login(long id);
    // 退出数据库,为了演示方便将字符串作为执行结果返回
    string logout();
}
public interface documentdao {
    // 新增文档,为了演示方便将字符串作为执行结果返回
    string add(document document);
    // 删除文档,为了演示方便将字符串作为执行结果返回
    string delete(document document);
}

接下来是两个真实主题角色,impuserdao 类和impdocumentdao类,为了使示例结果清晰,此处将接口的执行结果直接以字符串形式返回。

public class impuserdao implements userdao {
	@override
	public string login(long id) {
		user user = database.getdatabaseinstance().getusermap().get(id);
		if (null != user) {
			// 数据库有此用户的信息,则允许登陆...
			database.getdatabaseinstance().setcurrentuser(user);
			return "用户["   user.getname()   "]登陆成功...";
		} else {
			// 数据库没有此用户信息,则不让登陆...
			return "登陆失败,id为\""   id   "\"的用户不存在!";
		}
	}
	@override
	public string logout() {
		user user = database.getdatabaseinstance().getcurrentuser();
		if (null != user) {
			// 若当前有用户登陆,则退出成功...
			database.getdatabaseinstance().setcurrentuser(null);
			return "用户["   user.getname()   "]退出登陆成功...";
		} else {
			// 若当前无用户登陆,则退出失败...
			return "退出登陆失败,当前无登陆用户!";
		}
	}
}
public class impdocumentdao implements documentdao {
	@override
	public string add(document document) {
		user user = database.getdatabaseinstance().getcurrentuser();
		if (null == user) {
			// 若当前用户未登陆,则新增文档失败...
			return "保存失败,未获取到登陆信息!";
		} else {
			document dbdocument = database.getdatabaseinstance().getdocumentmap().get(document.getid());
			if (null != dbdocument) {
				// 若数据库中已经存在该id的文档,则新增文档失败...
				return "添加文档《"   document.gettitle()   "》失败,文档已存在!";
			} else {
				// 若该id的文档在数据库不存在,则新增文档成功...
				database.getdatabaseinstance().getdocumentmap().put(document.getid(), document);
				return "添加文档《"   document.gettitle()   "》成功...";
			}
		}
	}
	@override
	public string delete(document document) {
		user user = database.getdatabaseinstance().getcurrentuser();
		if (null == user) {
			// 若当前用户未登陆,则新增文档失败...
			return "保存失败,未获取到登陆信息!";
		} else {
			document dbdocument = database.getdatabaseinstance().getdocumentmap().get(document.getid());
			if (null == dbdocument) {
				// 若数据库中该文档不存在,则删除文档失败...
				return "删除文档《"   document.gettitle()   "》失败,文档不存在!";
			} else {
				// 若数据库中该文档存在,则删除文档成功...
				database.getdatabaseinstance().getdocumentmap().remove(document.getid());
				return "删除文档《"   document.gettitle()   "》成功...";
			}
		}
	}
}

最后,我们就要定义动态代理类了,前面说过,每一个动态代理类都必须要实现 invocationhandler 这个接口,因此我们这个动态代理类也不例外。 

import java.lang.reflect.invocationhandler;
import java.lang.reflect.method;
import java.util.calendar;
import java.util.gregoriancalendar;
public class databaseloghandler implements invocationhandler {
	private object object;
	private calendar calendar;
	public databaseloghandler(object object) {
		super();
		this.object = object;
	}
	// invoke()方法用于处理对代理类实例的方法调用并返回相应的结果
	@override
	public object invoke(object proxy, method method, object[] args) throws throwable {
		before(method);
		// 继续转发请求给内部真实主题角色
		object result = method.invoke(object, args);
		after(result);
		if (method.getname().equalsignorecase("logout")) {
			system.out.println("**********************************");
			system.out.println("");
		}
		return result;
	}
	public void before(method method) {
		calendar = new gregoriancalendar();
		int year = calendar.get(calendar.year);
		int month = calendar.get(calendar.month);
		int date = calendar.get(calendar.date);
		int hour = calendar.get(calendar.hour_of_day);
		int minute = calendar.get(calendar.minute);
		int second = calendar.get(calendar.second);
		string time = hour   "时"   minute   "分"   second   "秒";
		system.out.println("北京时间:"   year   "年"   month   "月"   date   "日"   time   ",执行方法\""   method.getname()   "\"");
	}
	public void after(object object) {
		system.out.println("执行结果:"   object);
	}
}

至此,动态代理所需要的类就算创建完成了,接下来创建一个client充当客户端来测试一下。

import java.lang.reflect.proxy;
public class mainclass {
	public static void main(string[] args) {
		// 此处来创建了两个动态代理类对象...
		userdao userdao = new impuserdao();
		databaseloghandler userhandler = new databaseloghandler(userdao);
		documentdao doucumentdao = new impdocumentdao();
		databaseloghandler documenthandler = new databaseloghandler(doucumentdao);
		userdao userproxy = (userdao) proxy.newproxyinstance(userdao.class.getclassloader(),
				new class[] { userdao.class }, userhandler);
		documentdao documentproxy = (documentdao) proxy.newproxyinstance(documentdao.class.getclassloader(),
				new class[] { documentdao.class }, documenthandler);
		// 先输入一个不存在的用户id登陆试试...
		userproxy.login(20160718l);
		documentproxy.add(new document(30160711l, "转角遇见幸福"));
		userproxy.logout();
		// 再用一个真实用户id登陆试试...
		userproxy.login(20160708l);
		documentproxy.add(new document(30160711l, "转角遇见幸福"));
		documentproxy.add(new document(30160711l, "转角遇见幸福"));
		userproxy.logout();
	}
}

运行程序打印结果如下: 

北京时间:2016年6月11日19时33分46秒,执行方法"login"
执行结果:登陆失败,id为"20160718"的用户不存在!
北京时间:2016年6月11日19时33分46秒,执行方法"add"
执行结果:保存失败,未获取到登陆信息!
北京时间:2016年6月11日19时33分46秒,执行方法"logout"
执行结果:退出登陆失败,当前无登陆用户!
**********************************
北京时间:2016年6月11日19时33分46秒,执行方法"login"
执行结果:用户[燕凌娇]登陆成功...
北京时间:2016年6月11日19时33分46秒,执行方法"add"
执行结果:添加文档《转角遇见幸福》成功...
北京时间:2016年6月11日19时33分46秒,执行方法"add"
执行结果:添加文档《转角遇见幸福》失败,文档已存在!
北京时间:2016年6月11日19时33分46秒,执行方法"logout"
执行结果:用户[燕凌娇]退出登陆成功...
**********************************

从以上程序的最终运行结果可以看出:通过使用jdk自带的动态代理,我们同时实现了对impuserdao和impdocumentdao两个真实主题类的统一代理和集中控制。至于该动态代理类是如何被创建的?将在下一篇文章详细讨论,接下来我们先看看如何使用cglib实现动态代理。  

生成动态代理类的方法很多,如上例中jdk自带的动态代理、cglib、javassist 或者 asm 库。jdk 的动态代理使用简单,它内置在 jdk 中,因此不需要引入第三方 jar 包,但相对功能比较弱。cglib 和 javassist 都是高级的字节码生成库,总体性能比 jdk 自带的动态代理好,而且功能十分强大。asm 是低级的字节码生成工具,使用 asm 已经近乎于在使用 java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 asm 的使用很繁琐,而且性能也没有数量级的提升,与 cglib 等高级字节码生成工具相比,asm 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 cglib 或者 javassist。

接下来我们继续用上面的例子来体验一下如何使用cglib实现动态代理。

首先,使用cglib来实现动态代理需引入“asm.jar”(cglib的底层是使用asm实现的)和“cglib.jar”两个第三方jar包,引入两个jar包时需注意其版本,若版本有冲突会出现以下异常:exception in thread "main" java.lang.nosuchmethoderror: org.objectweb.asm.classwriter.(i)v

接下来开始写代码,延用上例中的:user类、document类、database类、两个抽象主题接口userdao和documentdao、两个真实主题角色类impuserdao和impdocumentdao,这几个类无需做任何修改,此处不再重复贴出。

去掉上例中的 databaseloghandler 类,新增一个 cglibproxy 类,该类需实现cglib的 methodinterceptor(方法拦截) 接口。 

import java.lang.reflect.method;
import java.util.calendar;
import java.util.gregoriancalendar;
import net.sf.cglib.proxy.enhancer;
import net.sf.cglib.proxy.methodinterceptor;
import net.sf.cglib.proxy.methodproxy;
public class cglibproxy implements methodinterceptor {
	private calendar calendar;
	/**
	 * 创建动态代理类对象
	 *
	 * @param clazz
	 *            需要创建子类代理的父类的类型
	 * 
	 * @return
	 */
	public object getproxyinstance(class clazz) {
		enhancer enhancer = new enhancer();
		// 设置要创建的动态代理类的父类
		enhancer.setsuperclass(clazz);
		// 设置回调的对象,此句会导致调用动态代理类对象的方法会被指派到cglibproxy对象的intercept()方法
		enhancer.setcallback(this);
		// 通过字节码技术动态创建动态代理类实例
		return enhancer.create();
	}
	@override
	// 回调方法
	public object intercept(object obj, method method, object[] args, methodproxy proxy) throws throwable {
		before(method);
		// 通过动态子类代理实例调用父类的方法
		object result = proxy.invokesuper(obj, args);
		after(result);
		if (method.getname().equalsignorecase("logout")) {
			system.out.println("**********************************");
			system.out.println("");
		}
		return result;
	}
	public void before(method method) {
		calendar = new gregoriancalendar();
		int year = calendar.get(calendar.year);
		int month = calendar.get(calendar.month);
		int date = calendar.get(calendar.date);
		int hour = calendar.get(calendar.hour_of_day);
		int minute = calendar.get(calendar.minute);
		int second = calendar.get(calendar.second);
		string time = hour   "时"   minute   "分"   second   "秒";
		system.out
				.println("北京时间:"   year   "年"   month   "月"   date   "日"   time   ",执行方法\""   method.getname()   "\"");
	}
	public void after(object object) {
		system.out.println("执行结果:"   object);
	}
}

对客户端进行相应修改,如下。 

public class mainclass {
	public static void main(string[] args) {
		// 创建一个cglibproxy代理类对象,用来创建子类代理实例
		cglibproxy cglib = new cglibproxy();
		// 为impuserdao类添加一个动态代理类对象,即子类代理对象
		userdao userproxy = (userdao) cglib.getproxyinstance(impuserdao.class);
		// 为impdocumentdao类添加一个动态代理类对象,即子类代理对象
		documentdao documentproxy = (documentdao) cglib.getproxyinstance(impdocumentdao.class);
		// 先输入一个不存在的用户id登陆试试...
		userproxy.login(20160718l);
		documentproxy.add(new document(30160711l, "转角遇见幸福"));
		userproxy.logout();
		// 再用一个真实用户id登陆试试...
		userproxy.login(20160708l);
		documentproxy.add(new document(30160711l, "转角遇见幸福"));
		documentproxy.add(new document(30160711l, "转角遇见幸福"));
		userproxy.logout();
	}
}

运行程序结果打印如下,与之前使用jdk自带动态代理程序运行结果完全相同: 

北京时间:2016年6月12日20时22分35秒,执行方法"login"
执行结果:登陆失败,id为"20160718"的用户不存在!
北京时间:2016年6月12日20时22分35秒,执行方法"add"
执行结果:保存失败,未获取到登陆信息!
北京时间:2016年6月12日20时22分35秒,执行方法"logout"
执行结果:退出登陆失败,当前无登陆用户!
**********************************
北京时间:2016年6月12日20时22分35秒,执行方法"login"
执行结果:用户[燕凌娇]登陆成功...
北京时间:2016年6月12日20时22分35秒,执行方法"add"
执行结果:添加文档《转角遇见幸福》成功...
北京时间:2016年6月12日20时22分35秒,执行方法"add"
执行结果:添加文档《转角遇见幸福》失败,文档已存在!
北京时间:2016年6月12日20时22分35秒,执行方法"logout"
执行结果:用户[燕凌娇]退出登陆成功...
**********************************

ps:cglib创建的动态代理对象性能比jdk创建的动态代理对象的性能高不少,但是cglib在创建代理对象时所花费的时间却比jdk多得多,所以对于单例的对象,因为无需频繁创建对象,用cglib合适,反之,使用jdk方式要更为合适一些。同时,由于cglib采用动态创建子类的方法来对被代理的父类的功能进行增强和代理,所以,无法对被声明为final的类或方法进行代理。  

动态代理类使用字节码动态生成加载技术,在运行时生成并加载代理类。与静态代理相比,动态代理具有以下优点: 

-无需单独为每一个接口添加一个代理类,使用动态代理可以一次性为多个接口实现代理。
-无需逐个为接口中的所有方法添加实现,使用动态代理可以一次性为多个接口中的所有方法实现代理,在接口方法数量比较多的时候,可以避免出现大量的重复代码。

动态代理的缺点:
           目前,jdk中提供的动态代理只能对接口实现代理,无法代理未实现接口的类。如果需要动态代理未实现接口的类,必须借助第三方工具,如:cglib(code generation library)等。

网站地图