Spring 小记

摘抄自 Spring揭秘

BeanFactoryProcessor

  1. PropertyPlaceholderConfiguration: 帮助解析xml文件中的数据源配置内容
1
2
3
4
5
6
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

在加载完所有BeanDefinition时,对象的属性信息还是以占位符的形式存在,当PropertyPlaceholderConfiguration作为BeanFactoryPostProcessor被应用时,会将相应的配置信息替换占位符。

  1. PropertyOverideConfigurer: 覆盖配置文件的信息
1
2
3
4
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<!--替换为jdbc.properties中的属性-->
<property name="location" value="jdbc.properties"></property>
</bean>

其中jdbc.properties:

1
2
3
beanName.propertyName=value
# 也就是说把 某个bean的某个属性替换为value
# dataSource.maxActive=50

PropertyPlaceholderConfigurationPropertyOverideConfigurer 都继承于 PropertyResourceConfigurer, 它提供了一个方法 #convertProperties 允许子类覆盖实现对相应配置项的转换,如将加密后的字符串进行揭秘后再覆盖到原有的bean中。

  1. CustomEditorConfigurer:辅助的将后期需要用到的信息注册到容器,也就是把各个属性对应的 PropertyEditor 注册到容器中,以供后面的 BeanWrapper 使用

    用xml加载bean时,由于xml记载的都是String类型,想要完成字符串到具体对象的转换,就需要相应的转换规则,CustomEditorConfigurer 就是帮助我们做这些的

    Spring内部通过 PropertyEditor 来帮助进行String类型转换到其它类型的工作,只要为每种对象提供一个 PropertyEditor ,就可以根据该对象类型取得其相应的 PropertyEditor 来做具体的转换。

    • StringArrayPropertyEditor:将符合CSV格式的字符串转换成String[]数组的形式,默认是以逗号分隔的字符串
    • ClassPropertyEditor:根据String类型的class名称,直接转换成相应的Class对象

    比如我们现在有某个需求,系统的某个部分需要用yyyy-MM-dd的形式表现日期,但是另一个部分需要采用yyyyMMdd的形式表现日期,默认情况下我们没有这种转换模式,因此我们需要如何定义这种转换方式。

    1. 给出针对特定对象类型的 PropertyEditor 的实现

    可以通过继承 java.beans.PropertyEditorSupport ,实现其 #setAsText 方法即可

    1. 通过 CustomEditorConfigurer 注册自定义的 PropertyEditor

生命周期

image-20220429114201577

Bean的初始化与BeanWrapper

分为实例化对象设置对象属性两步

实例化对象

容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。默认情况下,容器内部采用的是 CglibSubclassingInstantiationStrategy, 即通过CGLIB的动态字节码生成

容器只要根据相应bean定义的 BeanDefintion 取得实例化信息,结合 CglibSubclassingInstantiationStrategy
以及不同的bean定义类型,就可以返回实例化完成的对象实例。但是,返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper 对构造完成的对象实例进行包裹,返回相应的 BeanWrapper 实例。

设置对象属性

BeanWrapper定义继承了 org.springframework.beans.PropertyAccessor 接口,可以以统一的方式对对象属性进行访问BeanWrapper定义同时又直接或者间接继承了PropertyEditorRegistryTypeConverter接口。在第一步构造完成对象之后,Spring会根据对象实例构造一个BeanWrapperImpl实例,然后将之前CustomEditorConfigurer注册的PropertyEditor复制一份给BeanWrapperImpl实例(这就是BeanWrapper同时又是PropertyEditorRegistry的原因)。这样,当BeanWrapper转换类型、设置对象属性值时,就不会无从下手了。

BeanWrapper 作为 BeanDefinition 向 Bean 转换过程中的中间产物,承载了 Bean 实例的包装、类型转换、属性的设置以及访问等重要作用。

检查Aware接口并设置相关依赖

  • BeanNameAware:会将该对象实例的bean定义对应的beanName设置到当前对象实例
  • BeanClassLoaderAware:会将对应加载当前bean的Classloader注入当前对象实例
  • BeanFactoryAware:BeanFactory容器会将自身设置到当前对象实例
  • ApplicationContextAware:会将 ApplicationContext 注入当前对象实例

BeanPostProcessor

前置处理

通常比较常见的使用 BeanPostProcessor 的场景,是处理标记接口实现类,或者为当前对象提供代理实现。

