设计模式篇的第一个模式,我选整理动态代理模式的相关内容。因为动态代理模式现在经常接触到,是java程序员成长中必须要掌握的一个模式。像开源技术Spring,其赖以成名的AOP(面向切面编程)的背后原理就是动态代理模式。AOP应用的一个典型实例就是日志,我们都知道,在系统运行过程中如果系统出了问题,第一排查手段就是查看系统日志。那么系统是怎么记录日志的?用过log4j的都知道如何显式的去打印日志,可还有更多的日志是系统来帮我们打印出来的,它究竟是怎么完成的?是在需要打印日志的地方像我们自己编程一样去显式调用日志打印方法吗?还有有其他更好的办法?答案看上去更像是后者,因为我们更相信这些开源技术的设计者(大牛)们在兼容性、扩展性等方面比我们考虑的更深入。
进入正题,讨论什么是动态代理模式。代理我们都清楚,经常听到“代理人”一说,其实明星的经纪人也是代理人的一种,比如某影视公司找明星拍一部电影,通常都是先找到他的经纪人进行初步协商,谈好后再由明星本人出面完成实际电影拍摄,经纪人代理了拍摄前的一些事物,但拍摄过程还是由被代理人(明星)自己来完成。下面用程序来模拟一下上面的内容:
首先创建一个接口,里面含有一个方法:
-
/**
-
* 影视拍摄
-
*/
-
public
interface ShootingWork {
-
/**
-
* 拍摄影视
-
*/
-
void shooting();
-
}
然后创建一个电影明星,电影明星可以拍摄电影,因此由它实现上面的接口:
-
/**
-
* 电影明星
-
*/
-
public
class FilmStar implements ShootingWork{
-
@Override
-
public void shooting() {
-
System.out.println(
"电影明星准备拍摄");
-
System.out.println(
"电影明星拍摄中");
-
System.out.println(
"电影明星拍摄完成");
-
}
-
}
然后在实现一个代理人,它负责对外代理电影明星的电影拍摄事务:
-
**
-
* 经纪人
-
*/
-
public
class FilmProxy implements ShootingWork {
-
private ShootingWork star =
new FilmStar();
-
-
@Override
-
public void shooting() {
-
/**
-
* 处理一些事务后,调用实际执行人的方法
-
*/
-
System.out.println(
"经纪人协商合同");
-
System.out.println(
"签合同。。。");
-
this.star.shooting();
-
}
-
}
最后我们来完成整个过程的模拟,创建一个客户端:
-
/**
-
* 客户端
-
*/
-
public
class ProxyClient {
-
@Test
-
public void filming(){
-
ShootingWork film =
new FilmProxy();
-
film.shooting();
-
}
-
}
控制台输出结果:
-
经纪人协商合同
-
签合同。。。
-
电影明星准备拍摄
-
电影明星拍摄中
-
电影明星拍摄完成
-
-
Process finished with exit code
0
可以看到,通过简单的几步,一个代理的模式就实现了,其实这是一个典型的静态代理。我们分析上面模式,发现一个代理只能对于一个实际委托人(明星),而且代码中必须明确指定代理的委托人。也就是编码中就必须确定代理人、委托人的关系。而实际生活中的代理远比这个模式复杂。我们再来举一个NBA的例子,NBA球员跟球队协商合同时,往往也是通过经纪人(经纪公司)的方式,而且一个经纪人往往明星会签约(代理)多个NBA球员。 这样,当球队找到经纪人时,事前(假想)并不知道这个球队要签约我名下的哪名球员,只有进一步沟通后才能确定。我们把这种方式称之为动态代理(如理解有误,请指出)。我们还是以明星这个为例编写demo,只不过现在一个经纪人代理多个明星,影视公司找人拍摄影视时,并不知道是找哪位,具体详谈后才能确定下来。按照这个思路我们来看JDK中动态代理如何实现:
首先实现JDK的InvocationHandler接口,作为动态代理人:
-
/**
-
* 实现调用类
-
*/
-
public
class ShootInvocationHandler implements InvocationHandler {
-
private ShootingWork star;
-
/**
-
* 构造方法中动态传入对象
-
*
-
* @param star
-
*/
-
public ShootInvocationHandler(ShootingWork star) {
-
this.star = star;
-
}
-
@Override
-
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-
System.out.println(
"动态代理人开始协商合同");
-
System.out.println(
"动态代理人开始签合同");
-
return method.invoke(star, args);
-
}
-
}
创建个简单的工厂类(工厂模式后面再补)实现实例的获取:
-
/**
-
* 工厂类
-
*/
-
public
class StarFactory {
-
-
public static ShootingWork getStarInstance(String type) {
-
if (
"film".equals(type)) {
-
return
new FilmStar();
-
}
-
return
null;
-
}
-
}
最后我们看客户端执行时的变化:
-
/**
-
* 客户端
-
*/
-
public
class ProxyClient {
-
/**
-
* 静态代理
-
*/
-
@Test
-
public void filming() {
-
ShootingWork film =
new FilmProxy();
-
film.shooting();
-
}
-
/**
-
* 动态代理
-
*/
-
@Test
-
public void dyProxy() {
-
ShootingWork film = StarFactory.getStarInstance(
"film");
-
ShootingWork subject = (ShootingWork) Proxy.newProxyInstance(getClass().getClassLoader(),
-
new Class<?>[] { ShootingWork.class },
-
new ShootInvocationHandler(film));
-
subject.shooting();
-
}
-
}
执行结果:
-
动态代理人开始协商合同
-
动态代理人开始签合同
-
电影明星准备拍摄
-
电影明星拍摄中
-
电影明星拍摄完成
-
-
Process finished with
exit code 0
这样一个动态代理就实现了。我们试着分析下好处在哪,当再代理一个电视明星、歌手、曲艺明星时,只需要实现ShootWork的接口,然后修改工厂里面的方法即可,而不需要去重新创建代理类了。这是动态代理JDK自带的实现方式。
还有一种实现动态代理的方式,那就是cglib。从上面jdk原生的动态代理实现方式可以看出,使用时委托对象需要实现一个或多个接口才能实现动态代理的机制,这也算是这种方式的一个缺点吧。如果想被代理的对象(委托对象)不用去实现任何接口,就可以被动态代理怎么办?答案就是cglib的动态代理方式。我们来看如何使用cglib来实现一个动态代理:
首先定义一个委托对象:
-
/**
-
* 一名演员
-
*/
-
public
class ActualActor {
-
-
public String doSomething(String something) {
-
return
"ActualActor is doing " + something;
-
}
-
}
实现cglib提供的MethodInterceptor接口,这里只做简单实现即可:
-
/**
-
* CGlib提供的接口实现
-
*/
-
public
class DoMethodIterceptor implements MethodInterceptor {
-
@Override
-
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
-
return methodProxy.invokeSuper(o, objects);
-
}
-
}
最后看客户端如何实现动态代理调用:
-
public
class CglibClient {
-
-
@Test
-
public void testCglib() {
-
Enhancer enhancer =
new Enhancer();
-
enhancer.setSuperclass(ActualActor.class);
-
enhancer.setCallback(
new DoMethodIterceptor());
-
-
ActualActor actualActor = (ActualActor) enhancer.create();
-
System.out.println(actualActor.doSomething(
"拍电影"));
-
System.out.println(actualActor.doSomething(
"拍电视"));
-
}
-
}
我们看下执行结果:
-
ActualActor is doing 拍电影
-
ActualActor is doing 拍电视
-
-
Process finished with
exit code 0
比较JDK原生的动态代理实现方式,cglib的方式似乎更简便一些,代码更清晰。在实际工作中,可以根据实际需要去选择实现方式。
源码获取方式:
github地址:https://github.com/walker0819/designpattern
</div>
</div>