设计模式学习笔记 - 开源实战四(中):剖析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> {
    @Override
    public void onApplicationEvent(DemoEvent demoEvent) {
        String message = demoEvent.getMessage();
        System.out.println(message);
    }
}

// Publisher发送者
@Component
public class DemoPublisher {
    @Autowired
    private 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 {
	@Override
	public 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 {
	@Nullable
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
	
	@Nullable
	Object 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 个小的步骤:初始化前置操作、初始化、初始化后置操作。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

yolov8 裁剪检测结果

yolov8 裁剪检测结果 1. 基础2. 图片批量裁剪2.1 检测裁剪2.2 分割裁剪 3. 视频裁剪3.1 检测裁剪3.2 分割裁剪3.3 实时裁剪 4. 源码 1. 基础 本项目是在 WindowsYOLOV8环境配置 的基础上实现的 思路&#xff1a;将检测得到的物体边框提取&#xff0c;然后边框裁剪原图&#xf…

Python网络数据抓取(3):Requests

引言 在这一部分&#xff0c;我们将探讨Python的requests库&#xff0c;并且利用这个库来进行网页数据抓取。那么&#xff0c;我们为何需要这个库&#xff0c;以及怎样利用它呢&#xff1f; requests库是广受大家欢迎的一个库&#xff0c;它是下载次数最多的。这个库使我们能够…

直流负载在新能源领域的作用有哪些

直流负载在新能源领域的作用主要体现在以下几个方面&#xff1a; 新能源如太阳能、风能等&#xff0c;其发电过程中产生的电能为直流电。传统的电力系统主要采用交流电&#xff0c;因此在新能源并网时需要进行逆变器转换。然而&#xff0c;逆变器在转换过程中会存在一定的能量损…

设计模式-模板模式

模板设计模式 定义 在模板模式中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。 简单来说,有多个子类共有的方法,且逻辑相同,可以考虑作为模板方法。 模板的价值就在于骨架的定义,骨架内部将问题…

手写基于redis-lua脚本实现分布式id生成器starter

手写基于redis-lua脚本实现分布式id生成器starter 文章目录 1.前言2.实现思路2.1lua脚本的特性2.2 了解三个redis命令2.3集群自增序列实现原理2.4三种实现思路2.4.1 实现思路一2.4.2 实现思路二2.4.3实现思路三 3.项目工程目录4.源码仓库地址5.依赖及使用配置5.1依赖5.2nacos配…

科研基础与工具(论文写作)

免责申明&#xff1a; 本文内容只是学习笔记&#xff0c;不代表个人观点&#xff0c;希望各位看官自行甄别 参考文献 科研基础与工具&#xff08;YouTube&#xff09; 学术写作句型 Academic Phrase bank 曼彻斯特大学维护的一个网站 写论文的时候&#xff0c;不不知道怎么…

机器学习基础-PR\ROC\F1

1 1 、ROC曲线2 、PC曲线3、F14 、正负样本不均衡时怎么选择 1 、ROC曲线 就是TPR 与FPR 曲线 如图&#xff0c;就是根据阈值不同&#xff0c;我们看我们的二分类器的结果&#xff0c;根据结果算出TPR(真阳性)与FPR(假阳性)&#xff0c;最好的情况就是如图&#xff0c;我们的…

2024年三支一扶报名照上传要求很严格

2024年三支一扶报名照上传要求很严格

2024年最新版云开发cms开通步骤,开始开发微信小程序前的准备工作,认真看完奥!

小程序官方有改版了&#xff0c;搞得石头哥不得不紧急的再新出一版&#xff0c;教大家开通最新版的cms网页管理后台 一&#xff0c;技术选型和技术点 1&#xff0c;小程序前端 wxml css JavaScript MINA原生小程序框架 2&#xff0c;数据库 云开发 云数据库 云…

合合信息Embedding模型:引领中文文本向量化技术新高度

目录 &#x1f345;前言&#x1f353;赛事含金量&#x1f353;Embedding技术简介&#x1f353;Embedding在大模型中的价值&#x1f353;合合信息Embedding模型特点及优势&#x1f353;合合信息Embedding模型测试&#x1f353;技术突破&#x1f353;公司介绍 &#x1f345;总结 …

360在线翻译免费API

一、需求&#xff1a; 根据360在线翻译&#xff0c;获取免费API&#xff0c;并调用 二、主要步骤 1、请求 url url "https://fanyi.so.com/index/search" 2、传入信息 datas {"query": "桌子"} 3、请求头 headers {"pro": &…

Axure糖尿病健康管理APP原型 (知识科普/病友社区/远程医生会诊/购物商城/血糖监测/饮食监测)

作品概况 页面数量&#xff1a;共 50 页 源文件格式&#xff1a;rp格式&#xff0c;兼容 Axure RP 9/10&#xff0c;非程序软件无源代码 应用领域&#xff1a;医疗健康、慢病管理、糖尿病管理 作品特色 本作品为Axure糖尿病健康管理APP端原型图&#xff0c;设计规范内容清晰…

第54篇:创建Platform Designer系统

Q&#xff1a;本期我们开始使用Platform Designer工具创建带IP核的FPGA自定义硬件系统。 A&#xff1a;Platform Designer是集成在Quartus软件里的系统设计工具&#xff0c;名称随着Quartus的不断更新曾命名为SOPC Builder和Qsys。 使用Platform Designer可以添加Quartus已有自…

Aigtek高压放大器在电活性聚合物中的作用是什么

电活性聚合物是一类特殊类型的聚合物&#xff0c;其性质和形状可以受到外部电场的调控。这些聚合物在多个领域中有着广泛的应用&#xff0c;包括人工肌肉、电动液体透镜、柔性电子、生物医学传感器等。高压放大器在电活性聚合物的研究和应用中扮演着关键的角色&#xff0c;下面…

【Qt 学习笔记】Qt常用控件 | 显示类控件 | Calendar Widget的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 显示类控件 | Calendar Widget的使用及说明 文章编号&am…

C#-使用Harmony库实现DLL文件反射调用

一. Harmony工作原理 利用C#运行时Runtime的反射机制,动态加载dll中的方法,字段,属性,实现对DLL方法的重写和代码注入。 二. Harmony下载及安装 1.下载Harmony_lib库lib.harmony.2.3.3.nupkg 霸王•吕布 / CSharpHarmonyLib GitCodehttps://gitcode.net/qq_35829452/csharph…

南京邮电大学数学实验A答案 | 《MATLAB数学实验》第三版课后习题答案

数学实验A 本仓库收集了2024年我在学习《数学实验A》课程期间完成的作业。课程使用的教材为《MATLAB数学实验》第三版&#xff0c;作者为胡良剑和孙晓君教授。 这个资源库的建立初衷是为了帮助南京邮电大学的同学们在学习过程中有一个参考的依据&#xff0c;减少一些无端浪费…

【网络编程】UDP实现回显服务器

一.网络编程的基本术语. 客户端 客户端是为用户提供本地服务的程序&#xff0c;通常位于用户设备上。也称为用户端&#xff0c;是相对于服务器而言的。它主要指安装在用户设备上的程序&#xff0c;这些程序能够与服务器进行通信&#xff0c;从而获取服务或者执行特定功能。在…

虚拟机中安装的CentOS7的桌面右上角没有网络图标解决方案

问题描述 今天在打开CentOS7后&#xff0c;发现右上角的网络图标不见了&#xff0c;然后命令行访问百度也不通。然后上网查了一些解决方法。 原因分析及解决方案&#xff1a; 上网查了许多解决方法&#xff0c;其中一种成功解决了我的问题&#xff1b;我的是配置文件的问题。…

双链向表专题

1.链表的分类 链表的种类非常多组合起来就有 2 2 8种 链表说明&#xff1a; 虽然有这么多的链表的结构&#xff0c;但是我们实际中最常⽤还是两种结构&#xff1a; 单链表 和 双向带头循环链表 1. 无头单向⾮循环链表&#xff1a;结构简单&#xff0c;⼀般不会单独⽤来存数…