ApplicationContext 中每个对象的实例化过程走到 BeanPostProcessor 前置处理这一步时,ApplicationContext 容器会检测到之前注册到容器的 ApplicationContextAwareProcessor 这个 BeanPostProcessor 的实现类,然后就会调用其postProcessBeforeInitialization() 方法,检查并设置Aware相关依赖。

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
// 对之前实现了的Aware接口做一些操作
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware)bean).setEnvironment(this.applicationContext.getEnvironment());
}

if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware)bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}

if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware)bean).setResourceLoader(this.applicationContext);
}

if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(this.applicationContext);
}

if (bean instanceof MessageSourceAware) {
((MessageSourceAware)bean).setMessageSource(this.applicationContext);
}

if (bean instanceof ApplicationStartupAware) {
((ApplicationStartupAware)bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
}

if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware)bean).setApplicationContext(this.applicationContext);
}

}

除了检查标记接口以便应用自定义逻辑,还可以通过BeanPostProcessor对当前对象实例做更多的处理。比如替换当前对象实例或者字节码增强当前对象实例等.

InstantiationAwareBeanPostProcessor :在所有的步骤之前,也就是实例化bean对象步骤之前,容器会首先检查容器中是否注册有InstantiationAwareBeanPostProcessor类型的BeanPostProcessor。如果有,首先使用相应的InstantiationAwareBeanPostProcessor来构造对象实例。构造成功后直接返回构造完成的对象实例,而不会按照“正规的流程”继续执行

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
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
// 用于实例化之前,如果该方法返回了一个beanClass的对象,则返回该对象而不进入之后的CreateBean阶段(依然会执行BeanPostProcessor.postProcessAfterInitialization)
@Nullable //对象初始化之前执行
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// 完成实例化后执行,如果该方法返回了false,则跳过依赖注入阶段
//对象初始化之后执行
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}

// 依赖注入之前执行,用于自定义的注值
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return null;
}

/** @deprecated */
@Deprecated
@Nullable
default PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return pvs;
}
}

InitializingBean和init-method

InitializingBean 是容器内部广泛使用的一个对象生命周期标识接口该接口定义很简单,其作用在于,在对象实例化过程调用过 “ BeanPostProcessor 的前置处理” 之后,会接着检测当前对象是否实现了 InitializingBean 接口,如果是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态。

虽然该接口在Spring容器内部广泛使用,但如果真的让我们的业务对象实现这个接口,则显得Spring容器比较具有侵入性。所以,Spring还提供了另一种方式来指定自定义的对象初始化操作,那就是在XML配置的时候,使用<bean>的init-method属性。

通过init-method,系统中业务对象的自定义初始化操作可以以任何方式命名,而不再受制于InitializingBeanafterPropertiesSet()。如果系统开发过程中规定:所有业务对象的自定义初始化操作都必须以init()命名,为了省去挨个<bean>的设置init-method这样的烦琐,我们还可以通过最顶层的<beans>的default-init-method统一指定这一init()方法名。

DisposableBean与destroy-method

当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了DisposableBean 接口。或者其对应的bean定义是否通过<bean>的 destroy-method 属性指定了自定义的对象销毁方法。如果是,
就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。
InitializingBeaninit-method用于对象的自定义初始化相对应,DisposableBeandestroy-method为对象提供了执行自定义销毁逻辑的机会。最常见到的该功能的使用场景就是在Spring容器中注册数据库连接池,在系统退出后,连接池应该关闭,以释放相应资源。

ApplicationContext

作为Spring提供的较之 BeanFactory 更为先进的IoC容器实现,ApplicationContext 除了拥有 BeanFactory 支持的所有功能之外,还进一步扩展了基本容器的功能.

统一的资源加载策略

资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等

资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方

Spring提出一套基于ResourceResourceLoader接口的资源抽象和加载策略

常见的Resource:

  • ByteArrayResource:将字节(byte)数组提供的数据作为一种资源进行封装
  • ClassPathResource:该实现从Java应用程序的ClassPath中加载具体资源并进行封装
  • FileSystemResource:对java.io.File类型的封装
  • UrlResource:通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具体的资源操作

常见的ResourceLoader:

  • DefaultResourceLoader
    • 首先检查资源路径是否以 classpath: 前缀打头,如果是,则尝试构造 ClassPathResource 类型资源并返回
    • 尝试通过 URL ,根据资源路径来定位资源,如果没有抛出MalformedURLException,有则会构造 UrlResource 类型的资源并返回
    • 如果还是无法根据资源路径定位指定的资源,则委派getResourceByPath(String) 方法来定位, DefaultResourceLoader 的getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回
  • FileSystemResourceLoader:继承自 DefaultResourceLoader但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以 FileSystemResource 类型返回

