一、降低耦合

解耦场景在互联网开发的设计中使用的也是非常频繁,如:这里需要一个注册完成事件推送消息、用户下单我会发送一个MQ、收到我的支付消息就可以发货了等等,都是依靠事件订阅和发布以及MQ消息这样的组件,来处理系统之间的调用解耦,最终通过解耦的方式来提升整体系统架构的负载能力。其实解耦思路可以理解为设计模式中观察者模式的具体使用效果,在观察者模式中当对象间存在一对多关系时,则使用观察者模式,它是一种定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。事件传播的一个典型应用是,当Bean中的操作发生异常(如数据库连接失败),则通过事件传播机制通知异常监听器进行处理

二、目标

在 Spring 中有一个 Event 事件功能,它可以提供事件的定义、发布以及监听事件来完成一些自定义的动作。比如你可以定义一个新用户注册的事件,当有用户执行注册完成后,在事件监听中给用户发送一些优惠券和短信提醒,这样的操作就可以把属于基本功能的注册和对应的策略服务分开,降低系统的耦合。以后在扩展注册服务,比如需要添加风控策略、添加实名认证、判断用户属性等都不会影响到依赖注册成功后执行的动作。那么在本章节我们需要以观察者模式的方式,设计和实现 Spring Event 的容器事件和事件监听器功能,最终可以让我们在现有实现的 Spring 框架中可以定义、监听和发布自己的事件信息。

三、方案

其实事件的设计本身就是一种观察者模式的实现,它所要解决的就是一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。在功能实现上我们需要定义出事件类、事件监听、事件发布,而这些类的功能需要结合到 Spring 的 AbstractApplicationContext#refresh(),以便于处理事件初始化和注册事件监听器的操作。整体设计结构如下图:

  • 在整个功能实现过程中,仍然需要在面向用户的应用上下文 AbstractApplicationContext中添加相关事件内容,包括:初始化事件发布者、注册事件监听器、发布容器刷新完成事件。
  • 使用观察者模式定义事件类、监听类、发布类,同时还需要完成一个广播器的功能,接收到事件推送时进行分析处理符合监听事件接受者感兴趣的事件,也就是使用isAssignableFrom 进行判断。
  • isAssignableFrom 和 instanceof 相似,不过isAssignableFrom 是用来判断子类和父类的关系的,或者接口的实现类和接口的关系的,默认所有的类的终极父类都是Object。如果A.isAssignableFrom(B)结果是true,证明B可以转换成为A,也就是A可以由B转换而来。

四、实现

  1. 工程结构
small-spring-step-10
└── src├── main│   └── java│       └── cn.bugstack.springframework│           ├── beans│           │   ├── factory│           │   │   ├── config│           │   │   │   ├── AutowireCapableBeanFactory.java│           │   │   │   ├── BeanDefinition.java│           │   │   │   ├── BeanFactoryPostProcessor.java│           │   │   │   ├── BeanPostProcessor.java│           │   │   │   ├── BeanReference.java│           │   │   │   ├── ConfigurableBeanFactory.java│           │   │   │   └── SingletonBeanRegistry.java│           │   │   ├── support│           │   │   │   ├── AbstractAutowireCapableBeanFactory.java│           │   │   │   ├── AbstractBeanDefinitionReader.java│           │   │   │   ├── AbstractBeanFactory.java│           │   │   │   ├── BeanDefinitionReader.java│           │   │   │   ├── BeanDefinitionRegistry.java│           │   │   │   ├── CglibSubclassingInstantiationStrategy.java│           │   │   │   ├── DefaultListableBeanFactory.java│           │   │   │   ├── DefaultSingletonBeanRegistry.java│           │   │   │   ├── DisposableBeanAdapter.java│           │   │   │   ├── FactoryBeanRegistrySupport.java│           │   │   │   ├── InstantiationStrategy.java│           │   │   │   └── SimpleInstantiationStrategy.java  │           │   │   ├── support│           │   │   │   └── XmlBeanDefinitionReader.java│           │   │   ├── Aware.java│           │   │   ├── BeanClassLoaderAware.java│           │   │   ├── BeanFactory.java│           │   │   ├── BeanFactoryAware.java│           │   │   ├── BeanNameAware.java│           │   │   ├── ConfigurableListableBeanFactory.java│           │   │   ├── DisposableBean.java│           │   │   ├── FactoryBean.java│           │   │   ├── HierarchicalBeanFactory.java│           │   │   ├── InitializingBean.java│           │   │   └── ListableBeanFactory.java│           │   ├── BeansException.java│           │   ├── PropertyValue.java│           │   └── PropertyValues.java │           ├── context  │           │   ├── event│           │   │   ├── AbstractApplicationEventMulticaster.java │           │   │   ├── ApplicationContextEvent.java │           │   │   ├── ApplicationEventMulticaster.java │           │   │   ├── ContextClosedEvent.java │           │   │   ├── ContextRefreshedEvent.java │           │   │   └── SimpleApplicationEventMulticaster.java │           │   ├── support│           │   │   ├── AbstractApplicationContext.java │           │   │   ├── AbstractRefreshableApplicationContext.java │           │   │   ├── AbstractXmlApplicationContext.java │           │   │   ├── ApplicationContextAwareProcessor.java │           │   │   └── ClassPathXmlApplicationContext.java │           │   ├── ApplicationContext.java │           │   ├── ApplicationContextAware.java │           │   ├── ApplicationEvent.java │           │   ├── ApplicationEventPublisher.java │           │   ├── ApplicationListener.java │           │   └── ConfigurableApplicationContext.java│           ├── core.io│           │   ├── ClassPathResource.java │           │   ├── DefaultResourceLoader.java │           │   ├── FileSystemResource.java │           │   ├── Resource.java │           │   ├── ResourceLoader.java │           │   └── UrlResource.java│           └── utils│               └── ClassUtils.java└── test└── java└── cn.bugstack.springframework.test├── event│   ├── ContextClosedEventListener.java│   ├── ContextRefreshedEventListener.java│   ├── CustomEvent.java│   └── CustomEventListener.java└── ApiTest.java

容器事件和事件监听器实现类关系,如图:

  • 以上整个类关系图以围绕实现 event 事件定义、发布、监听功能实现和把事件的相关内容使用AbstractApplicationContext#refresh 进行注册和处理操作。
  • 在实现的过程中主要以扩展 spring context 包为主,事件的实现也是在这个包下进行扩展的,当然也可以看出来目前所有的实现内容,仍然是以IOC为主。
  • ApplicationContext 容器继承事件发布功能接口ApplicationEventPublisher,并在实现类中提供事件监听功能。
  • ApplicationEventMulticaster接口是注册监听器和发布事件的广播器,提供添加、移除和发布事件方法。
  • 最后是发布容器关闭事件,这个仍然需要扩展到AbstractApplicationContext#close 方法中,由注册到虚拟机的钩子实现。
  1. 定义和实现事件
public abstract class ApplicationEvent extends EventObject {/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ApplicationEvent(Object source) {super(source);}
}
  1. 以继承 java.util.EventObject 定义出具备事件功能的抽象类ApplicationEvent,后续所有事件的类都需要继承这个类。
public class ApplicationContextEvent extends ApplicationEvent {/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ApplicationContextEvent(Object source) {super(source);}/*** Get the <code>ApplicationContext</code> that the event was raised for.*/public final ApplicationContext getApplicationContext() {return (ApplicationContext) getSource();}
}
public class ContextClosedEvent extends ApplicationContextEvent{/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ContextClosedEvent(Object source) {super(source);}
}
public class ContextRefreshedEvent extends ApplicationContextEvent{/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public ContextRefreshedEvent(Object source) {super(source);}
}
  • ApplicationContextEvent 是定义事件的抽象类,所有的事件包括关闭、刷新,以及用户自己实现的事件,都需要继承这个类。
  • ContextClosedEvent、ContextRefreshedEvent,分别是 Spring 框架自己实现的两个事件类,可以用于监听刷新和关闭动作。
  1. 事件广播器
