静态代理与动态代理

代理模式

一、概念

代理模式:代理模式是为另一个对象提供一个替身来控制对这个对象的访问。代理类负责为这个对象预处理消息,过滤消息并转发消息,以及进行消息被该对象执行后的后续处理。

用一个例子说明:客户去买联想电脑,但是他不是直接去厂家去买,而是去一个有联想电脑卖的店去买,这个店就相当于一个代理,它不负责生产电脑,但是客户来买电脑时他转交这个订单给厂家,厂家就先把电脑送到这个店,这个店再给客户。对于客户来说,代理和厂家没有什么区别(都能买到电脑),这样子给客户一种错觉就是,其实客户就是在跟厂家做交易,而实际却不是!有个代理也有个好处,避免了客户对厂家的频繁访问交易,有了代理,厂家就可以专心做生产。对于程序来说,代理对象就可以拦截下许多目标对象不需要或者不想知道的信息,代理类也可以再转发请求给目标对象之前对数据做点处理,总而言之就是,代理类让客户与目标对象解耦,既能满足客户的需求,也能让目标对象只管做好自己的本分工作。


二、模式图


三、分类

按照代理类的创建时期,可分为静态代理和动态代理

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

动态:在程序运行时运用反射机制动态创建而成。


静态代理演示示例

/**
 * 
 * @ClassName StaticProxy.java
 * @Description 用户管理接口
 *
 */
interface UserManager{
	//为了代码简洁和清晰阅读,只写一个方法
	void addUser(String userName);
}
 
/**
 * 
 * @ClassName StaticProxy.java
 * @Description 实际操作对象--被代理对象
 *
 */
class UserManagerImpl implements UserManager{
 
	@Override
	public void addUser(String userName) {
		System.out.println("添加用户成功!用户为:" + userName);
		
	}
	
}
/**
 * 
 * @ClassName StaticProxy.java
 * @Description 代理对象,与被代理对象实现同一个接口,这样就达到了隐藏了真正的目标对象,让客户觉得访问代理对象就是在方式真实的对象
 *
 */
class Proxy implements UserManager{
 
	private UserManagerImpl umi;
	
	public Proxy(UserManagerImpl umi){
		this.umi = umi;
	}
	
	@Override
	public void addUser(String userName) {
		//使用代理的一个好处是可以在对实际对象访问前进行一些必要的处理,控制了对实际对象的访问
		//在添加之前做一些日志操作
		System.out.println("添加之前,记录到日志里去……");
		umi.addUser(userName);
		//在添加之后也做一些日志操作
		System.out.println("记录成功!时间:" + new Date());
	}
}
 
public class StaticProxy {
 
	public static void main(String[] args) {
		//客户想访问实际对象,只需要访问实际对象的代理对象即可
		UserManager um = new Proxy(new UserManagerImpl());
		um.addUser("张三");
	}
}

静态代理类优缺点

 

优点:

 

代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。

 

缺点:

1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。

引入动态代理:

 

根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类

所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

 

在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。

java内部已经通过反射为我们提供有关动态代理的接口,我们在使用动态代理时,传入相应的参数即可。

动态代理的模式图



分析:博主在学习动态代理的时候,很纠结这个InvocationHandler,它到底是用来干嘛的?为什么要用它?

首先,在动态代理中,Proxy代理类在编译期是不存在的,而是在程序运行时被动态生成的,因为有了反射,可以根据传入的参数,生成你想要的代理(如你想代理A就代理A,想代理B就代理B),实现原理就是在生成Proxy的时候你需要传入被代理类的所有接口(如果没有接口是另一种方式,下文会提),反射机制会根据你传入的所有接口,帮你生成一个也实现这些接口的代理类出来。之后,代理对象每调用一个方法,都会把这个请求转交给InvocationHandler来执行,而在InvocationHandler里则通过反射机制,继续转发请求给真正的目标对象,最后由目标对象来返回结果。或许还有读者疑惑,为什么Proxy不能直接转发请求给目标对象,而是先交给InvocationHandler,(如果你会反射很容易明白)这是因为,要通过反射来转为目标的对象的调用,需要传入调用的方法名,方法所属的类对象以及方法的参数,Proxy不直接转发给目标对象是因为,在程序运行时,Proxy是方法的直接调用者,然后将调用的信息传给InvocationHandler,InvocationHandler再根据传过来的信息并利用反射转为目标对象的最终调用,这个过程中,如果没有Proxy对象的调用,又怎么有运行时的动态信息给反射机制来处理呢,只有Proxy对象调用了,才能够知道有哪些动态信息,而InvocationHandler的作用就是用来接收这些信息,通过反射,进而转为目标对象的调用。

