第4章 详述Spring配置和Spring Boot
4.2 管理bean生命周期
通常,有两个生命周期事件与bean特别相关:post-initialization
和pre-destruction
。
一旦完成bean的所有属性值设置以及所配置的依赖项检查,就会触发post-initialization
事件。在销毁bean实例之前,pre-destruction
事件被触发。对于原型作用域的bean来说,不会触发pre-destruction
事件。
Spring为这两个事件提供了三种机制:
- 基于接口;
InitializingBean
/DisposableBean
- 基于方法;bean定义的
init-method
/destroy-method
(或@Bean
的initMethod
/destroyMethod
) - 基于注解(JSR-250 JavaBean生命周期);
@PostConstruct
/@PreDestroy
Spring bean的生命周期:
4.3 挂钩到bean的创建
4.3.1 在创建bean时执行方法
在<beans>
中,添加属性 default-lazy-init="true"
(对应@Lazy
注解)来指示Spring仅在应用程序请求bean时才实例化配置文件中定义的bean。如果没有指定该属性,那么Spring将尝试在启动ApplicationContext的过程中初始化所有的bean。
在<beans>
中,还可以指定默认的初始化方法和销毁方法,default-init-method=""
default-destroy-method=""
初始化方法的唯一限制是不能接受任何参数。
4.3.3 使用JSR-250 @PostConstruct
注解
使用@PostConstruct
需要在配置文件中添加 <context:annotation-config/>
三种方法,如果不考虑移植性,使用 InitializingBean
接口。
4.4 使用@Bean
声明一个初始化方法
Spring首先调用@PostConstruct
注解的方法(1. 基于注解),然后调用afterPropertiesSet()
(2. 基于接口),最后调用配置文件中指定的初始化方法(3. 基于方法)。
4.7 了解解析的顺序
使用关闭钩子
在Spring中销毁回调函数的唯一缺点是它们不会自动触发;需要记住在应用程序关闭之前调用 AbstractApplicationContext.close()
或使用AbstractApplicationContext.registerShutdownHook()
。该方法自动指示Spring注册底层JVM运行时的关闭钩子。
4.8 让Spring感知bean
4.8.1 使用BeanNameAware
接口
想要获取自己名称地bean,可以实现 BeanNameAware
接口,它有一个方法 setBeanName(String)
。在完成bean的配置之后且在调用任何生命周期回调(初始化回调或销毁回调)之前,Spring会调用setBeanName()
方法。
这里获取名称是获取bean的id,而不是name。
4.8.2 使用ApplicationContextAware
接口
通过使用 ApplicationContextAware
接口,bean可以获得对配置它们的 ApplicationContext
实例的引用。创建此接口的主要原因,是为了允许bean在应用程序中访问Spring的ApplicationContext
,但是应该避免这种做法,并使用依赖注入为bean添加协作者。
AbstractApplicationContext.close()
和AbstractApplicationContext.registerShutdownHook()
区别:
close()
立即调用doClose()
方法进行容器销毁工作;registerShutdownHook()
将销毁工作加入虚拟机关闭钩子,随虚拟机关闭时销毁。
如果在实现ApplicationContextAware接口的setApplicationContext方法中分别调用这两个方法,会有差别。
Spring的refresh()
方法的顺序是先调用
finishBeanFactoryInitialization(beanFactory);
-> postProcessBeforeInitialization
-> ApplicationContextAware
,后finishRefresh
-> initLifecycleProcessor
,也就是说,先调用ApplicationContextAware
接口的setApplicationContext
方法,后初始化生命周期相关的方法。
这导致,如果在setApplicationContext
方法中调用close
方法,不会调用bean的销毁方法。而在setApplicationContext
方法中调用registerShutdownHook
方法,因为实在虚拟机关闭时才执行销毁方法,此时已经初始化好了生命周期相关的方法,所以会执行bean的销毁方法。
4.9 使用FactoryBean
当使用的类无法通过new
操作符创建时,FactoryBean
是完美的解决方案。如果使用通过工厂方法创建的对象,并且希望在Spring应用程序中使用这些类,那么可以创建FactoryBean
以充当适配器,从而使类可以充分利用Spring的IOC功能。
FactoryBean
是一个bean,可以作为其他bean的工厂。FactoryBean
像任何普通bean一样在ApplicationContext
中配置,但是当Spring使用FactoryBean
接口来满足依赖或查找请求时,它并不返回FactoryBean,而是调用FactoryBean.getObject()
方法并返回调用的结果。
4.10 直接访问FactoryBean
访问FactoryBean很简单,在调用getBean()
时用"&
"符号作为bean名称的前缀即可。
4.11 使用factory-bean
和factory-method
属性
有时需要实例化由非Spring的第三方应用程序提供的JavaBean。此时,不知道如何实例化该类,只知道第三方应用程序提供了一个可用于获取Spring应用程序所需的JavaBean实例的类。这种情况下,可以使用<bean>
中的factory-bean
和factory-method
属性。
4.12 JavaBean PropertyEditor
java.beans.PropertyEditor
是一个接口,将属性值从其本机类型表示形式转换为字符串。最初,该接口的设计目的是允许将属性值作为字符串值输入到编辑器中,并将它们转换为正确的类型。
为了避免认为创建String类型的属性,Spring允许定义PropertyEditor
以实现基于字符串的属性值到正确的类型的转换。Spring对PropertyEditor 的支持在org.springframework.beans.propertyeditors
包下,以及org.springframework.core.io.ResourceEditor
类。
4.12.1 使用内置的PropertyEditor
Spring在refresh()
的prepareBeanFactory(beanFactory);
里 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
这里添加了默认的PropertyEditorRegistrar
,会注册一些,具体可看ResourceEditorRegistrar#registerCustomEditors
。
自定义PropertyEditorRegistrar
时,可以通过org.springframework.beans.factory.config.CustomEditorConfigurer
,这是一个BeanFactoryPostProcessor
,Spring在 invokeBeanFactoryPostProcessors(beanFactory);
中会调用beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
将PropertyEditorRegistrar
加入容器,也就是AbstractBeanFactory#propertyEditorRegistrars
中。
示例:
<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"
p:propertyEditorRegistrars-ref="propertyEditorRegistrarsList"/>
<util:list id="propertyEditorRegistrarsList">
<bean class="study.hwj.chapter04.propertyeditor.PropertyEditorBean$CustomPropertyEditorRegister"/>
</util:list>
对映射文件中的String进行处理时,查找对应类型的PropertyEditor
代码在TypeConverterDelegate#convertIfNecessary
,调用阶段位于refresh()
的finishBeanFactoryInitialization(beanFactory);
默认类型与PropertyEditor
的映射关系可以查看 PropertyEditorRegistrySupport#createDefaultEditors
4.12.2 创建自定义PropertyEditor
通过继承 java.beans.PropertyEditorSupport
并实现 setAsText
方法,可以快速实现自定义 PropertyEditor
。
通过CustomEditorConfigurer
将自定义的PropertyEditor
注册进Spring。
<bean name="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="study.hwj.chapter04.propertyeditor.FullName"
value="study.hwj.chapter04.propertyeditor.NamePropertyEditor"/>
</map>
</property>
</bean>
原理:
CustomEditorConfigurer
是一个BeanFactoryPostProcessor
,在postProcessBeanFactory
方法中,调用了this.customEditors.forEach(beanFactory::registerCustomEditor);
从版本3开始,Spring引入了类型转换API
(Type Conversion API)和字段格式化SPI
(Field Formatting Service Provider Interface),它们提供了一个更简单且结构良好的API来执行类型转换和字段格式化。这对于WEB应用程序开发来说尤其有用。
4.13 更多的Spring ApplicationContext配置
在Spring中,BeanFactory
接口的各种实现负责bean实例化,为Spring管理的bean提供依赖注入和生命周期支持。作为BeanFactory接口的扩展,ApplicationContext
的主要功能是提供一个更丰富的框架来构建应用程序。它允许以完全声明的方式配置和管理Spring以及Spring所管理的资源。这意味着Spring 尽可能提供支持类来自动将ApplicationContext加载到应用程序中,从而不需要编写任何代码来访问ApplicationContext。目前仅在构建Web应用程序时可用。
4.13.1 使用MessageSource
进行国际化
Spring真正擅长的领域是支持国际化(i18n)。通过MessageSource
接口,应用程序可以访问以各种语言存储的字符串资源(称为消息)。对于希望在应用程序中得到支持的每种语言,都会维护一个与其他语言中的消息相对应的消息列表。
ApplicationContext接口扩展了MessageSource,并对加载信息以及特定环境下的可用性提供了特别的支持。虽然消息的自动加载在任何环境都可用,但是只有在某些Spring管理的场景中才提供自动访问。
使用MessageSource进行国际化
初了ApplicationContext,Spring还提供了三个MessageSource实现:
ResourceBundleMessageSource
ReloadableResourceBundleMessageSource
StaticMessageSource
StaticMessageSource
无法在外部配置,不应该在生产环境使用。ResourceBundleMessageSource
通过使用Java ResourceBundle
加载消息。ReloadableResourceBundleMessageSource
基本上相同,只不过它支持对基础源文件进行预定的重新加载。这三个MessageSource都实现了另一个名为HierarchicalMessageSource
的接口,它允许嵌套多个MessageSource实例。
要想充分利用ApplicationContext对MessageSource的支持,必须定义一个名为messageSource
的MessageSource类型的bean。
在查找特定语言环境下的消息时,ResourceBundle
会查找以基本名称和语言环境名称组合的文件。例如,如果基本名称是label,在简体中文(zh_CN
)的语言环境中,那么ResourceBundle
会查找label_zh_CN.properties
的文件。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basenames-ref="basenames"/>
<util:list id="basenames">
<value>label</value>
</util:list>
为什么使用ApplicationContext作为MessageSource
为了Spring对Web应用程序的支持。Spring可以尽可能的将ApplicationContext作为MessageSource公开到视图层。这意味着当使用Spring的JSP标记库时,<spring:message>
标记会自动从ApplicationContext读取信息,当使用JSTL时,<fmt:message>
也会执行相同的操作。
4.13.2 在独立的应用程序中使用MessageSource
MessageSourceResolvable
接口
从MessageSource查找消息时,可以使用实现了MessageSourceResolvable
的对象代替键和一组参数。该接口在Spring验证库中被广泛使用,用来将Error
对象链接到对应的国际化错误消息。
4.13.3 应用程序事件
事件是派生自ApplicationEvent
的类,而ApplicationEvent派生自java.util.EventObject
。任何bean都可以通过实现ApplicationListener<T>
接口来监听事件;当配置时,ApplicationContext会自动注册实现此接口的任何bean作为监听器。事件通过ApplicationEventPublisher.publishEvent()
方法发布,而ApplicationContext扩展了ApplicationEventPublisher
接口,所以可以让发布bean实现ApplicationContextAware
,使其获得ApplicationContext后发布事件。
不需要特殊配置就可以使用ApplicationContext注册ApplicationListener
,它由Spring自动获取。
发布事件时,最好发布ApplicationEvent
,而不是其他类型。发布xxxApplicationEvent
,实现了ApplicationListener<xxxApplicationEvent>
的Listener
可以接收到消息;发布其他类型,内容会被封装为PayloadApplicationEvent
,可以通过实现ApplicationListener<PayloadApplicationEvent>
的Listener接收到消息。
4.14 访问资源
Spring以独立于协议的方式提供了访问资源的统一机制。
Spring的资源支持的核心是org.springframework.core.io.Resource
接口。
Spring的ApplicationContext接口扩展了ResourcePatternResolver
(ResourceLoader
接口的子接口),用来访问文件(file:
)、类路径(classpath:
)或URL资源(http:
)。
classpath:
协议是特定于Spring的,指示ResourceLoader
应该在类路径中查找资源。
一旦获得Resource
实例,就可以自由的访问内容。当使用http:
协议时,对getFile()
的调用会导致FileNotFoundException
异常。出于这个原因,建议使用getInputStream()
来访问资源内容,因为它可能适用于所有可能的资源类型。
4.15.1 Java中的ApplicationContext配置
可以使用配置类(被@Configuration
注解)来替代xml配置文件。@Configuration注解用来告知Spring这是一个基于Java的配置文件。该类将包含用@Bean
注解的方法,它们表示bean声明。@Bean注解等同于<bean>
标记,方法名称等同于<bean>
标记内的id
属性。
有时出于测试目的,可以将配置类声明为静态内部类。
@ComponentScan
告诉Spring应该扫描那些带有bean定义注解的包。它与XML配置中的<context:component-scan>
标签相同。
@Import
注解通过导入配置类所定义的bean,可以从另一个配置类访问该bean。
4.15.2 Spring混合配置
在Java配置类中可以使用@ImportResource
注解从XML文件导入bean声明。另外,也可以执行相反的操作:在Java配置类中定义的bean可以导入XML配置文件。必须声明配置类类型的bean,并且必须使用<context:annotation-config/>
启用对注解方法的支持。这使得在类中声明的bean可以被配置为在XML文件中声明的bean的依赖项。
@Configuration
public class AppConfigSix {
@Bean
public MessageProvider provider() {
return new ConfigurableMessageProvider("Love 6");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="study.hwj.chapter04.javaconfig.AppConfigSix"/>
<bean id="messageRenderer" class="study.hwj.chapter04.javaconfig.StandardOutMessageRenderer"
p:messageProvider-ref="provider"/>
</beans>
4.15.3 Java或XML配置?
每种方法都有各自的优缺点,但决定使用其中一种方法时,就应该坚持使用并保持配置样式一致,而不是一会用Java类,一会儿用XML文件。使用一种方法会让维护工作更容易。
4.16 配置文件(Profile)
配置文件(configuration profile)只是Spring仅配置在指定配置文件处于活动状态时定义的ApplicationContext实例。
<beans>
标签的profile
属性指定配置文件的profile,只有当指定的profile处于active
状态时,文件中的bean才会被实例化。
通过传递JVM参数,-Dspring.profiles.active=xxx
,来激活profile。调用ctx.getEnvironment().setActiveProfiles("kindergarten");
可以以编程的方式在代码中激活profile。注意,需要在调用ctx.refresh()
之前设置,否则无效。
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.getEnvironment().setActiveProfiles("kindergarten");
ctx.load("classpath:chapter04/ac_configfile_*.xml");
ctx.refresh();
4.17 使用Java配置来配置Spring配置文件
@Profile
等价于<beans>
的profile
属性。一般配合@Configuration
注解使用。
解析Profile在ctx.load("classpath:chapter04/ac_configfile_*.xml");
这一步完成。
4.18 Enviroment
和PropertySource
抽象
除配置文件外,Environment
接口封装的其他关键信息都是属性。属性用来存储应用程序的底层环境配置,例如应用程序文件夹的文职、数据库连接信息等。
Spring中的Environment
和PropertySource
抽象功能帮助开发人员访问来自运行平台的各种配置信息。在抽象环境中,所有系统属性、环境变量和应用程序属性都有Environment接口提供,Spring启动ApplicationContext时将填充该接口。
对于PropertySource
抽象,Spring按照以下默认顺序访问属性:
- 运行JVM的系统属性;
- 环境变量;
- 应用程序定义的属性;
可以通过 propertySources.addxxx
调整访问顺序。
现实中,很少需要直接与Environment接口进行交互,但会以 ${}
的形式使用要给属性占位符,并将解析后的值注入Spring bean中。
使用<context:property-placeholder />
标签将属性从属性文件中加载到Spring的Environment
,该Environment被封装到ApplicationContext接口中。PropertySource
会按照默认行为去解析,如果想要覆盖并使用自定义的属性值,使用local-override="true"
。
4.19 使用JSR-330注解进行配置
JSR-330
提供Java的依赖注入支持,使用JSR-330可以帮助我们轻松从Spring迁移到JEE6容器或其他兼容的IOC容器。
要支持JSR-330注解,需要将javax.inject3
依赖项添加到项目中。
JSR-330标准注解:
@Named
:与Spring中的@Component
注解相同@Inject
:用于自动注入,类似于@Autowired
@Singleton
:标识单例bean,在JSR-330标准中,bean默认作用域是非单例,即Spring的prototype作用域
JSR-330注解与Spring注解差异:
- Spring的
@Autowired
可以指定required
属性表明必须完成DI,JSR-330的@Inject
没有等价属性。Spring还提供了@Qualifier
注解精细控制自动装配 - JSR-330仅支持单例和非单例作用域,Spring支持更多作用域。
- Spring中,可以使用
@Lazy
实现懒加载,JSR-330没有等价注解
相比于使用JSR-330,更推荐使用Spring注解,除非应用程序需要独立于IOC容器。
4.20 使用Groovy进行配置
可以通过Groovy语言来配置bean定义和ApplicationContext。
4.21 Spring Boot
Spring Boot项目旨在简化使用Spring构建应用程序的入门体验。
最简单的Spring Boot配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
如果@SpringBootApplication
没有定义组件扫描属性,那么它将扫描其注解的类所在的包。
Spring Boot为Web应用提供了起始依赖项spring-boot-starter-web
。