ResourceLoader的扩展:

ResourcePatternResolver:批量查找的ResourceLoaderResourcePatternResolverResourceLoader的扩展,ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例。

  • PathMatchingResourcePatternResolver:该实现类支持ResourceLoader级别的资源加载,支持基于Ant风格的路径匹配模式(类似于**/.suffix之类的路径形式),支持ResourcePatternResolver新增加的classpath:前缀等

    在构造PathMatchingResourcePatternResolver实例的时候,可以指定一个ResourceLoader,如果不指定的话, 则PathMatchingResourcePatternResolver内部会默认构造一个DefaultResourceLoader实例

Resource和ResourceLoader关系

Resource和ResourceLoader

ApplicationContext与ResourceLoader

ApplicationContext继承了ResourcePatternResolver,当然就间接实现了ResourceLoader接口。所以,任何的ApplicationContext实现都可以看作是一个ResourceLoader甚至ResourcePatternResolver

所有的ApplicationContext实现类会直接或者间接地继承org.springframework.context.support.AbstractApplicationContext,从这个类上,我们就可以看到ApplicationContextResourceLoader之间的所有关系。

  • 首先,AbstractApplicationContext继承了DefaultResourceLoader,所以它的 #getResource(String) 用的就是DefaultResourceLoader –> 它能做ResourceLoader的事情
  • 其次,AbstractApplicationContext类的内部声明有一个resourcePatternResolver,类型是ResourcePatternResolver,对应的实例类型为PathMatchingResourcePatternResolver –> 它能做ResourcePatternResolver的事情

说白了,ApplicationContext的实现类在作为ResourceLoader或者ResourcePatternResolver时候的行为,完全就是委派给了PathMatchingResourcePatternResolverDefaultResourceLoader来做

ApplicationContext与ResourceLoader

ApplicationContext启动伊始,会通过一个org.springframework.beans.support.ResourceEditorRegistrar来注册Spring提供的针对Resource类型的PropertyEditor实现到容器中,这个PropertyEditor叫做org.springframework.core.io.ResourceEditor。这样, ApplicationContext就可以正确地识别Resource类型的依赖了。

国际化信息支持

Locale和ResourceBundle

Locale

不同的Locale代表不同的国家和地区,每个国家和地区在Locale这里都有相应的简写代码表示,包括语言代码以及国家代码,这些代码是ISO标准代码。如,Locale.CHINA代表中国,它的代码表示为zh_CN;Locale.US代表美国地区,代码表示为en_US;而美国和英国等都属于英语地区,则可以使用Locale.ENGLISH来统一表示,这时代码只有语言代码,即en。

ResourceBundle

ResourceBundle用来保存特定于某个Locale的信息(可以是String类型信息,也可以是任何类型的对象)。通常,ResourceBundle管理一组信息序列,所有的信息序列有统一的一个basename,然后特定的Locale的信息,可以根据basename后追加的语言或者地区代码来区分。

MessageSource与ApplicationContext

通过MessageSource接口,我们统一了国际化信息的访问方式。传入相应的Locale、资源的键以及相应参数,就可以取得相应的信息,再也不用先根据Locale取得ResourceBundle,然后再从ResourceBundle查询信息了。

ApplicationContext实现了MessageSource接口,也就是说它可以作为MessageSource使用

在默认情况下,ApplicationContext将委派容器中一个名称为messageSourceMessageSource接口实现来完成MessageSource应该完成的职责。如果找不到这样一个名字的MessageSource实现,ApplicationContext内部会默认实例化一个不含任何内容的StaticMessageSource实例,以保证相应的方法调用。

常见的MessageSource实现类:

  • StaticMessageSource:MessageSource接口的简单实现,可以通过编程的方式添加信息条目,多用于测试,不应该用于正式的生产环境
  • ResourceBundleMessageSource:基于标准的java.util.ResourceBundle而实现的MessageSource,对其父类AbstractMessageSource的行为进行了扩展,提供对多个ResourceBundle的缓存以提高查询速度。同时,对于参数化的信息和非参数化信息的处理进行了优化,并对用于参数化信息格式化的MessageFormat实例也进行了缓存。它是最常用的、用于正式生产环境下的MessageSource实现。
  • ReloadableResourceBundleMessageSource:同样基于标准的java.util.ResourceBundle而构建的MessageSource实现类,但通过其cacheSeconds属性可以指定时间段,以定期刷新并检查底层的properties资源文件是否有变更。可以通过properties加载信息。

MessageSource