public interface ApplicationEventMulticaster {/*** Add a listener to be notified of all events.* @param listener the listener to add*/void addApplicationListener(ApplicationListener<?> listener);/*** Remove a listener from the notification list.* @param listener the listener to remove*/void removeApplicationListener(ApplicationListener<?> listener);/*** Multicast the given application event to appropriate listeners.* @param event the event to multicast*/void multicastEvent(ApplicationEvent event);
}
  • 在事件广播器中定义了添加监听和删除监听的方法以及一个广播事件的方法 multicastEvent 最终推送时间消息也会经过这个接口方法来处理谁该接收事件。
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {public final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new LinkedHashSet<>();private BeanFactory beanFactory;@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {applicationListeners.add((ApplicationListener<ApplicationEvent>) listener);}@Overridepublic void removeApplicationListener(ApplicationListener<?> listener) {applicationListeners.remove(listener);}@Overridepublic final void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;}protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();for (ApplicationListener<ApplicationEvent> listener : applicationListeners) {if (supportsEvent(listener, event)) allListeners.add(listener);}return allListeners;}/*** 监听器是否对该事件感兴趣*/protected boolean supportsEvent(ApplicationListener<ApplicationEvent> applicationListener, ApplicationEvent event) {Class<? extends ApplicationListener> listenerClass = applicationListener.getClass();// 按照 CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy 不同的实例化类型,需要判断后获取目标 classClass<?> targetClass = ClassUtils.isCglibProxyClass(listenerClass) ? listenerClass.getSuperclass() : listenerClass;Type genericInterface = targetClass.getGenericInterfaces()[0];Type actualTypeArgument = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];String className = actualTypeArgument.getTypeName();Class<?> eventClassName;try {eventClassName = Class.forName(className);} catch (ClassNotFoundException e) {throw new BeansException("wrong event class name: " + className);}// 判定此 eventClassName 对象所表示的类或接口与指定的 event.getClass() 参数所表示的类或接口是否相同,或是否是其超类或超接口。// isAssignableFrom是用来判断子类和父类的关系的,或者接口的实现类和接口的关系的,默认所有的类的终极父类都是Object。如果A.isAssignableFrom(B)结果是true,证明B可以转换成为A,也就是A可以由B转换而来。return eventClassName.isAssignableFrom(event.getClass());}
  • AbstractApplicationEventMulticaster 是对事件广播器的公用方法提取,在这个类中可以实现一些基本功能,避免所有直接实现接口放还需要处理细节。
  • 除了像 addApplicationListener、removeApplicationListener,这样的通用方法,这里这个类中主要是对
    getApplicationListeners 和 supportsEvent 的处理。
  • getApplicationListeners方法主要是摘取符合广播事件中的监听处理器,具体过滤动作在 supportsEvent 方法中。
  • 在 supportsEvent方法中,主要包括对Cglib、Simple不同实例化需要获取目标Class,Cglib代理类需要获取父类的Class,普通实例化的不需要。接下来就是通过提取接口和对应的ParameterizedType 和 eventClassName,方便最后确认是否为子类和父类的关系,以此证明此事件归这个符合的类处理。可以参考代码中的注释

supportsEvent 方法运行截图

在代码调试中可以看到,最终 eventClassName 和 event.getClass() 在 isAssignableFrom 判断下为 true
关于 CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy 可以尝试在 AbstractApplicationContext 类中更换验证。

  1. 事件发布者的定义和实现
public interface ApplicationEventPublisher {/*** Notify all listeners registered with this application of an application* event. Events may be framework events (such as RequestHandledEvent)* or application-specific events.* @param event the event to publish*/void publishEvent(ApplicationEvent event);
}
  • ApplicationEventPublisher 是整个一个事件的发布接口,所有的事件都需要从这个接口发布出去。
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";private ApplicationEventMulticaster applicationEventMulticaster;@Overridepublic void refresh() throws BeansException {// 6. 初始化事件发布者initApplicationEventMulticaster();// 7. 注册事件监听器registerListeners();// 9. 发布容器刷新完成事件finishRefresh();}private void initApplicationEventMulticaster() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, applicationEventMulticaster);}private void registerListeners() {Collection<ApplicationListener> applicationListeners = getBeansOfType(ApplicationListener.class).values();for (ApplicationListener listener : applicationListeners) {applicationEventMulticaster.addApplicationListener(listener);}}private void finishRefresh() {publishEvent(new ContextRefreshedEvent(this));}@Overridepublic void publishEvent(ApplicationEvent event) {applicationEventMulticaster.multicastEvent(event);}@Overridepublic void close() {// 发布容器关闭事件publishEvent(new ContextClosedEvent(this));// 执行销毁单例bean的销毁方法getBeanFactory().destroySingletons();}
}
  • 在抽象应用上下文 AbstractApplicationContext#refresh 中,主要新增了初始化事件发布者、注册事件监听器、发布容器刷新完成事件,三个方法用于处理事件操作。
  • 初始化事件发布者(initApplicationEventMulticaster),主要用于实例化一个SimpleApplicationEventMulticaster,这是一个事件广播器。
  • 注册事件监听器(registerListeners),通过 getBeansOfType 方法获取到所有从 spring.xml中加载到的事件配置 Bean 对象。 发布容器刷新完成事件(finishRefresh),发布了第一个服务器启动完成后的事件,这个事件通过 publishEvent 发布出去,其实也就是调用了applicationEventMulticaster.multicastEvent(event); 方法。
  • 最后是一个 close方法中,新增加了发布一个容器关闭事件。publishEvent(new ContextClosedEvent(this));

