前面几节,我们学习了设计模式中的创建型模式。创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。
其中,单例模式用来创建全局唯一的对象。工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。
从今天起,我们开始学习另外一种类型的设计模式:结构型模式。结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。 结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。今天我们要讲其中的代理模式。它也是在实际开发中经常被用到的一种设计模式。
话不多说,让我们正式开始今天的学习吧!
代理模式的原理解析(静态代理) 代理模式 (Proxy Design Pattern)的原理和代码实现都不难掌握。它在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。我们通过一个简单的例子来解释一下这段话。
这个例子来自我们在第25、26、39、40节中讲的性能计数器。当时我们开发了一个MetricsCollector类,用来收集接口请求的原始数据,比如访问时间、处理时长等。在业务系统中,我们采用如下方式来使用这个MetricsCollector类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class UserController { private MetricsCollector metricsCollector; public UserVo login (String telephone, String password) { long startTimestamp = System.currentTimeMillis(); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo ("login" , responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); } public UserVo register (String telephone, String password) { long startTimestamp = System.currentTimeMillis(); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo ("register" , responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); } }
很明显,上面的写法有两个问题。第一,性能计数器框架代码侵入到业务代码中,跟业务代码高度耦合。如果未来需要替换这个框架,那替换的成本会比较大。第二,收集接口请求的代码跟业务代码无关,本就不应该放到一个类中。业务类最好职责更加单一,只聚焦业务处理。
为了将框架代码和业务代码解耦,代理模式就派上用场了。代理类UserControllerProxy和原始类UserController实现相同的接口IUserController。UserController类只负责业务功能。代理类UserControllerProxy负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用原始类来执行业务代码 。具体的代码实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public interface IUserController { UserVo login (String telephone, String password) ; UserVo register (String telephone, String password) ; } public class UserController implements IUserController { @Override public UserVo login (String telephone, String password) { } @Override public UserVo register (String telephone, String password) { } } public class UserControllerProxy implements IUserController { private MetricsCollector metricsCollector; private UserController userController; public UserControllerProxy (UserController userController) { this .userController = userController; this .metricsCollector = new MetricsCollector (); } @Override public UserVo login (String telephone, String password) { long startTimestamp = System.currentTimeMillis(); UserVo userVo = userController.login(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo ("login" , responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } @Override public UserVo register (String telephone, String password) { long startTimestamp = System.currentTimeMillis(); UserVo userVo = userController.register(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo ("register" , responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } } IUserController userController = new UserControllerProxy (new UserController ());
参照基于接口而非实现编程的设计思想,将原始类对象替换为代理类对象的时候,为了让代码改动尽量少,在刚刚的代理模式的代码实现中,代理类和原始类需要实现相同的接口。 但是,如果原始类并没有定义接口 ,并且原始类代码并不是我们开发维护的(比如它来自一个第三方的类库),我们也没办法直接修改原始类 ,给它重新定义一个接口。在这种情况下,我们该如何实现代理模式呢?
对于这种外部类的扩展,我们一般都是采用继承的方式。 这里也不例外。我们让代理类继承原始类,然后扩展附加功能。原理很简单,不需要过多解释,你直接看代码就能明白。具体代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class UserControllerProxy extends UserController { private MetricsCollector metricsCollector; public UserControllerProxy () { this .metricsCollector = new MetricsCollector (); } public UserVo login (String telephone, String password) { long startTimestamp = System.currentTimeMillis(); UserVo userVo = super .login(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo ("login" , responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } public UserVo register (String telephone, String password) { long startTimestamp = System.currentTimeMillis(); UserVo userVo = super .register(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo ("register" , responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } } UserController userController = new UserControllerProxy ();
动态代理的原理解析 不过,刚刚的代码实现还是有点问题 。一方面 ,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面 ,如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。
如果有50个要添加附加功能的原始类,那我们就要创建50个对应的代理类。这会导致项目中类的个数成倍增加,增加了代码维护成本。并且,每个代理类中的代码都有点像模板式的“重复”代码,也增加了不必要的开发成本。那这个问题怎么解决呢?
我们可以使用动态代理来解决这个问题。所谓动态代理 (Dynamic Proxy) ,就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。 那如何实现动态代理呢?
如果你熟悉的是Java语言,实现动态代理就是件很简单的事情。因为Java语言本身就已经提供了动态代理的语法(实际上,动态代理底层依赖的就是Java的反射语法)。 我们来看一下,如何用Java的动态代理来实现刚刚的功能。具体的代码如下所示。其中,MetricsCollectorProxy作为一个动态代理类,动态地给每个需要收集接口请求信息的类创建代理类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class MetricsCollectorProxy { private MetricsCollector metricsCollector; public MetricsCollectorProxy () { this .metricsCollector = new MetricsCollector (); } public Object createProxy (Object proxiedObject) { Class<?>[] interfaces = proxiedObject.getClass().getInterfaces(); DynamicProxyHandler handler = new DynamicProxyHandler (proxiedObject); return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler); } private class DynamicProxyHandler implements InvocationHandler { private Object proxiedObject; public DynamicProxyHandler (Object proxiedObject) { this .proxiedObject = proxiedObject; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { long startTimestamp = System.currentTimeMillis(); Object result = method.invoke(proxiedObject, args); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; String apiName = proxiedObject.getClass().getName() + ":" + method.getName(); RequestInfo requestInfo = new RequestInfo (apiName, responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return result; } } } MetricsCollectorProxy proxy = new MetricsCollectorProxy ();IUserController userController = (IUserController) proxy.createProxy(new UserController ());
实际上,Spring AOP底层的实现原理就是基于动态代理。用户配置好需要给哪些类创建代理,并定义好在执行原始类的业务代码前后执行哪些附加功能。Spring为这些类创建动态代理对象,并在JVM中替代原始类对象。原本在代码中执行的原始类的方法,被换作执行代理类的方法,也就实现了给原始类添加附加功能的目的。
代理模式的应用场景 代理模式的应用场景非常多,我这里列举一些比较常见的用法,希望你能举一反三地应用在你的项目开发中。
1.业务系统的非功能性需求开发 代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发(这里有个问题,如何让多个非功能性需求共同生效,创建多个代理实例?还是生成代理的代理,一层层嵌套的代理? )。实际上,前面举的搜集接口请求信息的例子,就是这个应用场景的一个典型例子。
如果你熟悉Java语言和Spring开发框架,这部分工作都是可以在Spring AOP切面中完成的。前面我们也提到,Spring AOP底层的实现原理就是基于动态代理。
2.代理模式在RPC、缓存中的应用 实际上,RPC框架也可以看作一种代理模式 ,GoF的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用RPC服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
关于远程代理的代码示例,我自己实现了一个简单的RPC框架Demo,放到了GitHub中,你可以点击这里的链接 查看。
**我们再来看代理模式在缓存中的应用。**假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。比如,针对获取用户个人信息的需求,我们可以开发两个接口,一个支持缓存,一个支持实时查询。对于需要实时数据的需求,我们让其调用实时查询接口,对于不需要实时数据的需求,我们让其调用支持缓存的接口。那如何来实现接口请求的缓存功能呢?
最简单的实现方法就是刚刚我们讲到的,给每个需要支持缓存的查询需求都开发两个不同的接口,一个支持缓存,一个支持实时查询。但是,这样做显然增加了开发成本,而且会让代码看起来非常臃肿(接口个数成倍增加),也不方便缓存接口的集中管理(增加、删除缓存接口)、集中配置(比如配置每个接口缓存过期时间)。
针对这些问题,代理模式就能派上用场了,确切地说,应该是动态代理。如果是基于Spring框架来开发的话,那就可以在AOP切面中完成接口缓存的功能。在应用启动的时候,我们从配置文件中加载需要支持缓存的接口,以及相应的缓存策略(比如过期时间)等。当请求到来的时候,我们在AOP切面中拦截请求,如果请求中带有支持缓存的字段(比如http://…?..&cached=true),我们便从缓存(内存缓存或者Redis缓存等)中获取数据直接返回。
动态代理详细解析(扩展) 静态代理 代理模式 - 菜鸟
JDK动态代理 JDK中的动态代理是通过 反射类 Proxy 以及 InvocationHandler 回调接口 实现的,JDK中所有要进行动态代理的类必须要实现一个接口 ,也就是说只能对该类所实现接口中定义的方法进行代理 ,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
动态代理神奇的地方就是:
代理对象是在程序运行时产生的,而不是编译期;
对代理对象的所有接口方法调用都会转发到 InvocationHandler.invoke()
方法 ,在 invoke()
方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体。
注意:对于从Object中继承的方法,JDK Proxy会把 hashCode()
、equals()
、toString()
这三个非接口方法转发给 InvocationHandler
,其余的Object方法则不会转发。详见JDK Proxy官方文档 。
通过秒表代理创建动态代理类,实现程序执行时间的计算:
接口 被代理类(原始类)必须要实现的接口
1 2 3 4 5 6 public interface IUserService { public String selectPasswdById (Integer userId) ; }
原始类 被代理类(原始类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.util.concurrent.TimeUnit;public class UserServiceImpl implements IUserService { @Override public String selectPasswdById (Integer userId) { System.out.println("通过用户ID查询: selectById" ); try { TimeUnit.MILLISECONDS.sleep(100 ); } catch (InterruptedException e) { throw new RuntimeException (e); } return String.valueOf(userId); } }
实现InvocationHandler接口 实现 java.lang.reflect.InvocationHandler 接口,定义一个调用处理程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import java.lang.reflect.Method;import org.apache.commons.lang3.StringUtils;import org.apache.commons.lang3.time.StopWatch;public class CustomInvocationHandler implements java .lang.reflect.InvocationHandler { private Object proxiedObject; public CustomInvocationHandler (Object proxiedObject) { this .proxiedObject = proxiedObject; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { StopWatch stopWatch = StopWatch.createStarted(); System.out.println("原始方法调用前" ); Object result = method.invoke(proxiedObject, args); System.out.println("原始方法调用后" ); stopWatch.stop(); String apiName = proxy.getClass().getName() + ":" + method.getName(); System.out.println(apiName + " 执行耗时: " + stopWatch.getTime() + "ms" ); return StringUtils.join("代理修改了结果: " , "21218CCA77804D2BA1922C33E0151105" ); } }
创建动态代理类 可以称为 创建代理对象的工厂 吧。实际上将 InvocationHandler 的实现类作为 private 修饰的内部类更加合适,由于下文 CGLIB 会用到,所以放到外面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class CustomProxy { public static Object createCustomProxy (Object proxiedObject) { ClassLoader classLoader = proxiedObject.getClass().getClassLoader(); Class<?>[] interfaces = proxiedObject.getClass().getInterfaces(); CustomInvocationHandler handler = new CustomInvocationHandler (proxiedObject); return java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, handler); } }
使用动态代理类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Test { public static void main (String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles" , "true" ); System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles" , "true" ); IUserService userService = (IUserService) CustomProxy.createCustomProxy(new UserServiceImpl ()); System.out.println(userService.selectPasswdById(123456 )); System.out.println(); System.out.println("代理类 : " + userService.getClass()); System.out.println("代理类的父类: " + userService.getClass().getSuperclass()); System.out.println("代理类的实现: " + Arrays.toString(userService.getClass().getInterfaces())); if (java.lang.reflect.Proxy.isProxyClass(userService.getClass())) { java.lang.reflect.InvocationHandler handler = java.lang.reflect.Proxy.getInvocationHandler(userService); System.out.println("调用处理程序: " + handler.getClass()); } } }
输出:
1 2 3 4 5 6 7 8 9 10 原始方法调用前 通过用户ID查询: selectById 原始方法调用后 com.sun.proxy.$Proxy0:selectPasswdById 执行耗时: 105ms 代理修改了结果: 21218CCA77804D2BA1922C33E0151105 代理类 : class com.sun.proxy.$Proxy0 代理类的父类: class java.lang.reflect.Proxy 代理类的实现: [interface com.zhaolq.mars.demo.a.IUserService] 调用处理程序: class com.zhaolq.mars.demo.a.CustomInvocationHandler
问题 InvocationHandler
的 invoke
方法的第一个参数 proxy
好像从来没有用过 。
为什么JDK的动态代理一定要基于接口实现 查看代理类源码可知,其继承了 java.lang.reflect.Proxy
类,实现了被代理的接口。由于Java是单继承,不能再继承其他类, 所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
proxy代表什么意思 proxy是真实对象的真实代理对象,invoke方法可以返回调用代理对象方法的返回结果,也可以返回对象的真实代理对象(com.sun.proxy.$Proxy0)。
proxy参数怎么用及什么时候用 当需要代理后的方法返回代理对象进行后续调用时使用。
proxy参数的类型是什么com.zhaolq.mars.demo.$Proxy0
(被代理接口为非public)或 com.sun.proxy.$Proxy0
(被代理接口为public)真实的代理对象。
为什么不用 this
代替 proxy
因为 this
代表的是 InvocationHandler
接口实现类本身,并不是真实的代理对象。
CGLIB(Code Generation Library) 惊喜!!! 将上文实现的 JDK 动态代理示例中所有的
java.lang.reflect.Proxy 、 java.lang.reflect.InvocationHandler
修改为
net.sf.cglib.proxy.Proxy 、 net.sf.cglib.proxy.InvocationHandler
代理一样可以运行,同样的,原始类必须实现接口。由此可见,CGLIB 实现了 JDK 动态代理 的相同功能,JDK 动态代理 是 CGLIB 的子集。
什么是CGLIB? CGLIB 是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理 ,为 JDK 动态代理 提供了很好的补充 。通常可以使用 Java 的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能 ,CGLIB是一个好的选择。
CGLIB 作为一个开源项目,其代码托管在 Github,地址为:https://github.com/cglib/cglib
CGLIB原理 CGLIB 原理 :动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB 底层 :使用字节码处理框架ASM ,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
CGLIB缺点 :对于final方法,无法进行代理。
CGLIB的应用 广泛的被许多AOP的框架使用,例如Spring AOP和dynaop。Hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联。
为什么使用CGLIB? CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。 如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。
CGLIB组成结构
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解。
CGLIB的API Maven依赖
1 2 3 4 5 <dependency > <groupId > cglib</groupId > <artifactId > cglib</artifactId > <version > 3.3.0</version > </dependency >
CGLIB类库
由于基本代码很少,学起来有一定的困难,主要是缺少文档和示例,这也是CGLIB的一个不足之处。
本系列使用的CGLIB版本是3.3.0。
net.sf.cglib.core : 底层字节码处理类,他们大部分与ASM有关系。
net.sf.cglib.transform : 编译期或运行期类和类文件的转换
net.sf.cglib.proxy : 实现创建代理和方法拦截器的类
net.sf.cglib.reflect : 实现快速反射和C#风格代理的类
net.sf.cglib.util : 集合排序等工具类
net.sf.cglib.beans : JavaBean相关的工具类
本篇介绍通过MethodInterceptor和Enhancer实现一个动态代理。
CGLIb实现动态代理 使用CGLIb实现动态代理,完全不受代理类必须实现接口的限制 ,而且CGLIb底层采用ASM字节码生成框架 ,使用字节码技术 生成代理类,比使用Java反射效率要高 。唯一需要注意的是,CGLIb不能对声明为final的方法进行代理,因为CGLIb原理是动态生成被代理类(原始类)的子类,final修饰的方法不能被重写。
下面,将通过一个实例介绍使用CGLIb实现动态代理。
原始类 被代理类(原始类),没有实现任何接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.util.concurrent.TimeUnit;public class UserServiceImpl { public String selectPasswdById (Integer userId) { System.out.println("通过用户ID查询: selectById" ); try { TimeUnit.MILLISECONDS.sleep(100 ); } catch (InterruptedException e) { throw new RuntimeException (e); } return String.valueOf(userId); } public int methodOne (int count) { return count; } public String methodTwo (String paramName) { return paramName; } }
实现MethodInterceptor接口 创建 Callback 接口的实现,可以实现子接口 net.sf.cglib.proxy.MethodInterceptor、net.sf.cglib.proxy.InvocationHandler,更多子接口见源码。
定义一个方法拦截器。在调用原始方法时,CGLIb 会回调 MethodInterceptor 接口方法拦截,来实现自己的代理逻辑,类似于 JDK 中的 InvocationHandler 接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.lang.reflect.Method;import org.apache.commons.lang3.StringUtils;import org.apache.commons.lang3.time.StopWatch;import net.sf.cglib.proxy.MethodProxy;public class CustomInterceptor implements net .sf.cglib.proxy.MethodInterceptor { @Override public Object intercept (Object obj, Method method, Object[] params, MethodProxy proxy) throws Throwable { StopWatch stopWatch = StopWatch.createStarted(); System.out.println("原始方法调用前" ); Object result = proxy.invokeSuper(obj, params); System.out.println("原始方法调用后" ); stopWatch.stop(); String apiName = obj.getClass().getName() + ":" + method.getName(); System.out.println(apiName + " 执行耗时: " + stopWatch.getTime() + "ms" ); return StringUtils.join("代理修改了结果: " , "21218CCA77804D2BA1922C33E0151105" ); } }
定义一个调用处理程序 ,**注意不是 JDK 中的 InvocationHandler 接口,这里复用上文的 CustomInvocationHandler,除了实现的接口来自不同包以外,代码完全相同。**见 惊喜!!! 目录。
创建和使用动态代理类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Test { public static void main (String[] args) { Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(UserServiceImpl.class); enhancer.setCallback(new CustomInterceptor ()); UserServiceImpl userService = (UserServiceImpl) enhancer.create(); System.out.println(userService.selectPasswdById(123456 )); System.out.println(); System.out.println("代理类 : " + userService.getClass()); System.out.println("代理类的父类: " + userService.getClass().getSuperclass()); System.out.println("代理类的实现: " + Arrays.toString(userService.getClass().getInterfaces())); if (net.sf.cglib.proxy.Proxy.isProxyClass(userService.getClass())) { net.sf.cglib.proxy.InvocationHandler handler = net.sf.cglib.proxy.Proxy.getInvocationHandler(userService); System.out.println("调用处理程序: " + handler.getClass()); } } }
输出:
1 2 3 4 5 6 7 8 9 原始方法调用前 通过用户ID查询: selectById 原始方法调用后 com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$fddb78dd:selectPasswdById 执行耗时: 122ms 代理修改了结果: 21218CCA77804D2BA1922C33E0151105 代理类 : class com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$fddb78dd 代理类的父类: class com.zhaolq.mars.demo.a.UserServiceImpl 代理类的实现: [interface net.sf.cglib.proxy.Factory]
Callback[]和CallbackFilter 前面的动态代理类代理了原始类中的所有方法,那么如何让不同方法执行不同的回调(代理)逻辑呢?
Enhancer 类的 public void setCallbacks(Callback[] callbacks)
和 public void setCallbackFilter(CallbackFilter filter)
方法可以设置对不同方法执行不同的回调(代理)逻辑,或者根本不执行回调。
在 JDK 动态代理中并没有类似的功能,对 InvocationHandler 接口方法的调用对代理类内的所以方法都有效。
实现FixedValue接口 FixedValue 也是 Callback 的子接口,表示返回固定值的回调。
注意:loadObject() 方法的返回值类型必须与代理方法的返回值类型兼容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import net.sf.cglib.proxy.FixedValue;public class CustomResultFixedValue implements FixedValue { @Override public Object loadObject () throws Exception { Object obj = 999 ; return obj; } }
实现CallbackFilter接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import java.lang.reflect.Method;public class CustomMethodCallbackFilter implements net .sf.cglib.proxy.CallbackFilter { @Override public int accept (Method method) { if (method.getName().equals("toString" )) { System.out.println("filter toString == 0" ); return 0 ; } if (method.getName().equals("selectPasswdById" )) { System.out.println("filter selectPasswdById == 1" ); return 1 ; } if (method.getName().equals("methodOne" )) { System.out.println("filter method1 == 2" ); return 2 ; } return 0 ; } }
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import net.sf.cglib.proxy.Callback;import net.sf.cglib.proxy.CallbackFilter;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.NoOp;public class Test { public static void main (String[] args) { Callback noOpCallback = NoOp.INSTANCE; Callback interceptor = new CustomInterceptor (); Callback fixedValue = new CustomResultFixedValue (); Callback[] callbackArray = new Callback []{noOpCallback, interceptor, fixedValue}; CallbackFilter callbackFilter = new CustomMethodCallbackFilter (); Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(UserServiceImpl.class); enhancer.setCallbackFilter(callbackFilter); enhancer.setCallbacks(callbackArray); UserServiceImpl userService = (UserServiceImpl) enhancer.create(); ConsoleKeyValue.create() .addKeyValue("selectPasswdById" , userService.selectPasswdById(123456 )) .addKeyValue("methodOne" , String.valueOf(userService.methodOne(100 ))) .addKeyValue("methodTwo" , userService.methodTwo("m2" )) .addKeyValue("toString" , userService.toString()) .print(); } }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 filter method1 == 2 filter selectPasswdById == 1 filter toString == 0 原始方法调用前 通过用户ID查询: selectById 原始方法调用后 com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$675f4824:selectPasswdById 执行耗时: 112ms +----+--------------------+------------------------------------------------------------------------------+ selectPasswdById: 代理修改了结果: 21218CCA77804D2BA1922C33E0151105 methodOne: 999 methodTwo: m2 toString: com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$675f4824@25a65b77 +----+--------------------+------------------------------------------------------------------------------+
延迟加载对象(懒加载) 说到延迟加载,应该经常接触到,尤其是使用Hibernate的时候,本篇将通过一个实例分析延迟加载的实现方式。
两种延迟加载接口可用于创建延迟加载对象, 都继承了Callback,所以都是Callback类型(回调类型):
LazyLoader:只在第一次使用 延迟加载对象时触发代理类回调方法
Dispatcher:每次使用 延迟加载对象时都会触发代理类回调方法
**示例:**定义一个实体类LoaderBean,该Bean内有一个需要延迟加载的属性PropertyBean。
原始类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class PropertyBean { private String key; private Object value; public String getKey () { return key; } public void setKey (String key) { this .key = key; } public Object getValue () { return value; } public void setValue (Object value) { this .value = value; } @Override public String toString () { return "PropertyBean [key=" + key + ", value=" + value + "]" ; } }
实现LazyLoader接口 1 2 3 4 5 6 7 8 9 10 11 public class ConcreteClassLazyLoader implements net .sf.cglib.proxy.LazyLoader { @Override public Object loadObject () throws Exception { System.out.println("before lazyLoader..." ); PropertyBean propertyBean = new PropertyBean (); propertyBean.setKey("objectFromLazyLoader" ); propertyBean.setValue(new Object ()); System.out.println("after lazyLoader..." ); return propertyBean; } }
实现Dispatcher接口 1 2 3 4 5 6 7 8 9 10 11 public class ConcreteClassDispatcher implements net .sf.cglib.proxy.Dispatcher { @Override public Object loadObject () throws Exception { System.out.println("before Dispatcher..." ); PropertyBean propertyBean = new PropertyBean (); propertyBean.setKey("objectFromDispatcher" ); propertyBean.setValue(new Object ()); System.out.println("after Dispatcher..." ); return propertyBean; } }
创建延迟加载对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 import net.sf.cglib.proxy.Enhancer;public class LazyBean { private String name; private int age; private PropertyBean propertyBeanLazyLoader; private PropertyBean propertyBeanDispatcher; public LazyBean (String name, int age) { System.out.println("lazy bean init" ); this .name = name; this .age = age; this .propertyBeanLazyLoader = createPropertyBeanLazyLoader(); this .propertyBeanDispatcher = createPropertyBeanDispatcher(); } private PropertyBean createPropertyBeanLazyLoader () { Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(PropertyBean.class); PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class, new ConcreteClassLazyLoader ()); return pb; } private PropertyBean createPropertyBeanDispatcher () { Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(PropertyBean.class); PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class, new ConcreteClassDispatcher ()); return pb; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public PropertyBean getPropertyBeanLazyLoader () { return propertyBeanLazyLoader; } public void setPropertyBeanLazyLoader (PropertyBean propertyBeanLazyLoader) { this .propertyBeanLazyLoader = propertyBeanLazyLoader; } public PropertyBean getPropertyBeanDispatcher () { return propertyBeanDispatcher; } public void setPropertyBeanDispatcher (PropertyBean propertyBeanDispatcher) { this .propertyBeanDispatcher = propertyBeanDispatcher; } }
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test { public static void main (String[] args) { LazyBean lazyBean = new LazyBean ("zhangsan" , 18 ); System.out.println(lazyBean.getName() + ": " + lazyBean.getAge()); System.out.println(lazyBean.getName() + ": " + lazyBean.getAge()); System.out.println(); System.out.println(lazyBean.getPropertyBeanLazyLoader()); System.out.println(lazyBean.getPropertyBeanLazyLoader()); System.out.println(); System.out.println(lazyBean.getPropertyBeanDispatcher()); System.out.println(lazyBean.getPropertyBeanDispatcher()); } }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 zhangsan: 18 zhangsan: 18 before lazyLoader... after lazyLoader... PropertyBean [key=objectFromLazyLoader, value=java.lang.Object@58c1c010] PropertyBean [key=objectFromLazyLoader, value=java.lang.Object@58c1c010] before Dispatcher... after Dispatcher... PropertyBean [key=objectFromDispatcher, value=java.lang.Object@b7f23d9] before Dispatcher... after Dispatcher... PropertyBean [key=objectFromDispatcher, value=java.lang.Object@61d47554]
接口生成器InterfaceMaker InterfaceMaker 会动态生成一个接口,该接口包含指定类定义的所有方法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.InterfaceMaker;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class Test { public static void main (String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { InterfaceMaker interfaceMaker = new InterfaceMaker (); interfaceMaker.add(UserServiceImpl.class); Class<?> userServiceInterface = interfaceMaker.create(); for (Method method : userServiceInterface.getMethods()) { System.out.println(method.getName()); } System.out.println(); Object object = Enhancer.create(Object.class, new Class []{userServiceInterface}, new MethodInterceptor () { @Override public Object intercept (Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if (method.getName().equals("selectPasswdById" )) { System.out.println("filter selectPasswdById " ); return "aaa" ; } if (method.getName().equals("methodOne" )) { System.out.println("filter methodOne " ); return 123456 ; } if (method.getName().equals("methodTwo" )) { System.out.println("filter methodTwo " ); return "ccc" ; } return "default" ; } }); Method targetMethod1 = object.getClass().getMethod("methodOne" , new Class []{int .class}); int i = (int ) targetMethod1.invoke(object, new Object []{666666 }); System.out.println(i); Method targetMethod = object.getClass().getMethod("methodTwo" , new Class []{String.class}); String str = (String) targetMethod.invoke(object, new Object []{"abc" }); System.out.println(str); } }
输出:
1 2 3 4 5 6 7 8 selectPasswdById methodOne methodTwo filter methodOne 123456 filter methodTwo ccc
SpringBoot中的AOP使用哪种代理? Spring 和 SpringBoot 在动态代理的策略上是否相同,具体还要看哪个版本。
SpringBoot 中可以查看 AopAutoConfiguration
类,这个自动化配置主要是在讨论 application.properties 配置文件中的 spring.aop.proxy-target-class
属性的值。
具体起作用的是 @ConditionalOnProperty
注解,关于这个注解中的几个属性,松哥也来稍微说下:
prefix:配置文件的前缀。
name:配置文件的名字,和 prefix 共同组成配置的 key。
havingValue:期待配置的值,如果实际的配置和 having 的值相同,则这个配置类就会生效,否则不生效。
matchIfMissing:如果开发者没有在 application.properties 中进行配置,那么这个配置类是否生效。
基于如上介绍,我们很容易看出:
如果开发者设置了 spring.aop.proxy-target-class
为 false,则使用 JDK 代理。
如果开发者设置了 spring.aop.proxy-target-class
为 true,则使用 Cglib 代理。
如果开发者一开始就没配置 spring.aop.proxy-target-class
属性,则使用 Cglib 代理(SpringBoot2.0 )。
在 Controller 中 IUserService 的调用处打断点,DEBUG 运行一下,查看代理实例的变量信息(userService),会有大大的标志。
重点回顾 好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要掌握的重点内容。
1.代理模式的原理与实现
在不改变原始类(或叫被代理类)的情况下,通过引入代理类来给原始类附加功能。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。
2.动态代理的原理与实现
静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了维护成本和开发成本。对于静态代理存在的问题,我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
3.代理模式的应用场景
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在RPC、缓存等应用场景中。
课堂讨论
除了Java语言之外,在你熟悉的其他语言中,如何实现动态代理呢?
我们今天讲了两种代理模式的实现方法,一种是基于组合,一种基于继承,请对比一下两者的优缺点。
欢迎留言和我分享你的思考,如果有收获,也欢迎你把这篇文章分享给你的朋友。