A. spring类型转换器(三)
Converter用来将源数据类型转换目标数据类型,不过有时候一个数据类型会对应不同格式的字符串,如日期类型在不同国家显示的字符是不一样的,需要根据Locale进行转换,或者需要将日期类型转换为不同格式化的字符串,spring针对这种情况提供了Formatter接口来针对格式化进行处理。
格式化Formatter其实也是一种Converter,只是两个互转类型之间, 有一个固定是String类型 ,Formatter实现了不同格式的String类型与其他类型的类型互转
Formatter主要针对的是Number类型和日期类型。Spring对这两种类型的子类和字符串之间的转换提供了格式化实现,下面以日期类型的转换为例说明。
Formatter接口继承了Printer和Parser接口,一个用于将对象格式化为本地化的字符串,一个将字符串转换为对象。
AnnotationFormatterFactory集成了注解和Formatter。可以创建带有Annotation性质的Printer和Parser对象。
在上章节的继承图中可以看到FormattingConversionService继承了GenericConversionService。同时实现了FormatterRegistry和ConverterRegistry接口
FormatterRegistry扩展了ConverterRegistry接口,额外提供了注册Formatter、AnnotationFormatterFactory的功能
FormatterRegistrar接口用于批量注册Formatter的接口
spring提供了FormattingConversionService的默认实现,使用的时候一般都是直接使用这个类。
首先来看,在BeanFactory中只接收一个ConversionService变量,所以只能给spring容易配置一个ConversionService。那么到底应该用还是用DefaultConversionService?让我们来看中的实现
从源码可以看出,完全是对DefaultConversionService的扩展,在构造函数中调用了DefaultConversionService的addDefaultConverters完全拥有了DefaultConversionService所有的功能,所以只需要使用就可以
创建完成之后除了添加Converters,还注册了一些Formatters,这些Formaters主要是spring提供的用于针对 日期类型 、 Number类型 、 货币金额 进行格式化转换
接下来分析下Formater的注册功能。通过上述分析我们可以知道,Formater实质上是Class<?>和String之间的互转,所以在注册的时候,只需要提供Class<?>、Printer和Parser。来看FormattingConversionService类中的实现
可以看到的是注册Formater的过程,就是注册了一对Converter。注册Converter的过程已经在前文分析过,在这里我们来一起看下PrinterConverter和ParserConverter类。
可以看出PrinterConverter类型实现GenericConverter,用于实现传入类型到字符串的转换,具体的转换功能委托给参数Printer实现类对象实现。这里相当于是一个插件类保留。可以通过Printer实现任何类型到String类型的转换。
再来看ParserConverter
ParserConverter跟PrinterConverter实现了一个反方向的转换,至此注册通过一个Formater接口实现类,就可以完成Formater实现类中泛型到String类型之间的互转。
例如注册 LocalDateTime的转换器,实现LocalDateTime和String类型的互转
可以看到使用一个类TemporalAccessorPrinter和TemporalAccessorParser,并都传入了一个参数DateTimeFormatter,众所周知LocalDateTime和String格式化就是通过DateTimeFormatter来实现。那么我们猜测TemporalAccessorParser其实就是对DateTimeFormatter的封装调用
可以看到TemporalAccessorPrinter就是调用的DateTimeFormatter完成格式化的,不过这里使用到了ThreadLocal,可以先不管,TemporalAccessorParser中同理也会使用DateTimeFormatter完成String到日期的转换
Formatter的注册最终是注册了一对Converter,所以格式化转换完全就是Converter逻辑的实现,在前文已经分析过了,这里就不再赘述。
分析完了Formatter的注册和转换过程,一起来看下FormatConversionService提供了另外一种注册。前文提到了可以通过在对象字段上声明一个注解,在注解中指定格式化后字符串格式。这个功能就是通过AnnotationFormatterFactory来实现的。来看FormatConversionService的 () 方法
AnnotationFormatterFactory的注册,首先需要获取的就是AnnotationFormatterFactory泛型中的注解类型。然后通过 getFieldTypes() 获取所有声明的可以进行转换类型集合,然后循环注册了两个类型转换器AnnotationPrinterConverter和AnnotationParserConverter。那么来看一下AnnotationPrinterConverter到底如何完成可配置的类型转换的
可以看到的是,AnnotationPrinterConverter实现了ConditionalGenericConverter接口,在 matches() 方法中声明了该转换器只会作用于Class<?>上有指定的注解的类型。在convert方法中最终创建了一个PrinterConverter对象,使用PrinterConverter完成格式化的功能,这个在上面已经分析过了。唯一的不同就是Printer的获取。使用annotationFormatterFactory获取printer,并将注解作为参数传递进去。所以可以我们可以实现AnnotationFormatterFactory的 getPrinter() 方法提供转换为字符串的功能即可,通过看以通过参数annotation获取用户配置的格式。实现格式的可配置。
同样在AnnotationParserConverter中实现String到Class<?>的转换,调用AnnotationFormatterFactory获取Parser然后创建一个ParserConverter来实现类型转化。需要注意的是 注解只能添加在非String类型那一方上。
通过封装封装注册AnnotationPrinterConverter和AnnotationParserConverter,用户需要做的就只有是实现AnnotationFormatterFactory,在泛型中指定注解,然后实现了 getPrinter() 和 getParser() 来实现Class<?>和String之间的转换,其他的调用直接走Converter流程。来看Date类型格式化的实现
在DateFormatterRegistrar中,在注册Formatters之前,先注册了日期类型的一些converter,这里先不去管这个。最主要的是 方法,通过这个方法完成对Formater的注册
来看下实现了AnnotationFormatterFactory。并指定注解为DateTimeFormat。
可以看到的是在 getFieldType() 声明了支持转换的有Date、Calendar、Long。可以看到 getPrinter() 和 getFormatter() 方法返回了一个DateFormatter对象,并将用户配置的注解信息注入到这个DateFormatter对象中
接下来就是最终的格式化实现就是这个DateFormatter对象了。大家想一下,一般针对Date类型的格式化都会用什么呢?想必大家都猜到了,没错就是SimpleDateFormat。在DateFormatter中就是创建了一个SimpleDateFormat来实现类型和字符串的转换的
至此就实现了Formatter的注册和使用。那么在spring中是如何使用的呢?
没错就注册一个id为conversionService的对象。使用这个就可以了,不需要再注册ConversionServiceFactoryBean了。
B. spring自定义类型转换器,怎么不起作用
用自动扫描啊。component-scan可以根据你的条件设定扫描哪些位置,扫描哪些接口的实现,扫描包名,类名正则表达式等,完全可以满足你的需求。举例:使用Spring组件扫描的方式来实现自动注入bean,但是要如何对bean中通过set方法依赖的其他bean实现自动注入呢?现在不想使用@Resource注解来处理,因为代码已经写好。现在的配置如下:通过该配置可以实现对指定包指定类名的类实现自动装置bean,免去了在类上标注@Service注解。至于详细的配置,你可以搜索:Spring组件扫描使用详解
C. (新)springboot2.x ConversionService转换器
首先引用网上大多数人喜欢的这一段:
Binding 利用了一个新的 ApplicationConversionService 类,它提供了一些对属性绑定特别有用的额外转换器。最引人注目的是转换器的Duration类型和分隔字符串。
该Duration转换器允许在任一 ISO-8601 格式中指定的持续时间,或作为一个简单的字符串(例如10m,10 分钟)。现有的属性已更改为始终使用Duration。该@DurationUnit注释通过设置如果没有指定所使用的单元确保向后兼容性。例如,Spring Boot 1.5 中需要秒数的属性现在必须@DurationUnit(ChronoUnit.SECONDS)确保一个简单的值,例如10实际使用的值10s。
分隔字符串转换允许您将简单绑定String到Collection或Array不必分割逗号。例如,LDAP base-dn 属性用 @Delimiter(Delimiter.NONE),所以 LDAP DN(通常包含逗号)不会被错误解释。
其实上面起到了抛砖引引玉的作用,但是还不够详细,说白了就是指给了大的方向,下面我会给出具体详解:
一、
注解@DurationUnit(ChronoUnit.SECONDS)指明了sessionTimeout的单位是 秒,因此yml中可以指定30, PT30S and 30s,这几种写法都是正确的,表示i30秒
二、
前端可以传以逗号(,)分隔的string,后端集合或者List可以直接解析映射
三、
@ConfigurationProperties Validation
主要用于验证字段比如非空验证
D. spring mvc怎么转换数据格式
spring mvc数据绑定
spring mvc通过反射机制对目标方法的签名进行分析,将请求消息绑定到处理方法入参中。
数据绑定的核心部件是DataBinder。
spring
mvc主框架将servletRequest对象及处理方法入参对象实例传递给DataBinder,DataBinder调用装配在spring
mvc上下文中的ConversionService组件进行数据类型转换,数据格式化的工作,将servletRequest中的消息填充到入参对象中,然后再调用Validator组件对已绑定了请求消息数据的入参对象进行数据合法性检验,并最终生成数据绑定结果BindingResult对象,BindingResult包含了已完成数据绑定的入参对象,还包含相应的校验错误对象。
数据转换
conversionService是spring类型转换体系的核心接口,位于org.springframework.core.convert包中,可以利用org.springframework.context.support.ConversionServiceFactoryBean在spring上下文中定义一个ConversionSerivce。spring
自动识别出上下文中的ConversionService,并在Bean属性配置及springmvc处理方法入参绑定等场合使用它进行数据的转换。
<bean id="conversionSerivce"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
该FactoryBean创建ConversionSerivce内建了很多转换器,可通过该FactoryBean的convertors属性注册自定义的类型转换器
<bean id="conversionSerivce"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<propertyname="converters">
<list>
<bean class="xxxxx"/>
</list>
</property>
</bean>
spring 内置3中类型转换器接口,分别是
Converter<S,T>
ConverterFactory<S,R>
GenericConverter
自定义的类型转换器必须实现其中一个
Converter
Converter接口是spring最简单的转换器接口,仅包含一个方法
public interface Converter<S,T>{
Tconverter(S source);//负责将S类型对象转换成T类型对象
}
ConverterFactory<S,R>
ConverterFactory接口定义如下
public interface ConverFactory<S,R>{
<T,R>Converter<S,T> getConverter(Class<T> targetType);
}
S为源类型,R为目标类型的基类,T为扩展于R基类的类型。如spring的stringToNumberConverFactory就实现了ConverterFactory接口,封装了String转换成各种数据类型的Converter
在spring mvc中使用conversionSerivce
<userName>:<password>:<realName>-> User
1、<mvc:annotation-drivenconversion-service="conversionService"/>装配自定义conversionService
<mvc:annotation-driven>该标签可以简化springmvc相关配置,默认情况下,它会创建并注册一个默认的和一个,如果上下文中存在自定义的对应组件bean,spring
mvc会自动利用自定义的bean覆盖默认的,除此之外,<mvc:annotation-driven/>标签还会注册一个默认的ConversionService,即,由于要自定义converter,因此要显示定义一个ConversionSerivce覆盖默认的实现。
2、装配自定义转换器
<bean id="conversionSerivce"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<propertyname="converters">
<list>
<bean class="StringToUserConverter"/>
</list>
</property>
</bean>
3、编写转换器类
public class StringToUserConverter implementsConverter<String,User>{
publicUser convert(String source){
Useruser = new User();
if(source!=null){
Stringitems = source.split(“:”);
user.setUserName(items[0]);
user.setPassword(items[1]);
user.setRealName(items[2]);
}
}
}
@InitBinder
在控制器中使用@InitBinder添加自定义编辑器
@InitBinder
public void initBinder(WebDataBinder binder){
binder.registerCustomEditor(User.class,newUserEditor());
}
spring mvc使用WebDataBinder处理请求消息和处理方法入参的绑定工作,自定义编辑器必须实现PropertyEditor接口。
全局范围自定义编辑器
1、实现WebBindingInitalizer接口
public void initBinder(WebDataBinder binder,WebRequestrequest);
2、在web上下文中通过装配
<bean class="org.springframework.web.servlet.mvc.annotation.">
<propertyname="webBindingInitializer">
<bean class="xxx"/>
</property>
</bean>
spring mvc将按以下顺序查找类型转换编辑器
@InitBinder->ConversionService->WebBindingInitializer
数据格式化
spring 使用转换器进行源类型对象到目标类型对象的转换,spring 转换器并不提供输入输出信息格式化工作。
注解驱动格式化
注解驱动重要接口
spring在org.springframework.format包中提供了一个
AnnotationFormatterFactory<A extends Annotation>接口,接口方法如下:
Set<Class<?>> getFieldTypes():表示哪些类可以标注A注解
Parser<?> getParser(A annotation,Class<?>fieldType):根据注解A获取特定类型的Parser
Printer<?> getPrinter(Aannotation,Class<?> fieldType):根据注解A获取特定类型的Printer
spring提供2个内建实现类,分别支持数组及数字类型的注解驱动格式化
:支持数字类型的属性使用(@NumberFormat)
:用于日期类型(@DateTimeFormat)
启用注解驱动格式化功能
spring中定义了一个实现了ConversionSerivce实现类FormattingConversionService,该类扩展于GenericConversionService,它既有类型转换功能也有格式化功能。
FormattingConversionService也拥有一个,后者用于在spring上下文中构造一个FormattingConversionSerivce,通过这个类即可以注册自定义转换器,还可以注册自定义注解驱动逻辑。
/
会自动注册到中,因此装配该FactoryBean后,就可以在入参绑定及模型数据输出使用注解驱动格式化功能。
<bean id="conversionService"
class="org.springframework.format.support.">
<propertyname="converters">
<list><bean class="xxxx"/></list>
</property>
</bean>
用替换原来的ConversionServiceFactoryBean
<mvc:annotation-driven/>标签默认创建了
@DateTimeFormat
可以对java.util.Date,java.util.Calendar,java.lang.Long,joda时间类型属性标注
@DateTimeFormat(partten=”yyyy-MM-dd”)
@NumberFormat可对数字类型属性标注
@NumberFormat(pattern=”#,###.##”)
E. Spring源码(三) -- Xml文件转换Document
我们上一节探究了ClassPathResource通过输入Path构造出一个Resource对象,我们这一节继续学习XmlBeanFactory如何解析Resource,将Resource转化成一个Document。
,我们可以看到XmlBeanDefinitionReader上层是BeanDefinitionReader接口,EnvironmentCapable接口已经父类AbstractBeanDefinitionReader。
我们可以看到上面的步骤分为五步。1:对source进行encode封装,考虑到可能需要编码。2:通过sax读取xml文件方式构建一个inputSource。3:getValidationModeForResource获取xml的验证模式。4:加载xml文件,获得对应的Document。5:通过Document注册BeanDefinition。至此就完成了一个xml文件到document,document到BeanDefinition的转换,这里转换使用的sax解析xml文件的方法,有兴趣的可以debug进去看看。
F. spring类型转换器(一)
在spring容器初始化的时候,BeanDefinition中配置的bean的属性值一般都为String类型,如何将String类型转换为Bean中属性对应的类型呢,在这个过程中就需要用到类型转换器了。spring实例化bean过程中对属性值的转换主要是使用BeanWrapperImpl实现的。
首先来看下BeanWrapperImpl的使用
定义一个处理日期的转换器
创建一个BeanWrapperImpl用于包装目标bean(这里来模拟spring的内部实现)。然后注册Date类型的转换器,将值使用DatePropertyEditor转换为Date类型。调用setPropertyValue的时候,给testBean的birthday字段设置一个字符串类型的时间,在实际赋值的过程中会调用到类型转换器将字符串转换为日期类型。
同样可以实现一个String trim的转换器
BeanWrapper支持嵌套属性的赋值,当存在嵌套属性的时候需要设置 setAutoGrowNestedPaths=true。
接下来我们就来研究一下BeanWrapperImpl的实现过程
java.beans包中声明了一个PropertyEditor接口,用于提供对属性的访问和赋值。
PropertyEditor有个默认实现类PropertyEditorSupport,如果要自定义属性转换器,直接继承该类,然后覆写setAsText、getAsText、setValue、getValue方法。在类型转换的时候如果值是字符串会调用setAsText来赋值value,其他情况下会调用setValue来赋值value,然后再调用getValue获取改变后的value值完成类型转换。相当于将PropertyEditor当做一个加工作坊,传进去一个值,返回想要类型的值
注意 :PropertyEditor是线程不安全的,有状态的,因此每次使用时都需要创建一个,不可重用;
首先先看下BeanWrapperImpl的继承图。还有一个跟BeanWrapperImpl平级的实现类DirectFieldAccessor,用于处理getter和setter的字段访问
从图可以看出,BeanWrapperImpl实现了3个顶级接口,提供了对象属性访问、类型转换器的注册和查找、类型转换功能
从继承图可以看到PropertyEditorRegistrySupport实现了PropertyEditorRegistry接口,该类默认实现了类型转换器的注册和查找功能
在PropertyEditorRegistrySupport声明了多个存储结构用于存储不同的类型转换器来源。这里需要注意的是PropertyEditor是存在在Map中的,目标类型作为key,所以对于一个类型只能注册一个PropertyEditor,后面注册的会覆盖前面注册的
同时还看到一个conversionService变量,spring提供了另一种类型转换接口Converter,通过conversionService调用对应的Converter进行类型转换,在PropertyEditorRegistrySupport同样可以注册进来conversionService,用于使用Converter进行类型转换。conversionService的详细使用会在下篇文章中讲到。
接下来来看PropertyEditor的注册过程。
在上面提到过PropertyEditor存在在一个Map中,key是目标类型,那么这个参数propertyPath是干嘛的呢?这是为了给属性名指定专属的类型的转换器。因为一个目标类型只能有一个PropertyEditor的限制。但是有时候确实某个属性的类型转换比较特殊,这个时候就可以给这个属性名单独注册一个类型转换器,不会覆盖其他的哦。在类型转换的时候,会先根据属性名去customEditorsForPath中找可以用的PropertyEditor。
来看PropertyEditor的查找流程
首先根据属性名从customEditorsForPath查找特定的类型转换器
根据类型查找定义的类型转换器,如果没有对应的,则查找父类对应的类型转换器
PropertyEditorRegistrySupport 中默认添加了一些转换器,当调用 getDefaultEditor(requiredType)的时候会进行注册
TypeConverter接口定义了将一个值转换为目标类型的值的功能。在继承图中可以看出TypeConverterSupport对类型转换提供了默认实现。
TypeConverterSupport将类型转换功能委托给typeConverterDelegate实现
TypeConverterDelegate实现了类型转换的功能,创建TypeConverterDelegate的时候需要一个propertyEditorRegistry对象,用于查找匹配的类型转换器
首先通过propertyEditorRegistry查找自定义的类型转换器PropertyEditor和ConversionService
当该类型没有注册自定义的PropertyEditor,并且存在conversionService的时候,使用conversionService进行类型转换。如果conversionService中没有配置对应的converter,那么继续尝试使用默认注册的PropertyEditor。
如果目标类型存在自定义PropertyEditor 或者 目标类型和值类型不一样则需要进行类型转换(当没有找到ProperEditor的时候会尝试查找默认注册的PropertyEditor)
类型转换,主要分三种情况处理,值类型不是字符串,值类型是字符串数组,值类型是字符串
最后,需要对转换后的值和目标类型进行判断,是否符合要求,如果不符合继续处理。这里主要处理了集合类型还有Number类型、基本类型转String、枚举类型。
在attemptToConvertStringToEnum方法中自动根据枚举的名称转换为枚举的对象
PropertyAccessor接口定义了属性访问的功能。通过实现setPropertyValue 和 getPropertyValue方法实现对象属性的赋值和访问。
AbstractPropertyAccessor实现了PropertyAccessor接口,不过没有对setPropertyValue和getPropertyValue进行实现,而是单独提供了一个 setPropertyValues的方法, 用于批量设置属性值,同时可以通过 ignoreUnknown和ignoreInvalid参数忽略未知的属性
在类中实现了setPropertyValue和getPropertyValue功能。
由于存在嵌套属性赋值的情况,对于嵌套的处理,其实只需要对嵌套的最底层进行类型转换,上层每一层就创建默认的值然后set到再上层对象的属性中。在这里spring使用了递归解决这个问题,创建每一层属性的对象值,使用BeanWrapper包装该对象,那么又是一个BeanWrapperImpl的赋值流程。
来看这个递归的内部实现,在getNestedPropertyAccessor完成对外层属性的初始化和将该值赋值到所依赖的对象中。然后使用BeanWrapper封装属性对象,后续走属性对象的赋值流程
创建属性对象,并将该对象set到宿主对象。因为对象是指针引用的,所以在这步已经完成对宿主对象的属性赋值,接下来的流程只要对属性对象中的依赖属性进行赋值。
使用递归解决了嵌套赋值的问题,那么接下来就是针对最底层BeanWrapperImpl的属性赋值流程
在processLocalProperty方法中,首先通过子类获取属性处理器,通过PropertyHandler对属性赋值。在赋值之前再次判断属性是否已经进行了类型转换,如果没有再次调用类型转换器进行转换,如果已经完成类型转换,使用ConvertedValue
对属性的访问和设置spring进行了更小粒度的封装。提供了 PropertyHandler抽象类。为什么在这里进行抽象,看PropertyHandler的两个实现,可以看到一个是BeanPropertyHandler,一个是FieldPropertyHandler,不难想象,属性一种是由getter和setter方法进行访问,一种是没有getter和setter直接反射字段进行的。
如果要对map进行控制,我们可以再提供一个专门处理map的实现了类handler
BeanWrapperImpl提供了BeanPropertyHandler,将setter和getter传入
BeanPropertyHandler提供对setter和getter的访问
的另一个实现类DirectFieldAccessor,专门用于给字段赋值,不依赖setter和getter,那么这个是怎么实现的,看源码发现是DirectFieldAccessor中提供了一个PropertyHandler的实现类,通过Field的反射实现了setValue和getValue
G. spring jpa之实体属性类型转换器AttributeConverter,自定义Converter,通用Converter
在JPA注解中,有个@Convert注解,其中需要传入一个Class作为convert参数,该class需要实现AttributeConverter<X,Y>接口。下面来看看AttributeConverter接口的作用。
实体属性类型转换器。主要使用场景:
简单化操作,用持久化enum枚举来进行一个操作。 AttributeConverter<X,Y> 该接口中需要实现两个方法:
场景:用户登录时,记录用户的动作:登录,登出,注册,重置密码。
数据的加密和日期的转换也就类似的操作了。
但是这样的每个枚举可能都要去写这样的转换类,可能会存在重复的操作。可以尝试写个通用的枚举转换类。
H. [Spring boot源码解析] 2 启动流程分析
在了解 Spring Boot 的启动流程的时候,我们先看一下一个Spring Boot 应用是如何启动的,如下是一个简单的 SpringBoot 程序,非常的简洁,他是如何做到的呢,我们接下来就将一步步分解。
我们追踪 SpringApplication.run() 方法,其实最终它主要的逻辑是新建一个 SpringApplication ,然后调用他的 run 方法,如下:
我们先来看一下创建 SpringApplication 的方法:
在将Main class 设置 primarySources 后,调用了 WebApplicationType.deceFromClasspath() 方法,该方法是为了检查当前的应用类型,并设置给 webApplicationType 。 我们进入 deceFromClasspath 方法 :
这里主要是通过类加载器判断是否存在 REACTIVE 相关的类信息,假如有就代表是一个 REACTIVE 的应用,假如不是就检查是否存在 Servelt 和 ,假如都没有,就代表应用为非 WEB 类应用,返回 NONE ,默认返回 SERVLET 类型,我们这期以我们目前最常使用的 SERVLET 类型进行讲解,所以我们在应用中引入了 spring-boot-starter-web 作为依赖:
他会包含 Spring-mvc 的依赖,所以就包含了内嵌 tomcat 中的 Servlet 和 Spring-web 中的 ,因此返回了 SERVLET 类型。
回到刚才创建 SpringApplication 的构建方法中,我们设置完成应用类型后,就寻找所有的 Initializer 实现类,并设置到 SpringApplication 的 Initializers 中,这里先说一下 getSpringFactoriesInstances 方法,我们知道在我们使用 SpringBoot 程序中,会经常在 META-INF/spring.factories 目录下看到一些 EnableAutoConfiguration ,来出发 config 类注入到容器中,我们知道一般一个 config 类要想被 SpringBoot 扫描到需要使用 @CompnentScan 来扫描具体的路径,对于 jar 包来说这无疑是非常不方便的,所以 SpringBoot 提供了另外一种方式来实现,就是使用 spring.factories ,比如下面这个,我们从 Springboot-test 中找到的例子,这里先定义了一个ExampleAutoConfiguration,并加上了 Configuration 注解:
然后在 spring.factories 中定义如下:
那这种方式是怎么实现的你,这就要回到我们刚才的方法 getSpringFactoriesInstances :
我们先来看一下传入参数,这里需要注意的是 args,这个是初始化对应 type 的时候传入的构造参数,我们先看一下 SpringFactoriesLoader#loadFactoryNames 方法:
首先是会先检查缓存,假如缓存中存在就直接返回,假如没有就调用 classLoader#getResources 方法,传入 META-INF/spring.factories ,即获取所有 jar 包下的对应文件,并封装成 UrlResource ,然后使用 PropertiesLoaderUtils 将这些信息读取成一个对一对的 properties,我们观察一下 spring.factories 都是按 properties 格式排版的,假如有多个就用逗号隔开,所以这里还需要将逗号的多个类分隔开来,并加到 result 中,由于 result 是一个 LinkedMultiValueMap 类型,支持多个值插入,最后放回缓存中。最终完成加载 META-INF/spring.factories 中的配置,如下:
我们可以看一下我们找到的 initializer 有多少个:
在获取到所有的 Initializer 后接下来是调用 方法进行初始化。
这里的 names 就是我们上面通过类加载器加载到的类名,到这里会先通过反射生成 class 对象,然后判断该类是否继承与 ApplicationContextInitializer ,最后通过发射的方式获取这个类的构造方法,并调用该构造方法,传入已经定义好的构造参数,对于 ApplicationContextInitializer 是无参的构造方法,然后初始化实例并返回,回到原来的方法,这里会先对所有的 ApplicationContextInitializer 进行排序,调用 #sort(instances) 方法,这里就是根据 @Order 中的顺序进行排序。
接下来是设置 ApplicationListener ,我们跟进去就会发现这里和上面获取 ApplicationContextInitializer 的方法如出一辙,最终会加载到如图的 15 个 listener (这里除了 外,其他都是 SpringBoot 内部的 Listener):
在完成 SpringApplication 对象的初始化后,我们进入了他的 run 方法,这个方法几乎涵盖了 SpringBoot 生命周期的所有内容,主要分为九个步骤,每一个步骤这里都使用注解进行标识:
主要步骤如下:
第一步:获取 SpringApplicationRunListener, 然后调用他的 staring 方法启动监听器。
第二步:根据 SpringApplicationRunListeners以及参数来准备环境。
第三步:创建 Spring 容器。
第四步:Spring 容器的前置处理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的后置处理器。
第七步:通知所有 listener 结束启动。
第八步:调用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我们接下来一一讲解这些内容。
我们首先看一下第一步,获取 SpringApplicationRunListener :
这里和上面获取 initializer 和 listener 的方式基本一致,都是通过 getSpringFactoriesInstances , 最终只找到一个类就是: org.springframework.boot.context.event.EventPublishingRunListener ,然后调用其构造方法并传入产生 args , 和 SpringApplication 本身:
我们先看一下构造函数,首先将我们获取到的 ApplicationListener 集合添加到initialMulticaster 中, 最后都是通过操作 来进行广播,我,他继承于 ,我们先看一下他的 addApplicationListener 方法:
我们可以看出,最后是放到了 applicationListenters 这个容器中。他是 defaultRetriever 的成员属性, defaultRetriever 则是 的私有类,我们简单看一下这个类:
我们只需要看一下这里的 getApplicationListeners 方法,它主要是到 beanFactory 中检查是否存在多的 ApplicationListener 和旧的 applicationListeners 组合并返回,接着执行 listener 的 start 方法,最后也是调用了 的 multicastEvent 查找支持对应的 ApplicationEvent 类型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,这里除了会:
筛选的方法如下,都是调用了对应类型的 supportsEventType 方法 :
如图,我们可以看到对 org.springframework.boot.context.event.ApplicationStartingEvent 感兴趣的有5个 Listener
环境准备的具体方法如下:
首先是调用 getOrCreateEnvironment 方法来创建 environment ,我们跟进去可以发现这里是根据我们上面设置的环境的类型来进行选择的,当前环境会创建 StandardServletEnvironment
我们先来看一下 StandardServletEnvironment 的类继承关系图,我们可以看出他是继承了 AbstractEnvironment :
他会调用子类的 customizePropertySources 方法实现,首先是 StandardServletEnvironment 的实现如下,他会添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三种 properties,当前调试环境没有配置 jndi properties,所以这里不会添加。接着调用父类的 customizePropertySources 方法,即调用到了 StandardEnvironment 。
我们看一下 StandardEnvironment#customizePropertySources 方法,与上面的三个 properties 创建不同,这两个是会进行赋值的,包括系统环境变量放入 systemEnvironment 中,jvm 先关参数放到 systemProperties 中:
这里会添加 systemEnvironment 和 systemProperties 这两个 properties,最终拿到的 properties 数量如下 4个:
在创建完成 Environment 后,接下来就到了调用 configureEnvironment 方法:
我们先看一下 configurePropertySources 方法,这里主要分两部分,首先是查询当前是否存在 defaultProperties ,假如不为空就会添加到 environment 的 propertySources 中,接着是处理命令行参数,将命令行参数作为一个 CompositePropertySource 或则 添加到 environment 的 propertySources 里面,
接着调用 ConfigurationPropertySources#attach 方法,他会先去 environment 中查找 configurationProperties , 假如寻找到了,先检查 configurationProperties 和当前 environment 是否匹配,假如不相等,就先去除,最后添加 configurationProperties 并将其 sources 属性设置进去。
回到我们的 prepareEnvironment 逻辑,下一步是通知观察者,发送 事件,调用的是 SpringApplicationRunListeners#environmentPrepared 方法,最终回到了 #multicastEvent 方法,我们通过 debug 找到最后对这个时间感兴趣的 Listener 如下:
其主要逻辑如下:
这个方法最后加载了 PropertySourceLoader , 这里主要是两种,一个是用于 Properties 的,一个是用于 YAML 的如下:
其中 apply 方法主要是加载 defaultProperties ,假如已经存在,就进行替换,而替换的目标 PropertySource 就是 load 这里最后的一个 consumer 函数加载出来的,这里列一下主要做的事情:
1、加载系统中设置的所有的 Profile 。
2、遍历所有的 Profile ,假如是默认的 Profile , 就将这个 Profile 加到 environment 中。
3、调用load 方法,加载配置,我们深入看一下这个方法:
他会先调用 getSearchLocations 方法,加载所有的需要加载的路径,最终有如下路径:
其核心方法是遍历所有的 propertySourceLoader ,也就是上面加载到两种 propertySourceLoader ,最红 loadForFileExtension 方法,加载配置文件,这里就不展开分析了,说一下主要的作用,因为每个 propertySourceLoader 都有自己可以加载的扩展名,默认扩展名有如下四个 properties, xml, yml, yaml,所以最终拿到文件名字,然后通过 - 拼接所有的真实的名字,然后加上路径一起加载。
接下来,我们分析 BackgroundPreinitializer ,这个方法在接收 ApplicationPrepareEnvironment 事件的时候真正调用了这份方法:
1、 ConversionServiceInitializer 主要负责将包括 日期,货币等一些默认的转换器注册到 formatterRegistry 中。
2、 ValidationInitializer 创建 validation 的匹配器。
3、 MessageConverterInitializer 主要是添加了一些 http 的 Message Converter。
4、 JacksonInitializer 主要用于生成 xml 转换器的。
接着回到我们将的主体方法, prepareEnvironment 在调用完成 listeners.environmentPrepared(environment) 方法后,调用 bindToSpringApplication(environment) 方法,将 environment 绑定到 SpirngApplication 中。
接着将 enviroment 转化为 StandardEnvironment 对象。
最后将 configurationProperties 加入到 enviroment 中, configurationProperties 其实是将 environment 中其他的 PropertySource 重新包装了一遍,并放到 environment 中,这里主要的作用是方便 进行解析。
它主要是检查是否存在 spring.beaninfo.ignore 配置,这个配置的主要作用是设置 javaBean 的内省模式,所谓内省就是应用程序在 Runtime 的时候能检查对象类型的能力,通常也可以称作运行时类型检查,区别于反射主要用于修改类属性,内省主要用户获取类属性。那么我们什么时候会使用到内省呢,java主要是通过内省工具 Introspector 来完成内省的工作,内省的结果通过一个 Beaninfo 对象返回,主要包括类的一些相关信息,而在 Spring中,主要是 BeanUtils#Properties 会使用到,Spring 对内省机制还进行了改进,有三种内省模式,如下图中红色框框的内容,默认情况下是使用 USE_ALL_BEANINFO。假如设置为true,就是改成第三中 IGNORE_ALL_BEANINFO
首先是检查 Application的类型,然后获取对应的 ApplicationContext 类,我们这里是获取到了 org.springframework.boot.web.servlet.context. 接着调用 BeanUtils.instantiateClass(contextClass); 方法进行对象的初始化。
最终其实是调用了 的默认构造方法。我们看一下这个方法做了什么事情。这里只是简单的设置了一个 reader 和一个 scanner,作用于 bean 的扫描工作。
我们再来看一下这个类的继承关系
这里获取 ExceptionReporter 的方式主要还是和之前 Listener 的方式一致,通过 getSpringFactoriesInstances 来获取所有的 SpringBootExceptionReporter 。
其主要方法执行如下: