设计模式学习笔记 - 开源实战四(中):剖析Spring框架中用来支持扩展的设计模式

概述

上篇文章,学习了 Spring 框架背后蕴含的设计思想,比如约定优于配置、低侵入松耦合、模块化轻量级等等。这些设计思想可以借鉴到其他框架开发中,在大的设计层面提高框架的代码质量。

除了上篇文章降到的设计思想,实际上,可扩展也是大部分框架应该具备的一个重要特性。所谓框架可扩展,就是框架使用中在不修改框架源码的情况下,基于扩展点定制扩展新的功能。

前面在理论部分,曾经讲过,常用来实现扩展特性的设计模式有:观察者模式、模板模式、职责链模式、策略模式等。本章,再剖析下 Spring 框架为了支持可扩展特性用的 2 种设计模式:观察者模式和模板模式。


观察者模式在 Spring 中的应用

前面章节讲过,Java、Google Guava 都提供了观察者模式的实现框架。Java 提供的框架比较简单,只包含 java.util.Observablejava.util.Observer 两个类。Google Guava 提供的框架功能比较完善和强大:通过 EventBus 事件总线来实现观察者模式。实际上,Spring 也提供了观察者模式的实现框架。

Spring 中实现的观察者模式包含三部分:Event 事件(相当于消息)、Listener 监听者(相当于观察者)、Publisher 发送者(相当于被观察者)。通过一个例子来看下,Spring 提供的观察者模式是怎么使用的。

public class DemoEvent extends ApplicationEvent {private String message;public DemoEvent(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}
}// Listener监听者
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {@Overridepublic void onApplicationEvent(DemoEvent demoEvent) {String message = demoEvent.getMessage();System.out.println(message);}
}// Publisher发送者
@Component
public class DemoPublisher {@Autowiredprivate ApplicationContext applicationContext;public void publishEvent(DemoEvent demoEvent) {this.applicationContext.publishEvent(demoEvent);}
}

从代码中可以看出,框架使用起来并不复杂,主要包含三部分工作:定义一个继承 ApplicationEvent 的事件(DemoEvent);定义了一个实现 ApplicationListener 的监听器(DemoListener);定义了一个发送者(DemoPublisher),发送者调用 ApplicationContext 来发送事件消息。

其中,ApplicationEventApplicationListener 的代码实现都非常简单,内部并不包含太多属性和方法。实际上,它们最大的作用就是做类型标识用(继承 ApplicationEvent 的类是事件,实现 ApplicationListener 的类是监听器)。

public abstract class ApplicationEvent extends EventObject {private static final long serialVersionUID = 7099057708183571937L;private final long timestamp = System.currentTimeMillis();public ApplicationEvent(Object source) {super(source);}public final long getTimestamp() {return this.timestamp;}
}public class EventObject implements java.io.Serializable {private static final long serialVersionUID = 5516075349620653480L;protected transient Object  source;public EventObject(Object source) {if (source == null)throw new IllegalArgumentException("null source");this.source = source;}public Object getSource() {return source;}public String toString() {return getClass().getName() + "[source=" + source + "]";}
}@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E var1);
}

前面讲观察者模式时,我们提到,观察者需要实现注册到被观察者(JDK 的实现方式)或者事件总线(EventBus 的实现方式)中。那在 Spring 的实现中,观察者被注册到了哪里呢?是如何注册的呢?

Spring 的实现中,观察者被注册到了 ApplicationContext 对照中。这里的 ApplicationContext 就相当于 Google EventBus 框架中的 “事件总线”。不过稍微提醒一下,ApplicationContext 这个类并不是为观察者模式服务的。它底层依赖 BeanFactory (IOC 的主要实现类),提供应用启动、运行时的上下文信息,是访问你些信息的最顶层接口。