ApplicationContext启动的时候,会自动识别容器中类型为MessageSourceAware的bean定义,并将自身作为MessageSource注入相应对象实例中。如果某个业务对象需要国际化的信息支持,那么最简单的办法就是让它实现MessageSourceAware接口,然后注册到ApplicationContext容器。不过这样一来,该业务对象对ApplicationContext容器的依赖性就太强了,显得容器具有较强的侵入性。

而实际上,如果真的某个业务对象需要依赖于MessageSource的话,直接通过构造方法注入或者setter方法注入的方式声明依赖就可以了。只要配置bean定义时,将ApplicationContext容器内部的那个messageSource注入该业务对象即可。

既然MessageSource可以独立使用,那为什么还让ApplicationContext实现该接口呢?在独立运行的应用程序(Standalone Application)中,就如我们上面这些应用场景所展示的那样,直接使用MessageSource的相应实现类就行了。不过在Web应用程序中通常会公开ApplicationContext给视图(View)层,这样,通过标签(tag)就可以直接访问国际化信息了

容器内部事件发布

自定义事件发布流程

  1. 给出自定义事件类型(define your own event object)。为了针对具体场景可以区分具体的事件类型,我们需要给出自己的事件类型的定义,通常做法是扩展java.util.EventObject类来实现自定义的事件类型。
  2. 实现针对自定义事件类的事件监听器接口(define custom event listener)。自定义的事件监听器需要在合适的时机监听自定义的事件,事件监听器接口定义继承了java.util.EventListener。
  3. 组合事件类和监听器,发布事件。有了自定义事件和自定义事件监听器,剩下的就是发布事件,然后让相应的监听器监听并处理事件了。通常情况下,我们会有一个事件发布者(EventPublisher),它本身作为事件源,会在合适的时点,将相应事件发布给对应的事件监听器。
自定义事件

Spring容器内部事件发布

Spring 的ApplicationContext 容器内部允许以org.springframework.context.ApplicationEvent
的形式发布事件, 容器内注册的org.springframework.context.ApplicationListener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有ApplicationEvent类型的事件。也就是说,一旦容器内发布ApplicationEvent及其子类型的事件,注册到容器的ApplicationListener就会对这些事件进行处理。

  • ApplicationEvent

    Spring容器内自定义事件类型,继承自java.util.EventObject,它是一个抽象类,需要根据情况提供相应子类以区分不同情况。

    • ContextClosedEvent:ApplicationContext容器在即将关闭的时候发布的事件类型。
    • ContextRefreshedEvent:ApplicationContext容器在初始化或者刷新的时候发布的事件类型。
    • RequestHandledEvent:Web请求处理后发布的事件,其有一子类ServletRequestHandledEvent提供特定于Java EE的Servlet相关事件
  • ApplicationListener

    ApplicationContext容器内使用的自定义事件监听器接口定义,继承自java.util.EventListenerApplicationContext容器在启动时,会自动识别并加载EventListener类型bean定义,一旦容器内有事件发布,将通知这些注册到容器的EventListener

  • ApplicationContext

    ApplicationContext接口定义还继承了ApplicationEventPublisher接口,该接口提供了void publishEvent(ApplicationEvent event)方法定义。不难看出,ApplicationContext容器现在担当的就是事件发布者的角色

ApplicationContext容器的具体实现类在实现事件的发布和事件监听器的注册方面,并没事必躬亲,而是把这些活儿转包给了一个称作org.springframework.context.event.ApplicationEventMulticaster的接口。该接口定义了具体事件监听器的注册管理以及事件发布的方法,但接口终归是接口,还得有具体实现。ApplicationEventMulticaster有一抽象实现类——org.springframework.
context.event.AbstractApplicationEventMulticaster它实现了事件监听器的管理功能。出于灵活性和扩展性考虑,事件的发布功能则委托给了其子类。org.springframework.context.event.SimpleApplicationEventMulticaster 是Spring 提供的AbstractApplicationEventMulticaster的一个子类实现,添加了事件发布功能的实现。不过,其默认使用了SyncTaskExecutor进行事件的发布。与我们给出的样例事件发布者实现一样,事件是同步顺序发布的。为了避免这种方式可能存在的性能问题, 我们可以为其提供其他类型的TaskExecutor 实现类。
因为ApplicationContext容器的事件发布功能全部委托给了ApplicationEventMulticaster来做,所以,容器启动伊始,就会检查容器内是否存在名称为applicationEventMulticasterApplicationEventMulticaster对象实例。有的话就使用提供的实现,没有则默认初始化一个SimpleApplicationEventMulticaster作为将会使用的ApplicationEventMulticaster。3

Spring容器内部事件发布