面向对象编程内功心法系列十(聊一聊代理模式)
1.引子
代理,我们都很熟悉,非要举一个例子的话,比如说生活中的各种中介,比如说明星艺人的经纪人,都是代理,生活中的代理。
延伸到程序员的世界中也有代理,我们都非常熟悉代理设计模式。比如说提到spring框架,你一定会想起IOC,和AOP。IOC叫做控制反转,是工厂设计模式和依赖注入的应用。AOP叫做面向切面编程,相应的应用了代理设计模式。
这么看来代理两个字还有许多讲究,那么你有想过代理设计模式,到底解决了什么问题吗?或者说平常我们租房、买房为什么非要找中介呢?
在代理这种设计模式中,我们可以尝试问这么一些问题
-
谁是代理对象?
-
谁是被代理(目标对象)?
-
如果没有代理对象,目标对象会怎么样?
举个例子,某歌星需要开一场演唱会,需要做许多准备工作。比如说搭台、商务签约、行程安排、最后开唱。我们常说术业有专攻,对于一个歌手,他擅长于也仅擅长于唱歌,别的不会。
那怎么办?请经纪人呐!像商务签约、行程安排都交给经纪人来做,歌星只需要到点登台唱歌即可。你看这就是代理,我们可以用正式一点的话语来总结描述一下
-
代理是一种机制,一种用于控制访问目标对象的机制
-
代理,可以实现对目标对象的保护(要找歌星签名,先得过经纪人这一关)
-
代理,可以实现对目标对象的增强(歌星只需要关心唱歌,别的事情交给经纪人)
到这里,你应该能够充分理解代理,及代理设计模式了。
那到底什么是代理设计模式呢?
-
代理设计模式,它是一种结构型设计模式
-
有代理对象,被代理对象(目标对象)
-
通过代理对象,可以实现对目标对象的访问控制,增加目标对象安全性
-
通过代理对象,可以实现在不改变目标对象的的情况下,增强目标对象能力
2.案例
在引子部分,我们搞清楚了什么是代理设计模式,接下来我将通过案例实现,给你演示一下代理设计模式的应用。
关于代理设计模式,你需要留意它有两种实现方式,分别是
-
静态代理
-
动态代理
另外在实现的过程中,通常需要代理对象,与被代理对象实现相同的接口,即它们是同类。我们具体看代码案例吧,相信看到代码你就明白了。
2.1.静态代理
通常在项目中,我们将功能分为
-
业务功能(比如说用户、订单的增删改查)
-
非业务功能(比如说事务控制、记录日志、性能耗时统计)
对于业务开发工程师来说,只需要关注业务功能的开发。而非业务功能,在每个项目中都差不多,具备一定的通用性,完全可以通过代理设计模式来支持实现。
接下里我将给你模拟一个这样的案例,我们以保存用户接口为例,在保存用户的同时进行接口耗时统计。
2.1.1.接口
/**
* 用户接口
*
* @author ThinkPad
* @version 1.0
* @date 2021/3/7 16:09
*/
public interface UserService {
/**
* 保存用户接口
* @param name
*/
void saveUser(String name);
}
2.1.2.被代理类
/**
* 用户接口实现类
*
* @author ThinkPad
* @version 1.0
* @date 2021/3/7 16:10
*/
public class UserServiceImpl implements UserService {
/**
* 保存用户接口
* @param name
*/
@Override
public void saveUser(String name) {
System.out.println("保存用户:" + name);
}
}
2.1.3.代理类
/**
* 用户接口代理,与目标对象实现相同的接口
*
* @author ThinkPad
* @version 1.0
* @date 2021/3/7 16:12
*/
public class UserProxy implements UserService{
private UserService target;
public UserProxy(UserService target){
this.target = target;
}
/**
* 保存用户接口(代理)
* @param name
*/
@Override
public void saveUser(String name) {
// 统计耗时开始(增强的功能)
System.out.println("统计保存用户接口耗时.start:" + System.currentTimeMillis());
// 调用目标对象
target.saveUser(name);
// 统计耗时结束(增强的功能)
System.out.println("统计保存用户接口耗时.end:" + System.currentTimeMillis());
}
}
2.1.4.测试类
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建代理对象
UserService proxy = new UserProxy(target);
// 通过代理对象,实现保存用户的时候,统计耗时
proxy.saveUser("小明");
}
#执行结果
统计保存用户接口耗时.start:1615105035485
保存用户:小明
统计保存用户接口耗时.end:1615105035485
Process finished with exit code 0
2.1.5.总结分析
UserProxy类有两个特点
-
实现UserService接口,与目标对象实现相同的接口
-
持有UserSerivceImpl实例,通过组合实现目标对象的能力增强
-
你需要关注UserProxy中的saveUser方法,在保存用户target.saveUser(name)前后,进行耗时统计
最后通过执行结果,我们看到在不修改UserSerivceImpl类代码的情况下,实现了保存用户的同时,进行接口耗时统计能力的增强。你看这就是代理设计模式,代码实现比较简单。
不过你需要注意,在实际项目中,静态代理使用非常少,原因是静态代理方式,每一个目标类都需要相应定义一个代理类,导致类的数量会膨胀,另外代码维护性比较差。
那么针对静态代理存在的问题,我们该如何解决呢?答案是动态代理。
2.2.动态代理
通过静态代理示例代码,我们比较直观的看到了代理设计模式的实现。不过我们说静态代理存在两个问题
-
每一个目标类,都需要一个相应的代理类,类的数量膨胀问题
-
静态代理,代码是在编译时实现,代码维护性比较差
针对静态代理的问题,jdk提供了动态代理的语法支持,接下来我们一起来看动态代理示例代码,你需要关注两个类
-
Proxy,创建代理对象入口类
-
InvocationHandler,具体实现代理增强的接口类
2.2.1.通过动态代理,创建代理类
/**
* 动态代理示例
* 1.Proxy,创建代理对象入口类
* 2.InvocationHandler,具体实现代理增强的接口类
*
* @author ThinkPad
* @version 1.0
* @date 2021/3/7 16:33
*/
public class DynamicUserProxy {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建代理对象
UserService proxy = (UserService)Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 统计耗时开始
System.out.println("统计保存用户接口耗时.start:" + System.currentTimeMillis());
// 调用目标对象
Object result = method.invoke(target, "小明");
// 统计耗时结束
System.out.println("统计保存用户接口耗时.end:" + System.currentTimeMillis());
return result;
}
}
);
// 通过代理对象,实现保存用户的时候,统计耗时
proxy.saveUser("小明");
}
}
#执行结果
统计保存用户接口耗时.start:1615106267280
保存用户:小明
统计保存用户接口耗时.end:1615106267280
Process finished with exit code 0
2.2.2.总结分析
对比动态代理,静态代理
-
执行结果是一样的,都在不修改目标类UserServiceImpl的情况下,实现了接口耗时统计能力的增强
-
代码结构都遵循了三个步骤
-
创建目标对象
// 创建目标对象 UserService target = new UserServiceImpl();
-
创建代理对象
#静态代理 // 创建代理对象 UserService proxy = new UserProxy(target); #动态代理 // 创建代理对象 UserService proxy = (UserService)Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 统计耗时开始 System.out.println("统计保存用户接口耗时.start:" + System.currentTimeMillis()); // 调用目标对象 Object result = method.invoke(target, "小明"); // 统计耗时结束 System.out.println("统计保存用户接口耗时.end:" + System.currentTimeMillis()); return result; } } );
-
通过代理对象,实现功能增强
// 通过代理对象,实现保存用户的时候,统计耗时 proxy.saveUser("小明");
-
-
差异最大的地方,是在创建代理对象。动态代理中,我们通过Proxy的newProxyInstance方法,创建代理对象,它有三个参数
-
参数1:类加载器,动态代理的本质是运行时实现增强,即在运行的时候jvm动态生成代理类字节码Class,从而实例化代理对象。因此,需要类加载器
-
参数2:目标对象实现的接口列表,符合代理设计模式定义,代理类,与目标类实现相同的接口
-
参数3:InvocationHandler,用于实现增强的接口,关心接口中的invoke方法,该方法中实现代理逻辑
-
通过上面动态代理,静态代理的对比,并且我详细给你解释了动态代理相关的代码细节,相信你可以很好的理解代理设计模式、静态代理、动态代理了。
关于代理模式,是一个很重要,且用的比较多的设计模式。最后我想抛出一个知识点,基于jdk提供的Proxy动态代理实现,我们叫做基于接口的动态代理,它的意思是说目标类必须要实现接口。那么如果目标类在没有实现任何接口的情况下,如何实现动态代理呢?答案是cglib,抛砖引玉期望你可以去了解一下。