姐姐带我玩转java设计模式(内附照片)- 代理模式

Posted 浦江之首

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了姐姐带我玩转java设计模式(内附照片)- 代理模式相关的知识,希望对你有一定的参考价值。

先不说话,直接来张姐姐照片,称呼飞姐即可,希望飞姐能带飞。

原创不易,麻烦先三连,再细看,谢谢

示例的源码可以直接通过csdn下载也可以通过git导出:https://github.com/igdnss/java_proxy.git

我:飞姐,最近一直在看一些框架的源码,发现到很多地方都用到了代理,尤其是动态代理。例如SpringData jpa中EntityManager的创建过程。
飞姐:是的啊,代理模式是很常见的一种设计模式,一定要学会哦!
我:嗯嗯,我个人感觉在23种设计模式中代理模式比较难,尤其是动态代理。
飞姐:当然,学代理模式一定要看源码,不然对象怎么创建出来的你都搞不清楚。
我:这样啊,难怪我掌握的不牢。
飞姐:嗯,代理模式深究的话还需要追述到jvm。走,飞姐带你去探究一下,争取能一次性搞懂。

定义

我:飞姐,你能简单介绍一下什么时候代理模式吗?
飞姐:好啊,其实不难理解。
所谓的代理,就是中介,当我们想使用某个对象的时候,我们不是直接使用,而是通过中介(代理)来与之取得联系。说的专业一点就是给目标对象提供一个代理对象,并由这个代理对象去控制对目标对象的引用
我:嗯,解释到位,是不难理解,那飞姐知道干嘛要引入这个代理吗?不引用不可以吗,直接与目标对象取得联系不就可以吗?
飞姐:你就别考我啦,难不到我的呢,使用代理呢我觉得最起码有两个好处:

  • 通过代理对象访问目标对象可以有效防止直接访问目标对象给系统带来的不必要复杂性。
  • 通过代理对象扩展原有的业务。

举个例子:买二手房的时候不是直接跟房东接触,而是由房产中介帮忙接触,这样可以省去很多事,资金的安全保障,房产证的办理等。
我:飞姐真棒,那飞姐知道代理分成静态代理和动态代理吗?
飞姐:必须的啊。

静态代理

飞姐: 由一个固定的代理帮忙控制目标对象,功能单一,不易扩展,所以称之为静态代理。如果还是很抽象的话,看一下下面的类图就懂了:
在这里插入图片描述
Client 通过ProsySubject实现对RealSubject的访问,Client无需要直接访问RealSubject。这里需要注意:为了实现静态代理模式,ProxySubject与RealSubject都必须实现Subject接口
我:飞姐,能举一个再具体一点的例子吗?
飞姐:没问题,现在上海房价贵的吓人,把你卖了估计都不能外环外买一个老破小呢,这里就以买房为例吧。
示例
客户通过中介购房二手商品房,客户找的是一个夫妻中介(假设只能做一些二手商品房的交易)。
在这里插入图片描述

public interface Owner {
	void exchangeOldHouse(String str);
}

public class HouseOwner implements Owner {

	@Override
	public void exchangeOldHouse(String str) {

		System.out.println(str);

	}

}

public class CoupleAgent implements Owner {
	
	//这里必须要包含被真正访问的对象
	HouseOwner houseOwner;
	

	public CoupleAgent(HouseOwner houseOwner) {
		super();
		this.houseOwner = houseOwner;
	}


	@Override
	public void exchangeOldHouse(String str) {
		//扩展原有的业务1
		doSomeThingBefore();
		houseOwner.exchangeOldHouse(str);
		//扩展原有的业务2
		doSomeThingAfter();
	}
	private void doSomeThingBefore() {
		System.out.println("收点中介费");
	}
	private void doSomeThingAfter() {
		System.out.println("办理产证");
	}

}
public class Client {
	public static void main(String[] args) {
		//真正提供服务的对象
		HouseOwner owner = new HouseOwner();
		//由代理来接触真正的对象
		CoupleAgent agent = new CoupleAgent(owner);
		agent.exchangeOldHouse("房子交易成功");
	}

}