实际上,具体到源码来说,ApplicationContext 只是一个接口,具体的代码实现包含在它的实现类 AbstractApplicationContext 中。我们把观察者模式相关的代码,摘抄到了下面。你只需要关注它是如何发送事件和注册监听者就好,其他细节不需要深究。

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {// ...private final Set<ApplicationListener<?>> applicationListeners;// ...public void publishEvent(ApplicationEvent event) {this.publishEvent(event, (ResolvableType)null);}public void publishEvent(Object event) {this.publishEvent(event, (ResolvableType)null);}protected void publishEvent(Object event, @Nullable ResolvableType eventType) {Assert.notNull(event, "Event must not be null");Object applicationEvent;if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent)event;} else {applicationEvent = new PayloadApplicationEvent(this, event);if (eventType == null) {eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();}}if (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);} else {this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);}if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext)this.parent).publishEvent(event, eventType);} else {this.parent.publishEvent(event);}}}// ...public void addApplicationListener(ApplicationListener<?> listener) {Assert.notNull(listener, "ApplicationListener must not be null");if (this.applicationEventMulticaster != null) {this.applicationEventMulticaster.addApplicationListener(listener);}this.applicationListeners.add(listener);}public Collection<ApplicationListener<?>> getApplicationListeners() {return this.applicationListeners;}// ...protected void registerListeners() {Iterator var1 = this.getApplicationListeners().iterator();while(var1.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var1.next();this.getApplicationEventMulticaster().addApplicationListener(listener);}String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);String[] var7 = listenerBeanNames;int var3 = listenerBeanNames.length;for(int var4 = 0; var4 < var3; ++var4) {String listenerBeanName = var7[var4];this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (earlyEventsToProcess != null) {Iterator var9 = earlyEventsToProcess.iterator();while(var9.hasNext()) {ApplicationEvent earlyEvent = (ApplicationEvent)var9.next();this.getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}//...
}

从上面的代码中,可以发现,真正的消息发送,实际上是通过 ApplicationEventMulticaster 这个类来完成的。这个类的源码也是只摘抄了最关键的一部分,也就是 multicastEvent() 这个消息发送函数。不过,它的代码并不复杂。它通过线程池,支持异步非阻塞、同步组赛两种类型的观察者模式。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {// ...public void multicastEvent(ApplicationEvent event) {this.multicastEvent(event, this.resolveDefaultEventType(event));}public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);Iterator var4 = this.getApplicationListeners(event, type).iterator();while(var4.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var4.next();Executor executor = this.getTaskExecutor();if (executor != null) {executor.execute(() -> {this.invokeListener(listener, event);});} else {this.invokeListener(listener, event);}}}// ...
}

借助 Spring 提供的观察者模式的骨架代码,如果要在 Spring 下实现某个事件的发送和监听,只需要做很少的工作,定义事件、定义监听器、往 ApplicationContext 中发送事件就可以了,剩下的工作都由 Spring 框架来完成。实际上,这也体现了 Spring 框架的扩展性,也就是在不需要修改任何代码的情况下,扩展新的事件和监听器。

模板模式在 Spring 中的应用

刚刚讲的是观察者模式在 Spring 中的应用,现在再讲下模板模式。

有一个问题经常在面试中被问到:请你说一下 Spring Bean 的创建过程包含哪些主要的步骤。这其中就涉及模板模式。它也体现了 Spring 的扩展性。利用模板模式,Spring 能让用户定制 Bean 的创建过程。

Spring Bean 的创建过程大致可以分为两步:对象的创建和对象的初始化。

对象的创建是通过反射来动态生成对象,而不是 new 方法。不管哪种方式,说白了,总归还是调用构造函数来生成对象。对象的初始化有两种实现方式。一种是在类中自定义一个初始化函数,并通过配置文件,显示地告知 Spring,哪个函数是初始化函数。下面是一个例子,在配置文件中,通过 init-method 属性来指定初始化函数。

public class DemoClass {// ...public void initDemo() {// 初始化...}
}// 配置:需通过 init-method 显式地指定初始化方法
<bean id="demoBean" class="com.example.DemoClass" init-method="initDemo"></bean>

这种初始化方式有一个缺点,初始化函数并不固定,由用户随意定义,这就需要 Spring 通过反射,在运行时动态地调用这个初始化函数。而反射又会影响代码执行的性能,那有没有替代方案呢?

Spring 提供了另外一个定义初始化函数的方式,让类实现 InitializingBean 接口。这个接口包含一个固定的初始化函数的定义(afterPropertiesSet() 函数)。Spring 在初始化 Bean 时,可以直接通过 bean.afterPropertiesSet() 的方式,调用 Bean 对象上的这个函数,而不需要使用反射来调用了。例子代码如下所示:

public class DemoClass implements InitializingBean {@Overridepublic void afterPropertiesSet() throw Exception {// 初始化...}
}// 配置不需要显式的指定初始化方法
<bean id="demoBean" class="com.example.DemoClass"></bean>

尽管这种方式不会用到反射,执行效率提高了,但业务代码(DemoClass)跟框架代码(InitializingBean)耦合在了一起。框架代码侵入到了业务代码中,替换框架的成本就高了。所以,不是很推荐这种写法。

实际上,Spring 对 Bean 整个生命周期的管理中,还有一个跟初始化相对应的过程,那就是 Bean 的销毁过程。我们知道,Java 中对象的回收是通过 JVM 来自动完成的。但是,我们可以将 Bean 正式交给 JVM 垃圾回收前,执行一些销毁操作(比如关闭文件句柄等等)。

销毁过程跟初始化过程类似,也有两种实现方式。一种是通过配置 destory-method 指定类中的销毁函数,另一种是让实现类实现 DisposableBean 接口。因为 destory-methodDisposableBeaninit-methodInitializingBean 非常相似,所以这部分就不详细讲解了。

实际上,Spring 针对对象的初始化过程,还进一步做了细化,将它拆分成了三个小步骤:初始化前置操作、初始化、初始化后置操作。其中,中间的初始化操作就是我们刚刚讲的那部分,初始化的前置和后置操作,定义在 BeanPostProcessor 中。BeanPostProcessor 的接口定义如下所示:

public interface BeanPostProcessor {@NullableObject postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;@NullableObject postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

我们只需要定义一个实现了 BeanPostProcessor 接口的处理类,并在配置文件中像配置普通 Bean 一样去配置就可以了。Spring 中的 ApplicationContext 会自动检测配置文件中实现了 BeanPostProcessor 接口的所有 Bean,并把它们注册到 BeanPostProcessor 处理器列表中。在 Spring 容器创建 Bean 的过程中,Spring 会逐一去调用这些处理器。

通过上面的分析,基本上弄清楚了 Spring Bean 的整个生命周期(创建加销毁)。

在这里插入图片描述
不过,你可能会说,这哪里使用到了模板模式啊?模板模式不是定义一个包含模板方法的抽象类,以及定义子类实现模板方法吗?

实际上,这里的模板模式的实现,并不是标准的实现方式,而是有点类似 Callback 回调的实现方式,也就是将要执行的函数封装成对象(比如初始化方法封装成 InitializingBean 对象),传递给模板(BeanFactory)来执行。

总结

本章讲道理 Spring 中用到的两种支持扩展的设计模式:观察者模式和模板模式。

其中,观察者模式在 Java、Google Guava、Spring 中都有提供相应的实现代码。在平时的项目开发中,基于这些实现代码,我们可以轻松地实现一个观察者模式。

Java 提供的空间比较简单,只包含 java.util.Observablejava.util.Observer 两个类。Google Guava 提供的框架功能比较完成和强大,可以通过 EventBus 事件总线来实现观察者模式。Spring 提供了观察者模式包括 Event 事件、Listener 监听者、Publisher 发送者三部分。事件发送到 ApplicationContext 中,然后 ApplicationContext 将消息发送给实现注册好的监听者。

此外,还讲到模板模式在 Spring 中的一个典型应用,那就是 Bean 的创建过程。Bean 的创建包括两个大的步骤:对象的创建和对象的初始化。其中,对象的初始化可以分解为 3 个小的步骤:初始化前置操作、初始化、初始化后置操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/641707.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++——类和对象练习(日期类)

日期类 1. 构造函数和析构函数2. 拷贝构造和赋值运算符重载3. 运算符重载3.1 日期的比较3.2 日期加减天数3.3 日期减日期3.4 流插入和流提取 4. 取地址和const取地址重载5. 完整代码Date.hDate.c 对日期类进行一个完善&#xff0c;可以帮助我们理解六个默认成员函数&#xff0c…

【黑马头条】-day12项目部署和发布-jenkins

文章目录 1 持续集成2 软件开发模式2.1 瀑布模式2.2 敏捷开发2.2.1 迭代开发2.2.2 增量开发 3 Jenkins3.1 Jenkins安装3.1.1 导入镜像3.1.2 配置3.1.3 初始化设置 3.2 插件安装3.3 服务器环境准备3.3.1 Docker安装配置3.3.2 Git安装配置3.3.3 Maven安装配置 3.4 Jenkins工具配置…

盲人过马路安全:科技力量赋予“隐形守护者”

作为一名资深记者&#xff0c;我始终关注着社会各群体的生活现状&#xff0c;尤其是那些面临特殊挑战的人群。今天&#xff0c;我想聚焦一个看似平常却对盲人构成重大困扰的日常场景——过马路&#xff0c;以及一款名为蝙蝠避障的辅助应用如何成为他们的盲人过马路安全的守护者…

Docker 部署 WordPress 并完成建站

什么是 WordPress WordPress 是使用 PHP 语言开发的博客平台&#xff0c;用户可以在支持 PHP 和 MySQL 数据库的服务器上架设属于自己的网站。也可以把 WordPress 当作一个内容管理系统&#xff08;CMS&#xff09;来使用。WordPress 是一款个人博客系统&#xff0c;并逐步演化…

【Linux系统编程】第七弹---权限管理操作(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、修改文件权限的做法(一) 2、有无权限的表现 总结 上一弹我们讲解了Linux权限概念相关的知识&#xff0c;但是我们只知道有…

【C++学习】STL之空间配置器之一级空间配置器

文章目录 &#x1f4ca;什么是空间配置器✈STL 提供六大组件的了解&#x1f440;为什么需要空间配置器&#x1f44d;SGI-STL空间配置器实现原理&#x1f302;一级空间配置器的实现 &#x1f4ca;什么是空间配置器 空间配置器&#xff0c;顾名思义就是为各个容器高效的管理空间…

python爬虫 - 爬取html中的script数据(从zum.com爬取新闻信息 )

文章目录 1. 分析页面内容数据格式2. 使用re.findall方法&#xff0c;编写爬虫代码3. 使用re.search 方法&#xff0c;编写爬虫代码 1. 分析页面内容数据格式 &#xff08;1&#xff09;打开 https://zum.com/ &#xff08;2&#xff09;按F12&#xff08;或 在网页上右键 --…

Tomcat启动闪退疑难排解全攻略:资深技术视角详解(详细)

Tomcat 启动闪退问题是在尝试启动Apache Tomcat服务器时&#xff0c;遇到的一种情况&#xff0c;其中服务器进程无法正常运行并立即退出。这个问题可能是由于多种原因造成的&#xff0c;包括配置错误、端口冲突、类加载问题等。作为一个资深技术人员&#xff0c;解决这类问题需…

【Java网络编程】TCP通信(Socket 与 ServerSocket)和UDP通信的三种数据传输方式

目录 1、TCP通信 1.1、Socket 和 ServerSocket 1.3、TCP通信示例 2、UDP的三种通信&#xff08;数据传输&#xff09;方式 1、TCP通信 TCP通信协议是一种可靠的网络协议&#xff0c;它在通信的两端各建立一个Socket对象 通信之前要保证连接已经建立&#xff08;注意TCP是一…

PostgreSQL中的索引类型有哪些,以及何时应选择不同类型的索引?

文章目录 索引 解决方案和示例代码 PostgreSQL提供了多种索引类型&#xff0c;每种类型都有其特定的应用场景和优势。选择合适的索引类型可以显著提高查询性能&#xff0c;减少数据库负载。 索引 以下是PostgreSQL中常见的索引类型及其适用场景&#xff1a; 1. B-tree 索引 …

BFS解决FloodFill算法:(Leetcode:733. 图像渲染)

题目链接&#xff1a;733. 图像渲染 - 力扣&#xff08;LeetCode&#xff09; 使用广度优先遍历算法解决该问题&#xff1a; 从初始位置开始搜索&#xff0c;初始位置符合条件就入栈&#xff0c;并修改初始位置值。初始位置出栈。 再从初始位置开始广度优先搜索&#xff08;…

成都直播产业园「天府锋巢」电商流量深度变现,助力企业降本增效

天府锋巢园区环境 天府锋巢直播基地 其他重点特色产业服务 等您来解锁&#xff01; 「锋巢资讯 聚焦天府 诚邀企业 敬请关注」