通过 applicationcontext 获取value_Spring源码分析(十二)ApplicationContext详解(中)...

上篇文章已经对ApplicationContext的一部分内容做了介绍,ApplicationContext主要具有以下几个核心功能:

  1. 国际化
  2. 借助Environment接口,完成了对 Spring运行环境的抽象,可以返回环境中的属性,并能出现占位符
  3. 借助于Resource系列接口,完成对底层资源的访问和加载
  4. 接触了ApplicationEventPublisher接口,能够进行事件发布监听
  5. 负责创建,配置和管理bean

上篇文章写了1,2,两点,这篇文章继续之前的内容

  1. Spring的资源(Resource)

首先需要说明的是,Spring并没有让ApplicationContext直接继承了Resource接口,就像ApplicationContext接口并没有直接继承Environment接口一样。这也不难理解,采用这种组合的方式会让我们的类更加轻量,也起到了解耦的作用,ApplicationContext和Resoucre相关的接口的继承关系如下:

8c4b4e866024dcc982ccb6c4704ccf7e.png

不管是ResourceLoader还是ResourcePatternResolver都是为了获取Resource对象,不过ResourceLoader在ResourcePatternResolver的基础上扩展了一个获取多个Resource的方法,后面在介绍。

接口简介

Resource继承了InputStreamSource

277e1d80945e10bf271d22c13c985988.png

586e092f07a237d0261f1c5d9fd9954b.png

078ed6aea86a42d6065f13749d8b7f55.png

UML类图

432b0b081aba9f38178f684d64fcdb3c.png

因为实现了Resource的接口的类很多,并且一些类我们用不到或者很简单,所以上图中省略了一些不重要的分支,接下来就一个个分析。

抽象基类AbstractResource

实现了Resource接口,是大多数Resource实现类的基类,提供了很多通用的方法。比如exists方法会检查是否一个文件或者输入流能够被打开,isOpen永远返回false,getURL和getFile方法会抛出异常,toString将会返回描述信息。

FileSystemResource

基于Java的文件系统封装成一个资源对象。

AbstractFileResolvingResource

将URL解析成文件引用,即会处理协议为:file的URL,也会处理JBoss的vfs协议。然后相应的解析成对应的文件系统引用。

ByteArrayResource

根据一个给定的字节数组构建的一个资源,同时给出一个对应的输入流。

BeanDefinitionResource

只是对BeanDefinition进行的一次描述性的封装

InputStreamResource

是针对输入流封装的资源,它的构建需要一个输入流,对于getInputStream操作将直接返回该字节流,因此只能读取一次该字节流,即isOpen永远返回true。

UrlResource

UrlResource代表URL资源,用于简化URL资源访问。

UrlResource一般支持如下资源访问:

-http:通过标准的http协议访问web的资源,如new UrlResource("http//地址");

-ftp:通过ftp协议访问资源,如new UrlResource("ftp://地址");

-file:通过file协议访问本地文件系统资源,如new UrlResource("file:d/test.txt");

ClassPathResource

JDK获取资源的两种方式:

  1. 使用Class对象的getResource(String path)获取资源的URL,getResourceAsStream(String path) 获取资源流。参数即可是当前class文件相对路径(以文件夹或文件开头),也可以是当前class文件的绝对路径(以“/”开头,相对于当前classpath根目录)
  2. 使用ClassLoader对象的getResource(String path)获取资源的URL,getResourceAsStream(String path) 获取资源流。参数只能是绝对路径,但不能与“/”开头

ClassPathResource代表classpath路径的资源,将使用给定的Class或ClassLoader进行加载classpath资源,isOpen永远返回false,表示可多次读取资源

ServletContextResource

是针对于ServletContext封装的资源,用于访问ServletContext环境下的资源。ServletContextResource持有一个ServletContext的引用,其底层是通过ServletContext的getResource方法getResourceAsStream方法来获取资源的。