五、测试

  1. 创建一个事件和监听器
public class CustomEvent extends ApplicationContextEvent {private Long id;private String message;/*** Constructs a prototypical Event.** @param source The object on which the Event initially occurred.* @throws IllegalArgumentException if source is null.*/public CustomEvent(Object source, Long id, String message) {super(source);this.id = id;this.message = message;}// ...get/set
}
  • 创建一个自定义事件,在事件类的构造函数中可以添加自己的想要的入参信息。这个事件类最终会被完成的拿到监听里,所以你添加的属性都会被获得到。
public class CustomEventListener implements ApplicationListener<CustomEvent> {@Overridepublic void onApplicationEvent(CustomEvent event) {System.out.println("收到:" + event.getSource() + "消息;时间:" + new Date());System.out.println("消息:" + event.getId() + ":" + event.getMessage());}
}
  • 这个是一个用于监听 CustomEvent 事件的监听器,这里你可以处理自己想要的操作,比如一些用户注册后发送优惠券和短信通知等。
  • 另外是关于 ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent>、ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> 监听器,这里就不演示了,可以参考下源码。
  1. 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean class="cn.bugstack.springframework.test.event.ContextRefreshedEventListener"/><bean class="cn.bugstack.springframework.test.event.CustomEventListener"/><bean class="cn.bugstack.springframework.test.event.ContextClosedEventListener"/>
</beans>

在 spring.xml 中配置了三个事件监听器,监听刷新、监控自定义事件、监听关闭事件

  1. 单元测试
public class ApiTest {@Testpublic void test_event() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");applicationContext.publishEvent(new CustomEvent(applicationContext, 1019129009086763L, "成功了!"));applicationContext.registerShutdownHook();}
}
  • 通过使用 applicationContext 新增加的发布事件接口方法,发布一个自定义事件CustomEvent,并透传了相应的参数信息。
  1. 测试结果
刷新事件:cn.bugstack.springframework.test.event.ContextRefreshedEventListener$$EnhancerByCGLIB$$440a36f5
收到:cn.bugstack.springframework.context.support.ClassPathXmlApplicationContext@ea4a92b消息;时间:Mon Aug 16 14:27:49 CST 2021
消息:1019129009086763:成功了!
关闭事件:cn.bugstack.springframework.test.event.ContextClosedEventListener$$EnhancerByCGLIB$$f4d4b18d
  • 从测试结果可以看到,我们自己定义的事件和监听,以及监听系统的事件信息,都可以在控制台完整的输出出来了。你也可以尝试增加一些其他事件行为,并调试代码学习观察者模式。

六、总结

  • 在整个手写 Spring框架的学习过程中,可以逐步看到很多设计模式的使用,比如:简单工厂BeanFactory、工厂方法FactoryBean、策略模式访问资源,现在又实现了一个观察者模式的具体使用。所以学习Spring 的过程中,要更加注意关于设计模式的运用,这是你能读懂代码的核心也是学习的重点。
  • 那么本章节关于观察者模式的实现过程,主要包括了事件的定义、事件的监听和发布事件,发布完成后根据匹配策略,监听器就会收到属于自己的事件内容,并做相应的处理动作,这样的观察者模式其实日常我们也经常使用,不过在结合Spring 以后,除了设计模式的学习,还可以学到如何把相应观察者的实现和应用上下文结合
  • 所有在 Spring学习到的技术、设计、思路都是可以和实际的业务开发结合起来的,而这些看似比较多的代码模块,其实也是按照各自职责一点点的扩充进去的。在自己的学习过程中,可以先动手尝试完成这些框架功能,在一点点通过调试的方式与Spring 源码进行对照参考,最终也就慢慢掌握这些设计和编码能力了。

