想必搞 Java 的对这个代理模式至少不会很陌生,只要接触过 Spring AOP 相关的业务或者知识点都应该知道在 Spring 中 AOP 通过 CGLIB 和 JDK 动态代理实现切面编程的。在设计模式中,代理模式有着丰富的使用场景和代理类型,这些代理类型适用于各种不同的业务场景,代理模式也是属于七个结构型模式之一,其余六个是:
- 适配器模式 – Adapter Pattern
- 桥接模式 – Bridge Pattern
- 组合模式 – Composite Pattern
- 外观模式 – Facade Pattern
- 享元模式 – Flyweight Pattern
- 装饰模式 – Decorator Pattern
代理模式的定义
代理模式是为其他对象提供一种代理以控制对这个对象的访问。即给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。代理在生活中处处可见,微商代购、代购网站、房屋中介、黄牛、快递等你都可以理解为是一种代理模式的一种体现,说白了就是将内部的对象隐藏了起来,有些时候这个真实要被访问的对象可能会因为一些不可抗力的因素无法对其直接访问,所以在真实对象的基础上衍生出代理对象作为中介,既可以为客户去掉不想让他看到的内容,也可以在真实对象的基础上添加额外的服务。
代理模式的结构及角色
代理模式首先要求将核心业务声明出一个抽象层,然后真实的对象和代理对象共同继承或者实现抽象层,这样能够保证任何需要使用真实对象的地方都能够使用代理对象。
- Subject(抽象主题角色):声明真实对象和代理对象的共同接口,保证在使用真实对象的地方都可以使用代理对象。
- Proxy(代理主题角色):代理对象需要包含对象真实对象的引用,从而可以在任何时候任何地点保证对真实对象的调用,甚至对真实对象的约束和修饰。
- RealSubject(真实主题角色):实现真正的核心业务逻辑,客户端通过代理,代理再通过调用真实对象实现的核心操作完成业务逻辑。
举一个极其简单的例子演示一下代理模式的应用,就拿微商代购的生活例子好了,我们在生活中总是需要购买一些特定的商品,而这些商品可能因为地域的关系在当地买不到,又或者当地的这款商品因为不可描述的原因太贵,买了肉疼。所以一些微商代购应运而生,他们的主要职责就是做购买代理人,可以帮助我们全国各地飞,帮我们买到相对来讲价格便宜的并且还是我们所需要的商品,比如女孩子的各种打牌化妆品。。。。
首先我们抽象出真实对象的核心业务方式,也就是买无法直接购买的商品。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package learn.design.patterns.proxy; /** * 设计模式之代理模式 * * @ClassName: Person * @author: glorze.com * @since: 2020/7/12 16:20 */ public interface Person { /** * 人需要买东西 * @Title: shop * @return void */ void shop(); } |
创建真正的买家类,实现买东西的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package learn.design.patterns.proxy; /** * 设计模式之代理模式 * * @ClassName: TrueShopper * @author: 高老四博客 * @since: 2020/7/12 16:22 */ public class TrueShopper implements Person { @Override public void shop() { // 因为自己买太贵,需要找代购买免税的商品 System.out.println("我要买一只免税的萝卜丁口红!"); } } |
最后创建代理人类,该代理类也要实现人买东西的接口,并且构造真正买家的引用,在此基础上增强买家类的实现。
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 |
package learn.design.patterns.proxy; /** * 设计模式之代理模式 * * @ClassName: ProxyPerson * @author: glorze.com * @since: 2020/7/12 16:24 */ public class ProxyPerson implements Person { private TrueShopper trueShopper; public ProxyPerson(TrueShopper trueShopper) { this.trueShopper = trueShopper; } @Override public void shop() { System.out.println("帮助真正的买家去免税店购买商品!"); this.trueShopper.shop(); System.out.println("帮助真正的买家去购买萝卜丁口红!"); } public static void main(String[] args) { ProxyPerson proxyPerson = new ProxyPerson(new TrueShopper()); proxyPerson.shop(); } } |
代理模式的类型及对应的适用场景
我们能感知到,代理模式作为中介本身,除了协调客户端和真实对象,那么中介自己本身就能衍生出各种各样的功能,从而能够更好的协调两者。所以根据代理模式的核心思想以及实际的应用场景解决方案,能够总结出如下代理模式的类型,这些类型都对应的一定场景的问题解决方案。
- 远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。.远程代理对象承担了主要的网络通信工作,并且负责对远程业务方法的调用,这样客户端完全可以认为被代理的远程对象是在本地而不是远程。
- 虚拟代理:某些情况下可能需要创建一个开销很大的对象,那么虚拟代理就是先创建一个开销相对比较小的对象,等到真正调用真实对象的核心职责的时候在创建真实对象。
- 安全代理:用来控制真实对象访问时的权限。
- 智能指引(引用)代理:是指当调用真实的对象时,代理处理另外一些事实。比如记录对象的调用次数,记录调用前后的日志,接口监控等等。
- 缓冲代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
代理模式的优缺点
代理模式的优势:
- 作为中间方可以协调客户端和被调用者,系统解耦。
- 作为中间方既可以保护目标对象,又可以在目标对象的基础上对其增强。
- 符合开放-封闭原则,可以随时替换掉代理类,保证整个系统的灵活性和扩展性。
代理模式的劣势:
- 导致系统中类的数量增加,而且中间加了一层代理类,可能会带来处理速度变慢的问题。
- 增加系统的复杂度。像下文讲述的动态代理等,需要一定的编程语言底层技术,从而导致系统复杂度以及维护力度提升。
代理模式在 Java & Spring 中的应用
从上文中我们知道代理模式主要就是帮助我们保护目标对象或者是对目标对象进行增强。其实在上述对代理模式的描述中没有提及到代理模式的两种结构,因为结构型模式角度来区分,代理模式可以区分为「静态代理」和「动态代理」,上文描述的都是基于静态代理来讲解的,静态代理的特点就是需要与被代理对象进行手动同步,如果被代理类新增业务方法,那么对应的代理类也需要新增,这对于大型的系统建设来讲是灾难性的。
动态代理相对于静态代理,其实解决的就是静态代理不能通用扩展的问题,也就是抽象的解决方案,减少静态代理的成本。动态代理可以通过成熟的框架,或者编程语言的底层反射知识实现,比如 Java 中的反射。思路就是在代理类的运行过程中,动态地将被代理对象实例化对象,根据客户端执行的方法名称变量去动态的执行对应真实对象的业务方法。在 Spring 中,代理主要是应用在 AOP 面向切面编程中,Spring 中主要使用了两种动态代理,分别是 CGLIB 和 JDK 动态代理。JDK 动态代理老四在之前的关于 Java 反射的文章详细的讲解过,可以参考文末相关阅读索引直达。
动态代理之 CGLIB 和 JDK 动态代理
就拿代购的例子,我们分别使用 CGLIB 和 JDK 动态代理来进行改造,并且体会一下其中异同。
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 |
package learn.design.patterns.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 代购微商产业 * * @ClassName: MicroBusiness * @author: glorze.com * @since: 2020/7/12 16:45 */ public class MicroBusiness implements InvocationHandler { /** * 目标代理对象 */ private Person target; public MicroBusiness(Person target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { bfore(); Object person = method.invoke(this.target, args); after(); return person; } private void bfore() { System.out.println("帮助真正的买家去免税店购买商品!"); } private void after() { System.out.println("帮助真正的买家去购买萝卜丁口红!"); } public static void main(String[] args) { Person person = new TrueShopper(); MicroBusiness microBusiness = new MicroBusiness(person); Person p = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[] {Person.class}, microBusiness); // 动态代理对象调用目标对象方法实现invoke方法的调用 p.shop(); } } |
这是通过动态代理改造的代理模式,通过这样的方式我们会发现,代理类与买家类在客户端可以真正的分离,目标代理对象可以随意替换成其他的可代理对象,而不是像静态代理那样,代理人类与真正的买家类互相绑定在一起,违反开放-封闭原则。
接下来我们再看一下使用 CGLIB 的实现。CGLIB 的本质是通过继承要被代理的类来实现代理的增强或者保护的,不需要事先任何接口。代理类会获得所有从父类继承来的方法,并且会有 MethodProxy 与之对应。CGLIB 执行代理方法的效率是比 JDK 动态代理要高的,因为采用了 FastClass 机制,底层为代理类和被代理类各生成一个类,然后为他们的方法分配一个 index,然后 FastClass 可以直接定位要调用的方法直接调用,省去反射的开销。
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 |
package learn.design.patterns.proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 代购微商产业 * * @ClassName: MicroBusinessCglib * @author: glorze.com * @since: 2020/7/12 16:45 */ public class MicroBusinessCglib implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object object = methodProxy.invoke(o, objects); after(); return object; } private void before() { System.out.println("帮助真正的买家去免税店购买商品!"); } private void after() { System.out.println("帮助真正的买家去购买萝卜丁口红!"); } public static void main(String[] args) { TrueShopper trueShopper = new TrueShopper(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(trueShopper.getClass()); TrueShopper ts = (TrueShopper) enhancer.create(); ts.shop(); } } |
静态代理和动态代理的区别
- 静态代理只能手动完成代理操作,如果被代理类新增了业务方法,代理类也需要进行同步调用的处理,违反开放-封闭原则。
- 动态代理采用的是运行是利用变成语言的底层技术实现被代理对象的动态调用,即使被代理类进行了扩展,也不需要进行同步修改,符合七个面向对象设计原则中的开放-封闭原则。
Spring 中 AOP 源码简单分析
关于 Spring 中 AOP 的基本使用,老四之前简单的分析过,可以参考文末相关阅读索引直达。首先来看一下 AOP 模块的一些核心类 UML 设计图。
再来简单的梳理一下 Spring 是如何通过动态代理实现 AOP 的。首先从上图我们能看到 ProxyFactoryBean 就是代理类的工厂,Bean 对象要从这里获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * 在 Spring 中如果不做任何设置,那么 Spring 代理生成的 Bean 都是单例对象。 * 如果修改 scope,则每次创建一个新的原型对象。 * @return a fresh AOP proxy reflecting the current state of this factory */ @Override @Nullable public Object getObject() throws BeansException { initializeAdvisorChain(); if (isSingleton()) { return getSingletonInstance(); } else { if (this.targetName == null) { logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property."); } return newPrototypeInstance(); } } |
Spring 实现动态代理有两个类,当然就是 JDK 动态代理和 CGLIB,分别对应的类是「JdkDynamicAopProxy」、「CglibAopProxy」,当 Bean 有实现接口时,Spring 就会用 JDK 动态代理,当 Bean 没有实现接口时,Spring 会选择 CGLIB 代理。准备工作之后,我们可以看一下 AOP 的实现流程,可以从「入口」->「选择代理策略」->「调用代理方法」->「触发通知」的流程总体来看。
入口
AOP 的入口就是 BeanPostProcessor 后置触发器,我们知道 Bean 的生命周期,主要分为获取 Bean、创建 Bean、销毁 Bean,创建 Bean 的过程中又分为「创建之前的预处理」和「真正的创建 Bean」,而在真正的创建 Bean 的过程中,调用的是 doCreateBean 方法,这个方法负责通过构造实例化 Bean,对 Bean 进行填充,后置处理器等,这个后置处理器 BeanPostProcessor 就是 AOP 的入口。
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 |
package org.springframework.beans.factory.config; import org.springframework.beans.BeansException; import org.springframework.lang.Nullable; /** * 后置处理器 * * 是一个监听器,可以监听触发的 Bean 声明周期时间。向容器注册后置处理器以后,容器中管理的 Bean 就具备了接收 IoC 容器回调时间的能力 */ public interface BeanPostProcessor { /** * 在 Bean 的初始化前提供回调入口 * @param bean the new bean instance * @param beanName the name of the bean * @return the bean instance to use, either the original or a wrapped one; * @throws org.springframework.beans.BeansException in case of errors * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet */ @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /** * 在 Bean 的初始化之后提供回调入口 * @param bean the new bean instance * @param beanName the name of the bean * @return the bean instance to use, either the original or a wrapped one; * @throws org.springframework.beans.BeansException in case of errors */ @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } } |
由于 AbstractAutowireCapableBeanFactory 的 doCreateBean 方法比较长,这里只说一下这个方法的总流程。
- 如果是单例则需要首先清除缓存
- 实例化 bean,将 BeanDefinition 转换为 BeanWrapper,如果存在工厂方法则使用工厂方法进行初始化,一个类有多个构造函数,每个构造函数都有不同的参数,所以需要根据参数锁定构造函数并进行初始化,如果既不存在工厂方法也不存在带有参数的构造函数,则使用默认的构造函数进行 bean 的实例化。
- MergedBeanDefinitionPostProcessors 的应用。bean 合并后的处理,Autowired 注解正式通过此方法实现注入类型的预解析
- 依赖处理
- 属性填充
- 循环依赖检查
- 注册 DisposableBean
- 完成创建并返回
这个过程中 initializeBean 方法负责调用用户设定的初始化方法,同时也负责后置处理器的应用。
选择代理策略
对于代理策略的选择,无非就是 JDK 动态代理还是 CGLIB 了,Spring 中我们可以参考 AbstractAutoProxyCreator 对于 BeanPostProcessor 的实现,这个类的核心方法就是 wrapIfNecessary 方法,此方法负责找出指定 bean 对应的增强器,据找出的增强器创建代理。
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 |
/** * 找出指定 bean 对应的增强器 * 根据找出的增强器创建代理 */ protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 如果已经处理过 if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } // 判断是否应该代理这个 Bean if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } // 给定的 bean 类是否代表一个基础设施类,不应代理,或者配置了指定 bean 不需要自动代理 // 判断是否是一些 InfrastructureClass 或者是否应该跳过这个 Bean // shouldSkip 默认返回 false if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // 如果有通知就创建代理 // 获取这个 Bean 的通知 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } |
在 createProxy 方法中,proxyFactory.getProxy 就是对 JDK 动态代理或者 CGLIB 的选择,对于选择的判断,我们继续参考 DefaultAopProxyFactory 的 createAopProxy 方法,这里就是前文描述的看 Bean 对象是否实现了接口或者配置文件配置了对应的代理方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } } |
对代理的调用
代理的调用就是代理模式的正是应用了,这里可以参考前面提到的「JdkDynamicAopProxy」类的源码了,我们会看到一样会去实现 InvocationHandler 接口,生成的代理对象的方法调用会直接委派 invoke 方法,然后通过拦截器,执行 AOP 中的连接点,最后实现通知。这里面关于通知的代码相对来讲还是比较复杂的,可以单独写几篇文章了,感兴趣的可以去找找 Spring 相关的源码书籍参考一下。
相关阅读
- 《浅析Java反射系列相关进阶知识(下)之JDK动态代理及反射泛型》
- 《Spring AOP 的基本使用(下)以及浅析 Spring AOP 源码》
- 《浅析设计模式第十七章之适配器模式》
- 《浅析设计模式第十二章之外观模式》
- 《浅析设计模式第六章之装饰模式》
- 《浅析设计模式第四章之开放-封闭原则》
- 《IntelliJ IDEA 导入 Spring 源码教程》
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击「给你买杜蕾斯」),也可以加入本站封闭式交流论坛「DownHub」开启新世界的大门,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。