ResourceLoader

接口简介

ResourceLoader接口被设计用来从指定的位置加载一个Resource,其接口定义如下

40a1b3408c750ccc6e17025ff59160fa.png

UML类图

e5ef9b0d7239e884211a127b3b9984d8.png

对于一些不是很必要的类都忽略了,其核心的类只需要关注DefaultResourceLoader就可以了,因为其与子类(除了GenericApplicationContext)都是直接继承了DefaultResourceLoader的getRe方法,代码如下:

187f19d720dbf79baf90a643f46260b6.png

资源路径

ant-style

类似于下面这种含有通配符的路径

/

classpath和classpath*

classpath:用来加载类路径(包括jar包)中一个且仅一个资源;

classpath*:用来加载类路径(包括jar包)中所有配置的资源,可使用Ant路径模式。

  1. Spring中的事件监听机制(publish-event)

我们知道,ApplicationContext接口继承了ApplicationContextEventPublisher接口,能够进行事件发布监听,那么什么是事件的发布和监听呢?我们从监听者模式说起

监听者模式

概念

事件源经过事件的封装传给监听器,当事件源触发事件后,监听器接收到事件对象可以回调事件的方法。

f844d3d084e22adb54b170236a99ff26.png

Spring对监听者模式的实践

直接通过一个例子来体会:

public 

在上面的例子中,主要涉及到了三个角色,也就是我们之前提到的

  1. 事件源:ApplicationEventPublisher
  2. 事件:MyEvent
  3. 事件监听器:EventListener ,实现了ApplicationListener

我们通过ApplicationEventPublisher发布了一个事件(MyEvent),然后事件监听器监听到了事件,并进行对应的处理。

接口简介

ApplicationEventPublisher

677634c8dafd726cba8ea2ee1c1b1381.png

对于这个接口,只需要关注有哪些子类是实现了publishEvent(Object event)这个方法即可。

搜索发现,我们只需要关注org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object)这个方法即可,关于这个方法在后文的源码分析在详细介绍。

ApplicationEvent

继承关系如下:

68f02014d07439ddafd8f8f983a2afea.png

我们主要关注上面4个类(PayloadApplicationEvent在后文源码分析时再介绍),下面几个都是Spring直接在内部使用到的事件,比如ContextClosedEvent,在容器关闭时被创建然后发布

// 这个类是java的util包下的一个类,java本身也具有一套事件机制

7fee89b1f67f32987e53b46dd17faa98.png

ApplicationListener

e8046d7bff70ca3f3be7f9364d87e05e.png

注解方式实现事件发布机制

在上面的例子中,我们通过传统的方式实现了事件的发布监听,但是上面的过程是在有点繁琐,我们发布的事件需要实现指定的接口,在进行监听时又需要实现指定的接口。每增加一个发布的事件,代表需要多两个类,这样在项目的迭代过程中,会导致我们关于事件的类越来越大,所以在Spring4.2版本后,新增了一个注解,让我们可以快速的实现对发布的事件监听。示例代码:

@ComponentScan

可以看到上面的例子中,我们使用一个@EventListener注解,直接标注了Listen类中的一个方法是一个事件监听器,并且通过方法的参数类型Event指定了这个监听器的事件类型为Event类型。在这个例子中,第一,我们的事件不需要去继承特定的类,第二,我们的监听器也不需要去实现特定的接口,极大的方便我们的开发。

异步的方式实现事件监听

对于上面的例子,只需要按下面这种方式添加两个注解即可完成异步:

@ComponentScan

对于上面的两个注解@EnableAsync以及@Async,我会在AOP系列的文章中再做介绍,目前而言,知道能通过这种方式开启异步支持即可。

对监听器进行排序

当我们发布一个事件时,可能会同时被两个监听器监听到,比如在我们上面的例子中如果同时存在两个监听器,如下:

@Component

在这种情况下,我们可能希望两个监听器可以按顺序执行,这个时候需要用到另一个注解了:@Order

