第4章 详述Spring配置和Spring Boot

4.2 管理bean生命周期

通常,有两个生命周期事件与bean特别相关:post-initializationpre-destruction

一旦完成bean的所有属性值设置以及所配置的依赖项检查,就会触发post-initialization事件。在销毁bean实例之前,pre-destruction事件被触发。对于原型作用域的bean来说,不会触发pre-destruction事件。

Spring为这两个事件提供了三种机制:

  1. 基于接口;InitializingBean/DisposableBean
  2. 基于方法;bean定义的init-method/destroy-method(或@BeaninitMethod/destroyMethod
  3. 基于注解(JSR-250 JavaBean生命周期);@PostConstruct/@PreDestroy

Spring bean的生命周期:

20191103 《Spring5高级编程》笔记-第4章-LMLPHP

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-beanfactory-method属性

有时需要实例化由非Spring的第三方应用程序提供的JavaBean。此时,不知道如何实例化该类,只知道第三方应用程序提供了一个可用于获取Spring应用程序所需的JavaBean实例的类。这种情况下,可以使用<bean>中的factory-beanfactory-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>

20191103 《Spring5高级编程》笔记-第4章-LMLPHP

对映射文件中的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接口扩展了ResourcePatternResolverResourceLoader接口的子接口),用来访问文件(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属性。

20191103 《Spring5高级编程》笔记-第4章-LMLPHP

有时出于测试目的,可以将配置类声明为静态内部类。

@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 EnviromentPropertySource抽象

除配置文件外,Environment接口封装的其他关键信息都是属性。属性用来存储应用程序的底层环境配置,例如应用程序文件夹的文职、数据库连接信息等。

Spring中的EnvironmentPropertySource抽象功能帮助开发人员访问来自运行平台的各种配置信息。在抽象环境中,所有系统属性、环境变量和应用程序属性都有Environment接口提供,Spring启动ApplicationContext时将填充该接口。

对于PropertySource抽象,Spring按照以下默认顺序访问属性:

  1. 运行JVM的系统属性;
  2. 环境变量;
  3. 应用程序定义的属性;

可以通过 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

05-11 20:26