整个过程很好理解,通过代理客户成功买到了房子。
我:确实很好理解,但万一需求变了,来了一个大客户,需要买办公楼,当前的Owner只能提供二手商品房交易,无法提供办公楼的交易,那怎么办呢?难道非得让Owner偷一个座办公楼吗?说的专业一点,难道要对接口进行扩展,添加新的方法,这很显然违背了软件开发过程的开闭原则啊(对扩展开放,对修改关闭),在已设计好的接口基础之上,我们能扩展它,而不能去修改它,不然添加一个函数,之前实现此接口的类都需要进行修改,工作量非常大。
飞姐:你说的非常对,针对这种情况,我们可以让中介(代理)去找办公楼资源啊,客户还是只需要跟中介沟通。但是我们这里的中介能力太小,只能处理二手商品房交易,如果有一个能根据不同的客户而提供不同房源的中介岂不是相当完美。其实,这就是动态代理的概念,会根据需求的变化而变化,是一个动态的过程。
我:飞姐,可以介绍一下吗。
飞姐:OK,我们继续往下走。

动态代理

飞姐:在了解动态代理之前,希望大家有一定java反射机制以及类加载机制知识,如果没有的话也没关系,大家知道动态代理是怎么一回事就可以了,先当成一个公式使用。
我:感觉有点难啊
飞姐:是的,个人觉得动态代理比较难。目前提供动态的中间件有cglib(使用继承实现的)和jdk(使用反射实现的)。这里介绍jdk的动态代理,它使用反射机制创建代理类对象,并动态的指定要代理的目标类。在静态代理中,目标类都是固定的,就像上文中的HouseOwner一样,CoupleAgent 每次只能使用HouseOwner 。
我:有点感觉了,那为什么叫动态呢?
飞姐:这是个好问题,因为这种机制只有在程序执行时才能够使用,即类需要加载到内存中才能用。java创建对象的过程是在java文件编译为class文件后使用构造方法来创建,但动态代理不是这样的,是程序加载到内存中使用反射来创建代理对象,有点绕。
我:有点期待哦,那jdk动态代理到底是怎么回事呢。
飞姐:来了来了。jdk的java.lang.reflect包中提供了三个类:InvocationHandler,Method,Proxy。整个动态代理就是围绕这三个类完成的。

  • InvocationHandler: 它是一个接口,只有一个方法invoke(Object proxy,Metho method,Object[] args),这个方法最终是由jdk去调用,我们只需要知道我们需要重写它,理解每个参数的意思就行.代理类中要完成的功能就写在invoke方法中,因此可以把这个Handler看作代理类的功能。代理类完成的功能跟静态代理一样:调用目标方法,业务扩展。这三个参数的含义分别为:proxy:jdk创建的代理对象,无需赋值。method:目标类中的方法,jdk提供method对象。args:目标类中的方法参数,jdk提供的。

  • Method类:表示目标类中的方法,通过它可以执行目标类中的方法,Method.invoke(目标对象,方法的参数)。相当于上文中的houseOwner.exchangeOldHouse(str);

  • Proxy类:用于创建代理对象。之前创建对象用new,这里使用Proxy的静态方法newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h),返回值为目标类的代理对象,相当于上文中的HouseOwner owner = new HouseOwner(),其ClassLoader为类加载器,负责向内存中加载目标类(因为在程序执行时才能使用动态代理,所以这里要传入类加载器),例如HouseOwner.getClass().getClassLoader()。Class<?>[]为目标类实现的接口,也是反射获取的。InvocationHandler 是我们自己写的,代理类要完成的功能。

我:感觉好复杂啊
飞姐:有点吧,但是大家使用的时候遵循下面的步骤基本没有问题。

  • 创建接口,定义目标类要实现的功能
  • 创建目标类,实现接口,重写方法
  • 创建InvocationHandler接口实现类(可以理解为代理的功能类,因为代理的功能都会定义此实现类的invoke方法中),在invoke方法中完成代理类的功能( 调用目标方法,扩展业务)
  • 使用Proxy类的静态方法,创建代理对象,并把返回值转为目标接口类型。