附上动态代理模式的工作原理图



下面代码示例

/**
 * 
 * @ClassName StaticProxy.java
 * @Description 用户管理接口
 *
 */
interface UserManager{
	//为了代码简洁和清晰阅读,只写一个方法
	void addUser(String userName);
}
 
//多加一个接口,演示动态代理对象能够代理多个目标对象
interface Person{
	void consume(Float money);
}
 
/**
 * 
 * @ClassName StaticProxy.java
 * @Description 实际操作对象--被代理对象
 *
 */
class UserManagerImpl implements UserManager{
 
	@Override
	public void addUser(String userName) {
		System.out.println("添加用户成功!用户为:" + userName);
		
	}
	
}
 
class PersonImpl implements Person{
 
	@Override
	public void consume(Float money) {
		System.out.println("消费"+ money + "元");
		
	}
	
}
 
/**
 * 
 * @ClassName DynamicProxy.java
 * @Description 日志打印
 *
 */
class LogHandler implements InvocationHandler{
 
	private Object tagetObject;
	
	//对要代理的目标对象进行绑定,关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
	public Object newProxyInstance(Object tagetObject){
		System.out.println("=================");
		this.tagetObject = tagetObject;
		//第一个参数是:指定目标对象的类加载器
		//第二个参数:指定目标对象实现的所有接口,让代理对象对目标对象实现同样的接口
		//第三个参数:指定代理对象要要转发请求给的实现了InvokationHandler的子类,并执行对应的invoke()方法
		//根据传入的目标对象,产生并返回改目标对象的一个代理对象,这就是动态代理
		return Proxy.newProxyInstance(tagetObject.getClass().getClassLoader(), 
				tagetObject.getClass().getInterfaces(), this);
	}
	
	
	@Override
	//关联的这个实现类的方法被调用时将被执行  
    /*InvocationHandler接口的方法:proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		proxy = null;
		try {
			System.out.println("记录到日志里去……");
			proxy = method.invoke(tagetObject, args);
			System.out.println("记录成功!时间:" + new Date());
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("操作失败!!");
			throw e;
		}
		
		return proxy;
	}
	
}
 
public class DynamicProxy {
 
	public static void main(String[] args) {
		//给客户一个InvokationHandler对象,就能动态生成一个代理对象,进而能够访问目标对象
		LogHandler lh = new LogHandler();
		UserManager um = (UserManager) lh.newProxyInstance(new UserManagerImpl());
		System.out.println("******************");
		um.addUser("张三");
		
		//动态代理可以代理多个目标对象,减少了代码重写
		//如又代理PersonImpl,并不需要额外写其他代码
		Person p = (Person) lh.newProxyInstance(new PersonImpl());
                System.out.println("******************");
               p.consume(88f);
	}
}


程序运行结果



从运行结果可以看出,

我们直接调用方法的对象时代理对象,然后Handler里的invoke()方法被自动调用,最后转为了目标对象的调用。

那怎么实现的呢?根据动态代理的工作原理和实现方式,我们可以猜测,生成的代理类应该是这样

class $Proxy0 {
	public Object addUser(String userName) {
		return handler.invoke(Object proxy, Method method, Object[] args);
	}
}


如此就能实现自动调用Handler的invoke()方法了。

(如果读者不太好理解动态代理,建议先学习下反射)

动态代理的优点


动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强。


总结


其实所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。


代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户需要上facebook,而普通网络无法直接访问,网络代理帮助用户先fanqiang,然后再访问facebook。这就是代理的作用了


学会了动态代理,再学习AOP就没有那么难了,AOP就是基于动态代理实现的,另见博主另一篇文章。


希望对读者有帮助!转载请注明出处!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值