@Component

注解中的参数越小,代表优先级越高,在上面的例子中,会先执行listen2方法再执行listen1方法

那Spring到底如何实现这一套事件发布机制呢?接下来我们进行源码分析

源码分析(publishEvent方法)

我们需要分析的代码主要是org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)方法,源码如下:

b06231b1c9c9bbb1ea957ee671d8645e.png

上面这段代码核心部分就是getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);,我们分成几个部分解析:

  • getApplicationEventMulticaster()
  • multicastEvent(applicationEvent, eventType)

getApplicationEventMulticaster()方法代码如下:

9de382606569e4d72bc9ca01eb944ebc.png

可以看到,只是简单的获取容器中已经初始化好的一个applicationEventMulticaster,那么现在有几个问题:

  1. applicationEventMulticaster是什么?
  • 接口定义

7b80b8ece5e5824b63f192f15ab58077.png
  • UML类图

cb70c9d058f83934a71d1a215f05116b.png

主要涉及到两个类:

  • AbstractApplicationEventMulticaster,这个类对ApplicationEventMulticaster这个接口基础方法做了实现,除了核心方法multicastEvent,这个类最大的作用是获取监听器。稍后在介绍.。
  • SimpleApplicationEventMulticaster,这是Spring默认提供的一个事件分发器,如果我们没有进行特别配置的话,就会采用这个类生成的对象作为容器的事件分发器。
  1. 容器在什么时候对其进行的初始化

回到我们之前的图:

665d48313956d2934ea1c933c7dce9c0.png

可以看到,在3-8调用了一个initApplicationEventMulticaster方法,从名字我们就知道,这是对ApplicationEventMulticaster进行的初始化,看看这个方法做了什么。

  • initApplicationEventMulticaster方法

5109928d328997ebb42df848e575a244.png

这段代码的含义就是告诉我们,可以自己配置一个ApplicationEventMulticaster,如果没有进行配置,那么将默认使用一个SimpleApplicationEventMulticaster。

接下来,我们尝试自己配置一个简单的ApplicationEventMulticaster,示例代码如下:

@Component

运行程序后会发现“进入MyEventMulticaster”这句话打印了两次,第一次是容器启动时会发布一个ContextStartedEvent事件,也会调用我们配置的事件分发器进行事件发布。

  • multicastEvent(applicationEvent, eventType)

在Spring容器中,只内置了一个这个方法的实现类,就是SimpleApplicationEventMulticaster,实现的逻辑如下:

ed668d019a3e55727974202f497af000.png

上面代码主要的实现逻辑可以分为三步:

  1. 推断事件类型
  2. 根据事件类型获取对应的监听器
  3. 执行监听逻辑

我们一步步分析

  • resolveDefaultEventType(event),推断事件类型

48742a6b1cdc093383ca19a05ba9905d.png

d70fe970bde8ebf442d2fce5f07c618b.png

上面代码涉及到一个概念就是ResolvableType,对于ResolvableType我们需要了解的是,ResolvableType为所有Java类型提供了统一的数据结构和API,换句话说,一个ResolvableType对象就对应着一种Java类型。可以通过ResolvableType对象获取类型携带的信息(举例如下):

  1. getSuperType():获取直接父类型
  2. getInterfaces():获取接口类型
  3. getGeneric(int...):获取类型携带的泛型类型
  4. resolve():Type对象到Class对象的转换

另外,ResolvableType的构造方法全部为私有,我们不能直接new,只能使用其提供的静态方法案进行类型获取:

  1. forField(Field):获取指定字段的类型
  2. forMethodParameter(Method, int):获取指定方法的指定形参的类型
  3. forMethodReturnType(Method):获取指定方法的返回值的类型
  4. forClass(Class):直接封装指定的类型
  5. ResolvableType.forInsance:获取指定的实例的泛型信息

