1.引子
代理,咱们都很熟悉,非要举一个例子的话,好比说生活中的各类中介,好比说明星艺人的经纪人,都是代理,生活中的代理。java
延伸到程序员的世界中也有代理,咱们都很是熟悉代理设计模式。好比说提到spring框架,你必定会想起IOC,和AOP。IOC叫作控制反转,是工厂设计模式和依赖注入的应用。AOP叫作面向切面编程,相应的应用了代理设计模式。程序员
这么看来代理两个字还有许多讲究,那么你有想过代理设计模式,到底解决了什么问题吗?或者说日常咱们租房、买房为何非要找中介呢?spring
在代理这种设计模式中,咱们能够尝试问这么一些问题编程
-
谁是代理对象?设计模式
-
谁是被代理(目标对象)?安全
-
若是没有代理对象,目标对象会怎么样?框架
举个例子,某歌星须要开一场演唱会,须要作许多准备工做。好比说搭台、商务签约、行程安排、最后开唱。咱们常说术业有专攻,对于一个歌手,他擅长于也仅擅长于唱歌,别的不会。jvm
那怎么办?请经纪人呐!像商务签约、行程安排都交给经纪人来作,歌星只须要到点登台唱歌便可。你看这就是代理,咱们能够用正式一点的话语来总结描述一下ide
-
代理是一种机制,一种用于控制访问目标对象的机制性能
-
代理,能够实现对目标对象的保护(要找歌星签名,先得过经纪人这一关)
-
代理,能够实现对目标对象的加强(歌星只须要关心唱歌,别的事情交给经纪人)
到这里,你应该可以充分理解代理,及代理设计模式了。
那到底什么是代理设计模式呢?
-
代理设计模式,它是一种结构型设计模式
-
有代理对象,被代理对象(目标对象)
-
经过代理对象,能够实现对目标对象的访问控制,增长目标对象安全性
-
经过代理对象,能够实如今不改变目标对象的的状况下,加强目标对象能力
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,抛砖引玉指望你能够去了解一下。