第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的生命周期:

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>

对映射文件中的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属性。

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

@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

20191103 《Spring5高级编程》笔记-第4章的更多相关文章

  1. C#高级编程笔记之第二章:核心C#

    变量的初始化和作用域 C#的预定义数据类型 流控制 枚举 名称空间 预处理命令 C#编程的推荐规则和约定 变量的初始化和作用域 初始化 C#有两个方法可以一确保变量在使用前进行了初始化: 变量是字段, ...

  2. C#高级编程笔记之第一章:.NET体系结构

    1.1 C#与.NET的关系 C#不能孤立地使用,必须与.NET Framework一起使用一起考虑. (1)C#的体系结构和方法论反映了.NET基础方法论. (2)多数情况下,C#的特定语言功能取决 ...

  3. 20191105 《Spring5高级编程》笔记-【目录】

    背景 开始时间:2019/09/18 21:30 Spring5高级编程 版次:2019-01-01(第5版) Spring5最新版本:5.1.9 CURRENT GA 官方文档 Spring Fra ...

  4. 读《C#高级编程》第1章问题

    读<C#高级编程>第1章 .Net机构体系笔记 网红的话:爸爸说我将来会是一个牛逼的程序员,因为我有一个梦,虽然脑壳笨但是做事情很能坚持. 本章主要是了解.Net的结构,都是一些概念,并没 ...

  5. Android高级编程笔记(四)深入探讨Activity(转)

    在应用程序中至少包含一个用来处理应用程序的主UI功能的主界面屏幕.这个主界面一般由多个Fragment组成,并由一组次要Activity支持.要在屏幕之间切换,就必须要启动一个新的Activity.一 ...

  6. C#高级编程9 第18章 部署

    C#高级编程9 第18章 部署 使用 XCopy 进行部署 本主题演示如何通过将应用程序文件从一台计算机复制到另一台计算机来部署应用程序. 1.将项目中生成的程序集复制到目标计算机,生成的程序集位于项 ...

  7. C#高级编程9 第17章 使用VS2013-C#特性

    C#高级编程9 第17章 使用VS2013 编辑定位到 如果默认勾选了这项,请去掉勾选,因为勾选之后解决方案的目录会根据当前文件选中. 可以设置项目并行生成数 版本控制软件设置 所有文本编辑器行号显示 ...

  8. C#高级编程9 第16章 错误和异常

    C#高级编程9 第16章 错误和异常 了解这章可以学会如何处理系统异常以及错误信息. System.Exception类是.NET运行库抛出的异常,可以继承它定义自己的异常类. try块代码包含的代码 ...

  9. C#高级编程笔记之第三章:对象和类型

    类和结构的区别 类成员 匿名类型 结构 弱引用 部分类 Object类,其他类都从该类派生而来 扩展方法 3.2 类和结构 类与结构的区别是它们在内存中的存储方式.访问方式(类似存储在堆上的引用类型, ...

  10. UNIX环境高级编程笔记之文件I/O

    一.总结 在写之前,先唠几句,<UNIX环境高级编程>,简称APUE,这本书简直是本神书,像我这种小白,基本上每看完一章都是“哇”这种很吃惊的表情.其实大概三年前,那会大三,我就买了这本书 ...

随机推荐

  1. Stylus-富有表现力的、动态的、健壮的CSS

    今天总结一下Stylus记一些常用的也是最基本的用法 一.  选择器 Stylus是基于缩进的这让我们可以更快捷的编写css比如 body { margin:; paddind:; font-size ...

  2. 每日一蠢 .kettle 下的kettle.properties文件内配置的内容不能被识别

    昨天装封装好的ETL 工具  窝将环境变量中的KETTLE_HOME删除了, 结果 .kettle 下的kettle.properties文件内配置的内容不能被识别 can't parse argum ...

  3. bzoj3011 [Usaco2012 Dec]Running Away From the Barn 左偏树

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=3011 题解 复习一下左偏树板子. 看完题目就知道是左偏树了. 结果这个板子还调了好久. 大概已 ...

  4. TCP/IP基础总结性学习(7)

    确保 Web 安全的 HTTPS 在 HTTP 协议中有可能存在信息窃听或身份伪装等安全问题.使用 HTTPS 通信机制可以有效地防止这些问题. 一. HTTP 的缺点 HTTP 主要有这些不足,例举 ...

  5. 前端之JavaScript:JS简单介绍

    JavaScript(JS)之简单介绍 一.JavaScript的历史 1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后将其改名Scr ...

  6. Django【第22篇】:基于Ajax实现的登录

    基于ajax实现的登录 一.需要知道的新知识点 1.刷新验证码.给src属性加一个?号.加个?会重新去请求 //#给验证码刷新 $(".vialdCode_img").click( ...

  7. Facebook再现丑闻,约100位应用程序开发人员偷看用户数据

    Facebook今天披露了另一起安全事件,承认大约100名应用程序开发人员可能不正确地访问了某些Facebook组中的用户数据,包括他们的姓名和个人资料图片. 在周二发布的博客文章中,Facebook ...

  8. axios 各种请求方式传递参数

    get delete 方法较为不同 注意:每个方法的传参格式不同,具体用法看下方 get请求方式将需要入参的数据作为 params 属性的值,最后整体作为参数传递 delete请求方式将将需要入参的数 ...

  9. R语言-三种方法绘制单位圆

    与一般开发语言不同,R以数据统计分析和绘图可视化为主要卖点.本文是第一篇博客,解决一个简单的绘图问题,以练手为目的. 以下直接给出三种单位圆的画法: 方法1 f=seq(,*pi,0.001) x=s ...

  10. 什么是npm ? 什么是node ? 什么是vue-cli ?什么是webpack ?