我:飞姐,举个例子吧
飞姐:回到动态代理的开头,来了一个大客户,我们不可能去修改Owner接口,这不符合开闭原则,所要重新定义一个Owner实现类,但CoupleAgent又无法使用,见下面代码。

public class OfficeOwner implements Owner {

	@Override
	public void exchangeOldHouse(String str) {
		System.out.println("Here has kinds of office");
		System.out.println(str);
	}

}

/**
 * 这是一个代理,通过这个代理可以与目标类取得联系
 */
public class EstateHandler implements InvocationHandler {
	
	//目标对象
	private Object target;
	
	
	
	/**
	 * obj:目标对象通过构造方法传进来
	 */
	public EstateHandler(Object obj) {
		super();
		this.target = obj;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//扩展的业务1
		doSomeThingBefore();
		//目标方法,使用代理对象调用方法时,会通过反射生成method,从而保证能调用到目标函数
		Object result = method.invoke(target, args);
		//扩展的业务2 
		doSomeThingAfter();
		return result;
	}

	private void doSomeThingBefore() {
		System.out.println("收点中介费");
	}
	private void doSomeThingAfter() {
		System.out.println("办理产证");
	}

}

飞姐:为了验证动态代理的动态性,我在这里又加了一个房车客户,并将之前例子用动态代理实现了一遍

public class HouseVanOwner implements Owner {

	@Override
	public void exchangeOldHouse(String str) {
		System.out.println("Here is a house van");
		System.out.println(str);
	}

}

public class Client2 {

	public static void main(String[] args) {
		// 创建目标对象
		Owner officeOwner = new OfficeOwner();
		// 创建一个InvocationHandler对象
		InvocationHandler estateHandler = new EstateHandler(officeOwner);
		// 创建一个代理对象,知道这么用就行了,当作一个公式用,往下研究很复杂,先不考虑
		Owner officeProxyInstance = (Owner) Proxy.newProxyInstance(officeOwner.getClass().getClassLoader(),
				officeOwner.getClass().getInterfaces(), estateHandler);
		// 使用代理对象调用目标方法,此目标方法会被反射成invoke方法中的method
		officeProxyInstance.exchangeOldHouse("I am a rich client");

		System.out.println("====================================================");
		// 之前的静态代理用动态代理处理试试
		Owner houseOwner = new HouseOwner();
		InvocationHandler houseHandler = new EstateHandler(houseOwner);
		Owner houseProxyInstance = (Owner) Proxy.newProxyInstance(houseOwner.getClass().getClassLoader(),
				houseOwner.getClass().getInterfaces(), houseHandler);
		// 使用代理对象调用目标方法,此目标方法会被反射成invoke方法中的method
		houseProxyInstance.exchangeOldHouse("I am a poor client");

		System.out.println("====================================================");
		// 如果又来个客户需要买房车,这里只需要重新实现一下Owner接口,无需修改Owner接口了,符合开闭原则
		Owner houseVanOwner = new HouseVanOwner();
		InvocationHandler houseVanHandler = new EstateHandler(houseVanOwner);
		Owner houseVanProxyInstance = (Owner) Proxy.newProxyInstance(houseVanOwner.getClass().getClassLoader(),
				houseVanOwner.getClass().getInterfaces(), houseVanHandler);
		// 使用代理对象调用目标方法,此目标方法会被反射成invoke方法中的method
		houseVanProxyInstance.exchangeOldHouse("I am a car house client");
	}

}

飞姐:好了,到这动态代理介绍的差不多了,提醒大家一点,debug时动态代理对象跟new出来的对象不一样,留意一下就可以了。
在这里插入图片描述
我:感谢飞姐的细心讲解,很精彩,期待下一个设计模式。

以上是关于姐姐带我玩转java设计模式(内附照片)- 代理模式的主要内容,如果未能解决你的问题,请参考以下文章

颜值10分姐姐带我玩转java设计模式(内附照片)- 状态模式

《23种设计模式(Java版)》| 工厂模式(内附源码案例)。

玩转JAVA代理模式

jav设计模之的动态代理

曾贤志Excel答疑课-我玩转Excel有哪些诀窍?

国庆在家太无聊, 用Java爬了上千张小姐姐照片...