关于ResolvableType和Java的类型中的关系请关注我的后续文章,限于篇幅原因在本文就不做过多介绍了。

  • getApplicationListeners(event, type),获取对应的事件监听器

事件监听器主要分为两种,一种是我们通过实现接口直接注册到容器的bean,例如下面这种

@Component

另外一种是通过注解的方式,就是下面这种

@Component

对于实现接口的方式不用多说,因为实现了这个类本身就会被扫描然后加入到容器中。对于注解这种方式,Spring是通过一个回调方法实现的。大家关注下这个接口org.springframework.beans.factory.SmartInitializingSingleton,同时找到其实现类,org.springframework.context.event.EventListenerMethodProcessor。在这个类中,会先调用afterSingletonsInstantiated方法,然后调用一个processBean方法,在这个方法中会遍历所有容器中的bean,然后遍历bean中 的每一个方法判断方法上是否加了一个@EventListener注解。如果添加了这个注解,会将这个Method方法包装成一个ApplicationListenerMethodAdapter,这个类本身也实现了ApplicationListener接口,之后再添加到监听器的集合中。

  • invokeListener,执行监听逻辑

本身这个方法没什么好说的,就是调用了ApplicationListener中的onApplicationEvent方法,执行我们的业务逻辑。但是值得注意的是,在调用onApplicationEvent方法前,会先进行一个判断

Executor 

会先判断是否获取到一个Executor,如果能获取到那么会通过Executor异步执行监听的逻辑。所以基于这段代码,我们可以不通过@Async注解实现对事件的异步监听,而是复写SimpleApplicationEventMulticaster这个类的方法,如下:

@Component

相比于通过@Async注解实现对事件的异步监听,我更倾向于通过复写的方式进行实现,主要原因就是如果通过注解实现,那么所有加了这个注解的方法在异步执行都是用的同一个线程池,这些加了注解的方法有些可能并不是进行事件监听的,这样显然是不合理的。而后面这种方式,我们可以确保创建的线程池是针对事件监听的,甚至可以根据不同的事件类型路由到不同的线程池。

总结

在这篇文章中,完成了对ApplicationContext中以下两点内容

  1. 借助于Resource一系列接口,完成对底层资源的访问及加载
  2. 实现事件的发布

对于整个ApplicationContext体系,目前来说还剩一个很大的功能没涉及到。因为我们也知道ApplicationContext继承了一系列的BeanFactory接口,所以它还会负责创建,管理及配置bean。

BeanFactory本身也有一套自己的体系,在下篇文章中,就写BeanFactory相关的内容。虽然这一系列文章是以ApplicationContext命名的,但是其中的内容覆盖面很广,这些东西对于我们看懂Spring很重要

希望大家跟我一起慢慢啃掉Spring,加油!共勉!

不吃竹子的滚滚:Spring源码分析(十四)Spring中的BeanWrapper及类型转换

不吃竹子的滚滚:Spring源码分析(十二)ApplicationContext详解(中)

不吃竹子的滚滚:Spring源码分析(十一)ApplicationContext详细介绍(上)

不吃竹子的滚滚:Spring源码分析(十)Spring中Bean的生命周期(下)

不吃竹子的滚滚:Spring源码分析(九)Spring中Bean的生命周期(上)

不吃竹子的滚滚:Spring源码分析(八)容器的扩展点(BeanPostProcessor)

不吃竹子的滚滚:Spring源码分析(七)容器的扩展点(FactoryBean)

不吃竹子的滚滚:Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)

不吃竹子的滚滚:Spring源码分析(五)BeanDefinition(下)

不吃竹子的滚滚:Spring源码分析(四)BeanDefinition(上)

不吃竹子的滚滚:Spring源码分析(三)自动注入与精确注入

不吃竹子的滚滚:Spring源码分析(二)依赖注入及方法注入

不吃竹子的滚滚:Spring源码分析(一)Spring容器及Spring Bean

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值