手写简版spring --10--容器事件和事件监听器相关推荐

  1. 手写简版spring --1--创建简单的Bean容器

    一.声明 这个系列是我自己的学习笔记,为了在学习的过程中巩固知识而记录的,好强迫自己用心拜读,而不是进收藏夹.本系列都是基于小缚哥的文章和代码的,想要深入了解,请移步小缚哥博客 二.spring-Be ...

  2. 手写简版spring --9--对象作用域和FactoryBean

    一.目标 交给 Spring 管理的 Bean 对象,一定就是我们用类创建出来的 Bean 吗?创建出来的 Bean 就永远是单例的吗,没有可能是原型模式吗?在集合 Spring 框架下,我们使用的 ...

  3. 手写简版spring --2--实现Bean的定义、注册、获取

    一.目标 在上一章节我们初步依照 Spring Bean 容器的概念,实现了一个粗糙版本的代码实现.那么本章节我们需要结合已实现的 Spring Bean 容器进行功能完善,实现 Bean 容器关于 ...

  4. 手写简版spring --3--对象实例化策略

    一.目标 这一章节的目标主要是为了解决上一章节我们埋下的坑,那是什么坑呢?其实就是一个关于 Bean 对象在含有构造函数进行实例化的坑.在上一章节我们扩充了 Bean 容器的功能,把实例化对象交给容器 ...

  5. 手写简版spring --8--Aware感知容器对象Aware感知容器对象

    一.目标 目前已实现的 Spring 框架,在 Bean 操作上能提供出的能力,包括:Bean 对象的定义和注册,以及在操作 Bean 对象过程中执行的,BeanFactoryPostProcesso ...

  6. 手写简版spring --6--应用上下文(BeanPostProcessor 和 BeanFactoryPostProcessor)

    一.目标 如果你在自己的实际工作中开发过基于 Spring 的技术组件,或者学习过关于SpringBoot 中间件设计和开发等内容.那么你一定会继承或者实现了 Spring对外暴露的类或接口,在接口的 ...

  7. 手写简版spring --7--初始化方法和销毁方法

    一.目标 当我们的类创建的 Bean 对象,交给 Spring 容器管理以后,这个类对象就可以被赋予更多的使用能力.就像我们在上一章节已经给类对象添加了修改注册Bean定义未实例化前的属性信息修改和实 ...

  8. 手写简版spring --5--资源加载器解析文件注册对象

    一.目标 在完成 Spring 的框架雏形后,现在我们可以通过单元测试进行手动操作Bean对象的定义.注册和属性填充,以及最终获取对象调用方法.但这里会有一个问题,就是如果实际使用这个 Spring ...

  9. 手写简版spring --4--注入属性和依赖对象

    一.目标 首先我们回顾下这几章节都完成了什么,包括:实现一个容器.定义和注册Bean.实例化Bean,按照是否包含构造函数实现不同的实例化策略,那么在创建对象实例化这我们还缺少什么?其实还缺少一个关于 ...

最新文章

  1. SVN状态图标不显示的两种解决办法
  2. React学习(6)—— 高阶应用:非受控组件
  3. Qt/QML 窗口阴影边框实现
  4. 解决XML中报“cvc-complex-type.2.4.a: Invalid content was found starting with element ”错误
  5. TMS320F28335的SCI通信-FIFO中断通信实验
  6. 在.NET Core 中使用 FluentValidation 进行规则验证
  7. 前端学习(1560):ng-class颜色切换
  8. java 定义构造器_java的构造器定义以及使用
  9. 付款更方便了?腾讯接入!微信支持数字人民币支付
  10. 牛客竞赛,GDDU第十届文远知行杯新生程序设计竞赛,摸鱼记(BDEIKL题解,补G,ACFHJ)
  11. 关于机器学习模型的评估方法
  12. google浏览器html不提示,谷歌浏览器不显示标签页怎么回事 谷歌浏览器不显示标签页的解决方法...
  13. 毕业了,我的四年大学:平凡但不平庸(写给每一位想要认真学习的小伙伴)
  14. python捕捉warning_Python warnings.warn方法代码示例
  15. C语言变量inv,编写一个函数inv,将数组a中n个整数按相反顺序存放,用指针变量作为调用该函数时的实参...
  16. 自学编程,十年磨一剑
  17. 自媒体平台数据统计分析爬虫之【趣头条】模拟登陆分析详解及数据统计接口详解
  18. 浅谈“互联网+”浪潮下传统行业的战略转型
  19. 路由器和交换机用什么线连接?
  20. 在我的世界里玩我的世界是一种怎样的体验?

热门文章

  1. Golang的模板与渲染
  2. 0-1背包问题优化算法详解
  3. 使用NSURLCache 数据缓存
  4. JSPatch – 动态更新iOS APP
  5. 【Linux高级驱动】如何分析并移植网卡驱动
  6. Eclipse设置字体大小等!
  7. 【荐】中国最有潜力的十位企业家(IT行业占大半)
  8. Go 语言 练习 聊天室 01
  9. 工具 左侧服务列表_协作办公时代,在线编辑文档工具,安排!
  10. 云炬Android开发笔记 11主界面-商品分类开发(多布局Section RecyclerView)