从法外狂徒张三卖房引起的代理模式
微信公众号:大黄奔跑
关注我,可了解更多有趣的面试相关问题。
写在之前
谈到代理模式,最常用的使用场景莫过于 AOP 中的利用了,在讨论 AOP 的实现之前,先来聊聊什么代理模式。
动态代理有两种形式,静态代理和动态代理,大家先不用在意两者的概念,等了解本篇你将会发现其实两者差别不大。
静态代理
用一个简单的例子来分析什么是静态代理,用买房张三卖房这件事儿为例,聊聊代理模式有何作用,为何如此使用如此频繁。
Subject 接口:用于对被访问者的抽象化(比如卖房这件事儿)
SubjectImpl:被访问者的具体实现(张三想要卖房)
SubjectProxy:被访问者的代理实现类,该类需要有一个 Subject 接口具体的实例。(比如房产中介,需要拿着张三授权才可以代理)
Client:访问者的抽象。(比如李四想买房,本身是一个顾客)
代理类本身就是替被访问者做事的,李四想买房子,提了很多要求,比如朝南、学区房;房产中介(SubjectProxy)看张三家的房子刚好符合李四预期,将张三的房子介绍给李四;相当于当一个中间人的意思。有人可能会说,就类似于一个介绍的活儿吗?非得让中介来吗?如果只是简单的介绍,还真不需要中介,但是中介可以帮忙跑贷款、帮忙把握合同等。
这样,张三只需要授权给中介,就可以一边做别的事儿,一边更加省心地完成交易。
上面的图顺理成章变成了如下的模式
被代理的抽象接口——房子
public interface House {
/**
* 卖房子
*/
void sell();
}
被访问的类——张三的房子
public class HouseForZhangsan implements House {
@Override
public void sell() {
System.out.println("zhangsan sell house…………");
}
}
代理类——房产中介
public class HouseProxy implements House {
private House house;
// 通过构造方法做到每次替不同的人代理
public HouseProxy (House house) {
this.house = house;
}
@Override
public void sell() {
house = new HouseForZhangsan();
house.sell();
}
}
测试类——交易场所
public class Test {
public static void main(String[] args) {
// 构造具体的卖房者
House house = new HouseForZhangsan();
// 将张三交给中介代理
House houseForPerson = new HouseProxy(house);
houseForPerson.sell();
}
}
还是回到 Spring AOP 模式中,其中 SubjectProxy 就像是 SubjectImpl 的中介,而 SubjectImpl 本身是系统中的 JoinPoint 所在的对象(目标对象),顺理成章地为目标对象创建一个代理对象,完成切面的逻辑。
但是各位想想,市面上并不是只有房子卖呢,张三家里有一辆空闲的车,也想卖,还能去找房产中介吗?
肯定不能了。
于是催生出了专门用于车交易的中介,比如瓜子二手车(号称没有中间商赚差价,哈哈哈)、二手车之家等等。
再比如万一张三突然发现手机用久了,想卖掉二手手机,新的 iPhone,于是又出现了各种各样的二手手机平台(其实本质也是一种中介)
……
……
等等,那有人可能会想,能不能搞一个中介,能够啥都卖呢?于是更加全面的二手平台应运而生了。
在这上面可以灵活的代理各种商品 (被代理对象),这就达到了一种动态中介的效果。
对,没错,动态代理已经介绍完了。
开玩笑,继续聊聊 Spring AOP 是如何利用动态代理的。
动态代理
可以指定接口在运行期间动态的生成代理对象。(换句话说:无论你要卖什么,你来的时候都可以给你找一个对应的中介)
那么如何动态生成代理类呢?
需要借助两个工具,一个是 java.lang.reflect.Proxy
类 和 java.lang.reflect.InvocationHandler
,问题的关键在于如何实时的给客户产生一个满足要去的中介。
这个就是借助 InvocationHandler
来动态生成代理类,还是以上面中介为例,我们姑且讲要生成的代理类叫做 target
.
如何动态产生不同类型的中介?
第一步肯定需要知道此时替什么类型客户代理,但是又不能写得太死,我们姑且在生成代理类中先声明一个 被代理的对象。
第二步:通过某种方式将 被代理对象通过传入的方式传进来
第三步:将被代理对象与中介进行绑定。
/**
* 被代理的目标
*/
public Object target;
/**
* 绑定委托对象,并且生成代理类
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//绑定该类实现的所有接口,取得代理类
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
上述几步部署完成之后,会明白中介要替什么人做事儿,中介做什么事儿,并且将中介与客户关联起来。
最后才是真正的替客户做事儿。
public class SellInvocationHandler implements InvocationHandler {
/**
* 被代理的目标
*/
public Object target;
/**
* 绑定委托对象,并且生成代理类
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//绑定该类实现的所有接口,取得代理类
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("额外逻辑……");
return method.invoke(target, args);
}
}
还记得我们之前说过的吗?
动态代理解决的只是灵活产生不同代理类(换句话说灵活搭配不同类型中介)
至于做什么类型事儿,和替什么人做什么事儿这两件事儿还是得存在。
因此仍然需要申明两个类。
做什么类型事儿
public interface House {
/**
* 卖房子
*/
void sell();
}
替什么人做什么事儿
public class HouseForZhangsan implements House {
@Override
public void sell() {
System.out.println("zhangsan sell house…………");
}
}
然后就可以愉快地进行交易了,每次有新的顾客来,就可以叫不同类型的中介来服务。
public class DynamicProxyTest {
public static void main(String[] args) {
SellInvocationHandler invocationHandler = new SellInvocationHandler();
// 将被访问类和代理类相互绑定( 将房产中介 与 房子卖者相互绑定 )
House house = (House) invocationHandler.bind(new HouseForZhangsan());
// 真正执行
house.sell();
}
}
至此,我们已经完成了真正的灵活代理工作。
动态代理虽好,却不能解决所有的事情。比如,动态代理只能对实现了相应接口 (Interface) 的类使用,如果某个类没有实现任何的 Interface,就无法使用动态代理机制为其生成相应的动态代理对象。