Dunwu Blog

大道至简,知易行难

Spring Environment 抽象

理解 Spring Environment 抽象

统一的 Spring 配置属性管理

Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源(PropertySource)

条件化 Spring Bean 装配管理

通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean

Spring Environment 接口使用场景

  • ⽤于属性占位符处理
  • 用于转换 Spring 配置属性类型
  • 用于存储 Spring 配置属性源(PropertySource)
  • 用于 Profiles 状态的维护

Environment 占位符处理

Spring 3.1 前占位符处理

  • 组件:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
  • 接口:org.springframework.util.StringValueResolver

Spring 3.1 + 占位符处理

  • 组件:org.springframework.context.support.PropertySourcesPlaceholderConfigurer
  • 接口:org.springframework.beans.factory.config.EmbeddedValueResolver

理解条件配置 Spring Profiles

Spring 3.1 条件配置

  • API:org.springframework.core.env.ConfigurableEnvironment
  • 修改:addActiveProfile(String)、setActiveProfiles(String…) 和 setDefaultProfiles(String…)
  • 获取:getActiveProfiles() 和 getDefaultProfiles()
  • 匹配:#acceptsProfiles(String…) 和 acceptsProfiles(Profiles)
  • 注解:@org.springframework.context.annotation.Profile

Spring 4 重构 @Profile

基于 Spring 4 org.springframework.context.annotation.Condition 接口实现

org.springframework.context.annotation.ProfileCondition

依赖注入 Environment

直接依赖注入

  • 通过 EnvironmentAware 接口回调
  • 通过 @Autowired 注入 Environment

间接依赖注入

  • 通过 ApplicationContextAware 接口回调
  • 通过 @Autowired 注入 ApplicationContext

依赖查找 Environment

直接依赖查找

  • 通过 org.springframework.context.ConfigurableApplicationContext#ENVIRONMENT_BEAN_NAME

间接依赖查找

  • 通过 org.springframework.context.ConfigurableApplicationContext#getEnvironment

依赖注入 @Value

通过注入 @Value

实现 - org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

Spring 类型转换在 Environment 中的运用

Environment 底层实现

  • 底层实现 - org.springframework.core.env.PropertySourcesPropertyResolver
  • 核心方法 - convertValueIfNecessary(Object,Class)
  • 底层服务 - org.springframework.core.convert.ConversionService
  • 默认实现 - org.springframework.core.convert.support.DefaultConversionService

Spring 类型转换在 @Value 中的运用

@Value 底层实现

  • 底层实现 - org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
    • org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
  • 底层服务 - org.springframework.beans.TypeConverter
    • 默认实现 - org.springframework.beans.TypeConverterDelegate
      • java.beans.PropertyEditor
      • org.springframework.core.convert.ConversionService

Spring 配置属性源 PropertySource

  • API
    • 单配置属性源 - org.springframework.core.env.PropertySource
    • 多配置属性源 - org.springframework.core.env.PropertySources
  • 注解
    • 单配置属性源 - @org.springframework.context.annotation.PropertySource
    • 多配置属性源 - @org.springframework.context.annotation.PropertySources
  • 关联
    • 存储对象 - org.springframework.core.env.MutablePropertySources
    • 关联方法 - org.springframework.core.env.ConfigurableEnvironment#getPropertySources()

Spring 內建的配置属性源

內建 PropertySource

PropertySource 类型 说明
org.springframework.core.env.CommandLinePropertySource 命令行配置属性源
org.springframework.jndi.JndiPropertySource JDNI 配置属性源
org.springframework.core.env.PropertiesPropertySource Properties 配置属性源
org.springframework.web.context.support.ServletConfigPropertySource Servlet 配置属性源
org.springframework.web.context.support.ServletContextPropertySource ServletContext 配置属性源
org.springframework.core.env.SystemEnvironmentPropertySource 环境变量配置属性源

基于注解扩展 Spring 配置属性源

@org.springframework.context.annotation.PropertySource 实现原理

  • 入口 - org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
    • org.springframework.context.annotation.ConfigurationClassParser#processPropertySource
  • 4.3 新增语义
    • 配置属性字符编码 - encoding
    • org.springframework.core.io.support.PropertySourceFactory
  • 适配对象 - org.springframework.core.env.CompositePropertySource

基于 API 扩展 Spring 配置属性源

  • Spring 应用上下文启动前装配 PropertySource
  • Spring 应用上下文启动后装配 PropertySource

问题

简单介绍 Spring Environment 接口?

  • 核心接口 - org.springframework.core.env.Environment
  • 父接口 - org.springframework.core.env.PropertyResolver
  • 可配置接口 - org.springframework.core.env.ConfigurableEnvironment
  • 职责:
    • 管理 Spring 配置属性源
    • 管理 Profiles

参考资料

Spring 注解

Spring 注解驱动编程发展历程

  • 注解驱动启蒙时代:Spring Framework 1.x
  • 注解驱动过渡时代:Spring Framework 2.x
  • 注解驱动黄金时代:Spring Framework 3.x
  • 注解驱动完善时代:Spring Framework 4.x
  • 注解驱动当下时代:Spring Framework 5.x

Spring 核心注解场景分类

Spring 模式注解

Spring 注解 场景说明 起始版本
@Repository 数据仓储模式注解 2.0
@Component 通用组件模式注解 2.5
@Service 服务模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置类模式注解 3.0

装配注解

Spring 注解 场景说明 起始版本
@ImportResource 替换 XML 元素 <import> 2.5
@Import 导入 Configuration 类 2.5
@ComponentScan 扫描指定 package 下标注 Spring 模式注解的类 3.1

依赖注入注解

Spring 注解 场景说明 起始版本
@Autowired Bean 依赖注入,支持多种依赖查找方式 2.5
@Qualifier 细粒度的 @Autowired 依赖查找 2.5

Spring 注解编程模型

  • 元注解(Meta-Annotations)
  • Spring 模式注解(Stereotype Annotations)
  • Spring 组合注解(Composed Annotations)
  • Spring 注解属性别名和覆盖(Attribute Aliases and Overrides)

Spring 元注解(Meta-Annotations)

  • java.lang.annotation.Documented
  • java.lang.annotation.Inherited
  • java.lang.annotation.Repeatable

Spring 模式注解(Stereotype Annotations)

理解 @Component “派⽣性”:元标注 @Component 的注解在 XML 元素 context:component-scan 或注解 @ComponentScan 扫描中“派生”了 @Component 的特性,并且从 Spring Framework 4.0 开始支持多层次“派⽣性”。

举例说明:

  • @Repository
  • @Service
  • @Controller
  • @Configuration
  • @SpringBootConfiguration(Spring Boot)

@Component “派⽣性”原理

  • 核心组件 - org.springframework.context.annotation.ClassPathBeanDefinitionScanner
  • org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
  • 资源处理 - org.springframework.core.io.support.ResourcePatternResolver
  • 资源-类元信息
  • org.springframework.core.type.classreading.MetadataReaderFactory
  • 类元信息 - org.springframework.core.type.ClassMetadata
  • ASM 实现 - org.springframework.core.type.classreading.ClassMetadataReadingVisitor
  • 反射实现 - org.springframework.core.type.StandardAnnotationMetadata
  • 注解元信息 - org.springframework.core.type.AnnotationMetadata
  • ASM 实现 - org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor
  • 反射实现 - org.springframework.core.type.StandardAnnotationMetadata

Spring 组合注解(Composed Annotations)

Spring 组合注解(Composed Annotations)中的元注允许是 Spring 模式注解(Stereotype Annotation)与其他 Spring 功能性注解的任意组合。

Spring 注解属性别名(Attribute Aliases)

Spring 注解属性覆盖(Attribute Overrides)

Spring @Enable 模块驱动

@Enable 模块驱动

@Enable 模块驱动是以 @Enable 为前缀的注解驱动编程模型。所谓“模块”是指具备相同领域的功能组件集合,组合所形成⼀个独⽴的单元。⽐如 Web MVC 模块、AspectJ 代理模块、Caching(缓存)模块、JMX(Java 管理扩展)模块、Async(异步处理)模块等。

举例说明

  • @EnableWebMvc
  • @EnableTransactionManagement
  • @EnableCaching
  • @EnableMBeanExport
  • @EnableAsync

@Enable 模块驱动编程模式

  • 驱动注解:@EnableXXX
  • 导入注解:@Import 具体实现
  • 具体实现
  • 基于 Configuration Class
  • 基于 ImportSelector 接口实现
  • 基于 ImportBeanDefinitionRegistrar 接口实现

Spring 条件注解

基于配置条件注解 - @org.springframework.context.annotation.Profile

  • 关联对象 - org.springframework.core.env.Environment 中的 Profiles
  • 实现变化:从 Spring 4.0 开始,@Profile 基于 @Conditional 实现

基于编程条件注解 - @org.springframework.context.annotation.Conditional

  • 关联对象 - org.springframework.context.annotation.Condition 具体实现

@Conditional 实现原理

  • 上下文对象 - org.springframework.context.annotation.ConditionContext
  • 条件判断 - org.springframework.context.annotation.ConditionEvaluator
  • 配置阶段 - org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase
  • 判断入口
    • org.springframework.context.annotation.ConfigurationClassPostProcessor
    • org.springframework.context.annotation.ConfigurationClassParser

参考资料

Spring 事件

Java 事件/监听器编程模型

设计模式 - 观察者模式扩展

  • 可观者对象(消息发送者) - java.util.Observable
  • 观察者 - java.util.Observer

标准化接口

  • 事件对象 - java.util.EventObject
  • 事件监听器 - java.util.EventListener

面向接口的事件/监听器设计模式

事件/监听器场景举例

Java 技术规范 事件接口 监听器接口
JavaBeans java.beans.PropertyChangeEvent java.beans.PropertyChangeListener
Java AWT java.awt.event.MouseEvent java.awt.event.MouseListener
Java Swing javax.swing.event.MenuEvent javax.swing.event.MenuListener
Java Preference java.util.prefs.PreferenceChangeEvent java.util.prefs.PreferenceChangeListener

面向注解的事件/监听器设计模式

事件/监听器注解场景举例

Java 技术规范 事件注解 监听器注解
Servlet 3.0+ @javax.servlet.annotation.WebListener
JPA 1.0+ @javax.persistence.PostPersist
Java Common @PostConstruct
EJB 3.0+ @javax.ejb.PrePassivate
JSF 2.0+ @javax.faces.event.ListenerFor

Spring 标准事件 - ApplicationEvent

Java 标准事件 java.util.EventObject 扩展

  • 扩展特性:事件发生事件戳
  • Spring 应用上下文 ApplicationEvent 扩展 - ApplicationContextEvent
  • Spring 应用上下文(ApplicationContext)作为事件源

具体实现:

  • org.springframework.context.event.ContextClosedEvent
  • org.springframework.context.event.ContextRefreshedEvent
  • org.springframework.context.event.ContextStartedEvent
  • org.springframework.context.event.ContextStoppedEvent

基于接口的 Spring 事件监听器

Java 标准事件监听器 java.util.EventListener 扩展

  • 扩展接口 - org.springframework.context.ApplicationListener
  • 设计特点:单一类型事件处理
  • 处理方法:onApplicationEvent(ApplicationEvent)
  • 事件类型:org.springframework.context.ApplicationEvent

基于注解的 Spring 事件监听器

Spring 注解 - @org.springframework.context.event.EventListener

特性 说明
设计特点 支持多 ApplicationEvent 类型,无需接口约束
注解目标 方法
是否支持异步执行 支持
是否支持泛型类型事件 支持
是指支持顺序控制 支持,配合 @Order 注解控制

注册 Spring ApplicationListener

  • 方法一:ApplicationListener 作为 Spring Bean 注册
  • 方法二:通过 ConfigurableApplicationContext API 注册

Spring 事件发布器

  • 方法一:通过 ApplicationEventPublisher 发布 Spring 事件
    • 获取 ApplicationEventPublisher
      • 依赖注入
  • 方法二:通过 ApplicationEventMulticaster 发布 Spring 事件
    • 获取 ApplicationEventMulticaster
      • 依赖注入
      • 依赖查找

Spring 层次性上下文事件传播

  • 发生说明
  • 当 Spring 应用出现多层次 Spring 应用上下文(ApplicationContext)时,如 Spring WebMVC、Spring Boot 或 Spring Cloud 场景下,由子 ApplicationContext 发起 Spring 事件可能会传递到其 Parent ApplicationContext(直到 Root)的过程
  • 如何避免
  • 定位 Spring 事件源(ApplicationContext)进行过滤处理

Spring 内建事件

ApplicationContextEvent 派生事件

  • ContextRefreshedEvent :Spring 应用上下文就绪事件
  • ContextStartedEvent :Spring 应用上下文启动事件
  • ContextStoppedEvent :Spring 应用上下文停止事件
  • ContextClosedEvent :Spring 应用上下文关闭事件

Spring 4.2 Payload 事件

Spring Payload 事件 - org.springframework.context.PayloadApplicationEvent

  • 使用场景:简化 Spring 事件发送,关注事件源主体
  • 发送方法:ApplicationEventPublisher#publishEvent(java.lang.Object)

自定义 Spring 事件

  • 扩展 org.springframework.context.ApplicationEvent
  • 实现 org.springframework.context.ApplicationListener
  • 注册 org.springframework.context.ApplicationListener

依赖注入 ApplicationEventPublisher

  • 通过 ApplicationEventPublisherAware 回调接口
  • 通过 @Autowired ApplicationEventPublisher

依赖查找 ApplicationEventMulticaster

查找条件

  • Bean 名称:”applicationEventMulticaster”
  • Bean 类型:org.springframework.context.event.ApplicationEventMulticaster

ApplicationEventPublisher 底层实现

  • 接口:org.springframework.context.event.ApplicationEventMulticaster
  • 抽象类:org.springframework.context.event.AbstractApplicationEventMulticaster
  • 实现类:org.springframework.context.event.SimpleApplicationEventMulticaster

同步和异步 Spring 事件广播

基于实现类 - org.springframework.context.event.SimpleApplicationEventMulticaster

  • 模式切换:setTaskExecutor(java.util.concurrent.Executor) 方法
    • 默认模式:同步
    • 异步模式:如 java.util.concurrent.ThreadPoolExecutor
  • 设计缺陷:非基于接口契约编程

基于注解 - @org.springframework.context.event.EventListener

  • 模式切换
    • 默认模式:同步
    • 异步模式:标注 @org.springframework.scheduling.annotation.Async
  • 实现限制:无法直接实现同步/异步动态切换

Spring 4.1 事件异常处理

Spring 3.0 错误处理接口 - org.springframework.util.ErrorHandler

使用场景

  • Spring 事件(Events)
    • SimpleApplicationEventMulticaster Spring 4.1 开始支持
  • Spring 本地调度(Scheduling)
    • org.springframework.scheduling.concurrent.ConcurrentTaskScheduler
    • org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler

Spring 事件/监听器实现原理

核心类 - org.springframework.context.event.SimpleApplicationEventMulticaster

  • 设计模式:观察者模式扩展
    • 被观察者 - org.springframework.context.ApplicationListener
      • API 添加
      • 依赖查找
    • 通知对象 - org.springframework.context.ApplicationEvent
  • 执行模式:同步/异步
  • 异常处理:org.springframework.util.ErrorHandler
  • 泛型处理:org.springframework.core.ResolvableType

问题

Spring Boot 事件

事件类型 发生时机
ApplicationStartingEvent 当 Spring Boot 应用已启动时
ApplicationStartedEvent 当 Spring Boot 应用已启动时
ApplicationEnvironmentPreparedEvent 当 Spring Boot Environment 实例已准备时
ApplicationPreparedEvent 当 Spring Boot 应用预备时
ApplicationReadyEvent 当 Spring Boot 应用完全可用时
ApplicationFailedEvent 当 Spring Boot 应用启动失败时

Spring Cloud 事件

事件类型 发生时机
EnvironmentChangeEvent 当 Environment 示例配置属性发生变化时
HeartbeatEvent 当 DiscoveryClient 客户端发送心跳时
InstancePreRegisteredEvent 当服务实例注册前
InstanceRegisteredEvent 当服务实例注册后
RefreshEvent 当 RefreshEndpoint 被调用时
RefreshScopeRefreshedEvent 当 Refresh Scope Bean 刷新后

Spring 事件核心接口/组件

  • Spring 事件 - org.springframework.context.ApplicationEvent
  • Spring 事件监听器 - org.springframework.context.ApplicationListener
  • Spring 事件发布器 - org.springframework.context.ApplicationEventPublisher
  • Spring 事件广播器 - org.springframework.context.event.ApplicationEventMulticaster

Spring 同步和异步事件处理的使用场景

  • Spring 同步事件 - 绝大多数 Spring 使用场景,如 ContextRefreshedEvent
  • Spring 异步事件 - 主要 @EventListener 与 @Async 配合,实现异步处理,不阻塞主线程,比如长时间的数据计算任务等。不要轻易调整 SimpleApplicationEventMulticaster 中关联的 taskExecutor 对象,除非使用者非常了解 Spring 事件机制,否则容易出现异常行为。

参考资料

Spring 泛型处理

Java 泛型基础

泛型类型

  • 泛型类型是在类型上参数化的泛型类或接口

泛型使用场景

  • 编译时强类型检查
  • 避免类型强转
  • 实现通用算法

泛型类型擦写

  • 泛型被引入到 Java 语言中,以便在编译时提供更严格的类型检查并支持泛型编程。类型擦除确保不会
    为参数化类型创建新类;因此,泛型不会产生运行时开销。为了实现泛型,编译器将类型擦除应用于:
    • 将泛型类型中的所有类型参数替换为其边界,如果类型参数是无边界的,则将其替换为
      “Object”。因此,生成的字节码只包含普通类、接口和方法
    • 必要时插入类型转换以保持类型安全
    • 生成桥方法以保留扩展泛型类型中的多态性

Java 5 类型接口

Java 5 类型接口 - java.lang.reflect.Type

派生类或接口 说明
java.lang.Class Java 类 API,如 java.lang.String
java.lang.reflect.GenericArrayType 泛型数组类型
java.lang.reflect.ParameterizedType 泛型参数类型
java.lang.reflect.TypeVariable 泛型类型变量,如 Collection<E> 中的 E
java.lang.reflect.WildcardType 泛型通配类型

Java 泛型反射 API

类型 API
泛型信息(Generics Info) java.lang.Class#getGenericInfo()
泛型参数(Parameters) java.lang.reflect.ParameterizedType
泛型父类(Super Classes) java.lang.Class#getGenericSuperclass()
泛型接口(Interfaces) java.lang.Class#getGenericInterfaces()
泛型声明(Generics Declaration) java.lang.reflect.GenericDeclaration

Spring 泛型类型辅助类

核心 API - org.springframework.core.GenericTypeResolver

  • 版本支持:[2.5.2 , )
  • 处理类型相关(Type)相关方法
    • resolveReturnType
    • resolveType
  • 处理泛型参数类型(ParameterizedType)相关方法
    • resolveReturnTypeArgument
    • resolveTypeArgument
    • resolveTypeArguments
  • 处理泛型类型变量(TypeVariable)相关方法
    • getTypeVariableMap

Spring 泛型集合类型辅助类

核心 API - org.springframework.core.GenericCollectionTypeResolver

  • 版本支持:[2.0 , 4.3]
  • 替换实现:org.springframework.core.ResolvableType
  • 处理 Collection 相关
    • getCollection*Type
  • 处理 Map 相关
    • getMapKey*Type
    • getMapValue*Type

Spring 方法参数封装 - MethodParameter

核心 API - org.springframework.core.MethodParameter

  • 起始版本:[2.0 , )
  • 元信息
    • 关联的方法 - Method
    • 关联的构造器 - Constructor
    • 构造器或方法参数索引 - parameterIndex
    • 构造器或方法参数类型 - parameterType
    • 构造器或方法参数泛型类型 - genericParameterType
    • 构造器或方法参数参数名称 - parameterName
    • 所在的类 - containingClass

Spring 4.0 泛型优化实现 - ResolvableType

核心 API - org.springframework.core.ResolvableType

  • 起始版本:[4.0 , )
  • 扮演角色:GenericTypeResolverGenericCollectionTypeResolver 替代者
  • 工厂方法:for* 方法
  • 转换方法:as* 方法
  • 处理方法:resolve* 方法

ResolvableType 的局限性

  • 局限一:ResolvableType 无法处理泛型擦写
  • 局限二:ResolvableType 无法处理非具体化的 ParameterizedType

问题

Java 泛型擦写发生在编译时还是运行时

运行时

请介绍 Java 5 Type 类型的派生类或接口

  • java.lang.Class
  • java.lang.reflect.GenericArrayType
  • java.lang.reflect.ParameterizedType
  • java.lang.reflect.TypeVariable
  • java.lang.reflect.WildcardType

请说明 ResolvableType 的设计优势

  • 简化 Java 5 Type API 开发,屏蔽复杂 API 的运用,如 ParameterizedType
  • 不变性设计(Immutability)
  • Fluent API 设计(Builder 模式),链式(流式)编程

参考资料

Spring 类型转换

Spring 类型转换的实现

  • 基于 JavaBeans 接口的类型转换实现
    • 基于 java.beans.PropertyEditor 接口扩展
  • Spring 3.0+ 通用类型转换实现

使用场景

场景 基于 JavaBeans 接口的类型转换实现 Spring 3.0+ 通用类型转换实现
数据绑定 YES YES
BeanWrapper YES YES
Bean 属性类型转换 YES YES
外部化属性类型转换 NO YES

基于 JavaBeans 接口的类型转换

核心职责

  • 将 String 类型的内容转化为目标类型的对象

扩展原理

  • Spring 框架将文本内容传递到 PropertyEditor 实现的 setAsText(String) 方法
  • PropertyEditor#setAsText(String) 方法实现将 String 类型转化为目标类型的对象
  • 将目标类型的对象传入 PropertyEditor#setValue(Object) 方法
  • PropertyEditor#setValue(Object) 方法实现需要临时存储传入对象
  • Spring 框架将通过 PropertyEditor#getValue() 获取类型转换后的对象

Spring 內建 PropertyEditor 扩展

內建扩展(org.springframework.beans.propertyeditors 包下)

转换场景 实现类
String -> Byte 数组 org.springframework.beans.propertyeditors.ByteArrayPropertyEditor
String -> Char org.springframework.beans.propertyeditors.CharacterEditor
String -> Char 数组 org.springframework.beans.propertyeditors.CharArrayPropertyEditor
String -> Charset org.springframework.beans.propertyeditors.CharsetEditor
String -> Class org.springframework.beans.propertyeditors.ClassEditor
String -> Currency org.springframework.beans.propertyeditors.CurrencyEditor

自定义 PropertyEditor 扩展

扩展模式

  • 扩展 java.beans.PropertyEditorSupport

实现 org.springframework.beans.PropertyEditorRegistrar

  • 实现 registerCustomEditors(org.springframework.beans.PropertyEditorRegistry) 方法
  • PropertyEditorRegistrar 实现注册为 Spring Bean

org.springframework.beans.PropertyEditorRegistry 注册自定义 PropertyEditor 实现

  • 通用类型实现 registerCustomEditor(Class<?>, PropertyEditor)
  • Java Bean 属性类型实现:registerCustomEditor(Class<?>, String, PropertyEditor)

Spring PropertyEditor 的设计缺陷

违反职责单一原则

  • java.beans.PropertyEditor 接口职责太多,除了类型转换,还包括 Java Beans 事件和 Java GUI 交

java.beans.PropertyEditor 实现类型局限

  • 来源类型只能为 java.lang.String 类型

java.beans.PropertyEditor 实现缺少类型安全

  • 除了实现类命名可以表达语义,实现类无法感知目标转换类型

Spring 3 通用类型转换接口

类型转换接口 - org.springframework.core.convert.converter.Converter<S,T>

  • 泛型参数 S:来源类型,参数 T:目标类型
  • 核心方法:T convert(S)

通用类型转换接口 - org.springframework.core.convert.converter.GenericConverter

  • 核心方法:convert(Object,TypeDescriptor,TypeDescriptor)
  • 配对类型:org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
  • 类型描述:org.springframework.core.convert.TypeDescriptor

Spring 內建类型转换器

內建扩展

转换场景 实现类所在包名(package)
日期/时间相关 org.springframework.format.datetime
Java 8 日期/时间相关 org.springframework.format.datetime.standard
通用实现 org.springframework.core.convert.support

Converter 接口的局限性

局限一:缺少 Source Type 和 Target Type 前置判断

  • 应对:增加 org.springframework.core.convert.converter.ConditionalConverter 实现

局限二:仅能转换单一的 Source Type 和 Target Type

  • 应对:使用 org.springframework.core.convert.converter.GenericConverter 代替

GenericConverter 接口

org.springframework.core.convert.converter.GenericConverter

核心要素 说明
使用场景 用于“复合”类型转换场景,比如 Collection、Map、数组等
转换范围 Set<ConvertiblePair> getConvertibleTypes()
配对类型 org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
转换方法 convert(Object,TypeDescriptor,TypeDescriptor)
类型描述 org.springframework.core.convert.TypeDescriptor

优化 GenericConverter 接口

GenericConverter 局限性

  • 缺少 Source Type 和 Target Type 前置判断
  • 单一类型转换实现复杂

GenericConverter 优化接口 - ConditionalGenericConverter

  • 复合类型转换:org.springframework.core.convert.converter.GenericConverter
  • 类型条件判断:org.springframework.core.convert.converter.ConditionalConverter

扩展 Spring 类型转换器

实现转换器接口

  • org.springframework.core.convert.converter.Converter
  • org.springframework.core.convert.converter.ConverterFactory
  • org.springframework.core.convert.converter.GenericConverter

注册转换器实现

  • 通过 ConversionServiceFactoryBean Spring Bean
  • 通过 org.springframework.core.convert.ConversionService API

统一类型转换服务

org.springframework.core.convert.ConversionService

实现类型 说明
GenericConversionService 通用 ConversionService 模板实现,不内置转化器实现
DefaultConversionService 基础 ConversionService 实现,内置常用转化器实现
FormattingConversionService 通用 Formatter + GenericConversionService 实现,不内置转化器和 Formatter 实现
DefaultFormattingConversionService DefaultConversionService + 格式化 实现(如:JSR-354 Money & Currency, JSR-310 Date-Time)

ConversionService 作为依赖

类型转换器底层接口 - org.springframework.beans.TypeConverter

  • 起始版本:Spring 2.0
  • 核心方法 - convertIfNecessary 重载方法
  • 抽象实现 - org.springframework.beans.TypeConverterSupport
  • 简单实现 - org.springframework.beans.SimpleTypeConverter

类型转换器底层抽象实现 - org.springframework.beans.TypeConverterSupport

  • 实现接口 - org.springframework.beans.TypeConverter
  • 扩展实现 - org.springframework.beans.PropertyEditorRegistrySupport
  • 委派实现 - org.springframework.beans.TypeConverterDelegate

类型转换器底层委派实现 - org.springframework.beans.TypeConverterDelegate

  • 构造来源 - org.springframework.beans.AbstractNestablePropertyAccessor 实现
    • org.springframework.beans.BeanWrapperImpl
  • 依赖 - java.beans.PropertyEditor 实现
    • 默认內建实现 - PropertyEditorRegistrySupport#registerDefaultEditors
  • 可选依赖 - org.springframework.core.convert.ConversionService 实现

问题

Spring 类型转换实现有哪些

  • 基于 JavaBeans PropertyEditor 接口实现
  • Spring 3.0+ 通用类型转换实现

Spring 类型转换器接口有哪些

  • 类型转换接口 - org.springframework.core.convert.converter.Converter
  • 通用类型转换接口 - org.springframework.core.convert.converter.GenericConverter
  • 类型条件接口 - org.springframework.core.convert.converter.ConditionalConverter
  • 综合类型转换接口 - org.springframework.core.convert.converter.ConditionalGenericConverter

参考资料

Spring 数据绑定

Spring 数据绑定(Data Binding)的作用是将用户的输入动态绑定到 JavaBean。换句话说,Spring 数据绑定机制是将属性值设置到目标对象中。

在 Spring 中,数据绑定功能主要由 DataBinder 类实现。此外,BeanWrapper 也具有类似的功能,但 DataBinder 额外支持字段验证、字段格式化和绑定结果分析。

快速入门

定义一个用于测试的 JavaBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestBean {

private int num;

public int getNum() {
return num;
}

public void setNum(int num) {
this.num = num;
}

@Override
public String toString() {
return "TestBean{" + "num=" + num + '}';
}

}

数据绑定示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DataBindingDemo {

public static void main(String[] args) {

MutablePropertyValues mpv = new MutablePropertyValues();
mpv.add("num", "10");

TestBean testBean = new TestBean();
DataBinder db = new DataBinder(testBean);

db.bind(mpv);
System.out.println(testBean);
}

}

Spring 数据绑定使用场景

  • Spring BeanDefinition 到 Bean 实例创建
  • Spring 数据绑定(DataBinder
  • Spring Web 参数绑定(WebDataBinder

DataBinder

在 Spring 中,DataBinder 类是数据绑定功能的基类。WebDataBinderDataBinder 的子类,主要用于 Spring Web 数据绑定,此外,还有一些 WebDataBinder 的扩展子类,其类族如下图所示:

DataBinder 核心属性:

属性 说明
target 关联目标 Bean
objectName 目标 Bean 名称
bindingResult 属性绑定结果
typeConverter 类型转换器
conversionService 类型转换服务
messageCodesResolver 校验错误文案 Code 处理器
validators 关联的 Bean Validator 实例集合

DataBinder 类的核心方法是 bind(PropertyValues):将 PropertyValues Key-Value 内容映射到关联 Bean(target)中的属性上

  • 假设 PropertyValues 中包含 name=dunwu 的键值对时, 同时 Bean 对象 User 中存在 name 属性, 当 bind 方法执行时, User 对象中的 name 属性值将被绑定为 dunwu

Spring 数据绑定元数据

DataBinder 元数据 - PropertyValues

特征 说明
数据来源 BeanDefinition,主要来源 XML 资源配置 BeanDefinition
数据结构 由一个或多个 PropertyValue 组成
成员结构 PropertyValue 包含属性名称,以及属性值(包括原始值、类型转换后的值)
常见实现 MutablePropertyValues
Web 扩展实现 ServletConfigPropertyValues、ServletRequestParameterPropertyValues
相关生命周期 InstantiationAwareBeanPostProcessor#postProcessProperties

Spring 数据绑定控制参数

DataBinder 绑定特殊场景分析

  • 当 PropertyValues 中包含名称 x 的 PropertyValue,目标对象 B 不存在 x 属性,当 bind 方法执
    行时,会发生什么?
  • 当 PropertyValues 中包含名称 x 的 PropertyValue,目标对象 B 中存在 x 属性,当 bind 方法执
    行时,如何避免 B 属性 x 不被绑定?
  • 当 PropertyValues 中包含名称 x.y 的 PropertyValue,目标对象 B 中存在 x 属性(嵌套 y 属性)
    ,当 bind 方法执行时,会发生什么?

DataBinder 绑定控制参数

参数名称 说明
ignoreUnknownFields 是否忽略未知字段,默认值:true
ignoreInvalidFields 是否忽略非法字段,默认值:false
autoGrowNestedPaths 是否自动增加嵌套路径,默认值:true
allowedFields 绑定字段白名单
disallowedFields 绑定字段黑名单
requiredFields 必须绑定字段

BeanWrapper 的使用场景

  • Spring 底层 JavaBeans 基础设施的中心化接口
  • 通常不会直接使用,间接用于 BeanFactory 和 DataBinder
  • 提供标准 JavaBeans 分析和操作,能够单独或批量存储 Java Bean 的属性(properties)
  • 支持嵌套属性路径(nested path)
  • 实现类 org.springframework.beans.BeanWrapperImpl

Spring 底层 Java Beans 替换实现

JavaBeans 核心实现 - java.beans.BeanInfo

  • 属性(Property)
    • java.beans.PropertyEditor
  • 方法(Method)
  • 事件(Event)
  • 表达式(Expression)

Spring 替代实现 - org.springframework.beans.BeanWrapper

  • 属性(Property)
    • java.beans.PropertyEditor
  • 嵌套属性路径(nested path)

DataBinder 数据校验

DataBinder 与 BeanWrapper

  • bind 方法生成 BeanPropertyBindingResult
  • BeanPropertyBindingResult 关联 BeanWrapper

问题

标准 JavaBeans 是如何操作属性的?

API 说明
java.beans.Introspector Java Beans 内省 API
java.beans.BeanInfo Java Bean 元信息 API
java.beans.BeanDescriptor Java Bean 信息描述符
java.beans.PropertyDescriptor Java Bean 属性描述符
java.beans.MethodDescriptor Java Bean 方法描述符
java.beans.EventSetDescriptor Java Bean 事件集合描述符

参考资料

Spring 校验

Java API 规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email@Length等。Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。

快速入门

引入依赖

如果 spring-boot 版本小于 2.3.x,spring-boot-starter-web 会自动传入 hibernate-validator 依赖。如果 spring-boot 版本大于 2.3.x,则需要手动引入依赖:

1
2
3
4
5
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-parent</artifactId>
<version>6.2.5.Final</version>
</dependency>

对于 web 服务来说,为防止非法参数对业务造成影响,在 Controller 层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:

  • POST、PUT 请求,使用 requestBody 传递参数;
  • GET 请求,使用 requestParam/PathVariable 传递参数。

实际上,不管是 requestBody 参数校验还是方法级别的校验,最终都是调用 Hibernate Validator 执行校验,Spring Validation 只是做了一层封装。

校验示例

(1)在实体上标记校验注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

@NotNull
private Long id;

@NotBlank
@Size(min = 2, max = 10)
private String name;

@Min(value = 1)
@Max(value = 100)
private Integer age;

}

(2)在方法参数上声明校验注解

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
32
33
34
35
36
37
38
39
@Slf4j
@Validated
@RestController
@RequestMapping("validate1")
public class ValidatorController {

/**
* {@link RequestBody} 参数校验
*/
@PostMapping(value = "save")
public DataResult<Boolean> save(@Valid @RequestBody User entity) {
log.info("保存一条记录:{}", JSONUtil.toJsonStr(entity));
return DataResult.ok(true);
}

/**
* {@link RequestParam} 参数校验
*/
@GetMapping(value = "queryByName")
public DataResult<User> queryByName(
@RequestParam("username")
@NotBlank
@Size(min = 2, max = 10)
String name
) {
User user = new User(1L, name, 18);
return DataResult.ok(user);
}

/**
* {@link PathVariable} 参数校验
*/
@GetMapping(value = "detail/{id}")
public DataResult<User> detail(@PathVariable("id") @Min(1L) Long id) {
User user = new User(id, "李四", 18);
return DataResult.ok(user);
}

}

(3)如果请求参数不满足校验规则,则会抛出 ConstraintViolationExceptionMethodArgumentNotValidException 异常。

统一异常处理

在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

/**
* 处理所有不可知的异常
*/
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(Throwable.class)
public Result handleException(Throwable e) {
log.error("未知异常", e);
return new Result(ResultStatus.HTTP_SERVER_ERROR.getCode(), e.getMessage());
}

/**
* 统一处理请求参数校验异常(普通传参)
*
* @param e ConstraintViolationException
* @return {@link DataResult}
*/
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({ ConstraintViolationException.class })
public Result handleConstraintViolationException(final ConstraintViolationException e) {
log.error("ConstraintViolationException", e);
List<String> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
Path path = violation.getPropertyPath();
List<String> pathArr = StrUtil.split(path.toString(), ',');
errors.add(pathArr.get(0) + " " + violation.getMessage());
}
return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, ","));
}

/**
* 处理参数校验异常
*
* @param e MethodArgumentNotValidException
* @return {@link DataResult}
*/
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({ MethodArgumentNotValidException.class })
private Result handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException", e);
List<String> errors = new ArrayList<>();
for (ObjectError error : e.getBindingResult().getAllErrors()) {
errors.add(((FieldError) error).getField() + " " + error.getDefaultMessage());
}
return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, ","));
}

}

进阶使用

分组校验

在实际项目中,可能多个方法需要使用同一个 DTO 类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在 DTO 类的字段上加约束注解无法解决这个问题。因此,spring-validation 支持了分组校验的功能,专门用来解决这类问题。

(1)定义分组

1
2
3
4
5
6
7
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface AddCheck { }

@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface EditCheck { }

(2)在实体上标记校验注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class User2 {

@NotNull(groups = EditCheck.class)
private Long id;

@NotNull(groups = { AddCheck.class, EditCheck.class })
@Size(min = 2, max = 10, groups = { AddCheck.class, EditCheck.class })
private String name;

@IsMobile(message = "不是有效手机号", groups = { AddCheck.class, EditCheck.class })
private String mobile;

}

(3)在方法上根据不同场景进行校验分组

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
@Slf4j
@Validated
@RestController
@RequestMapping("validate2")
public class ValidatorController2 {

/**
* {@link RequestBody} 参数校验
*/
@PostMapping(value = "add")
public DataResult<Boolean> add(@Validated(AddCheck.class) @RequestBody User2 entity) {
log.info("添加一条记录:{}", JSONUtil.toJsonStr(entity));
return DataResult.ok(true);
}

/**
* {@link RequestBody} 参数校验
*/
@PostMapping(value = "edit")
public DataResult<Boolean> edit(@Validated(EditCheck.class) @RequestBody User2 entity) {
log.info("编辑一条记录:{}", JSONUtil.toJsonStr(entity));
return DataResult.ok(true);
}

}

嵌套校验

前面的示例中,DTO 类里面的字段都是基本数据类型和 String 类型。但是实际场景中,有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。
post
比如,上面保存 User 信息的时候同时还带有 Job 信息。需要注意的是,此时 DTO 类的对应字段必须标记@Valid 注解。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Data
public class UserDTO {

@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;

@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;

@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;

@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;

@NotNull(groups = {Save.class, Update.class})
@Valid
private Job job;

@Data
public static class Job {

@Min(value = 1, groups = Update.class)
private Long jobId;

@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;

@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String position;
}

/**
* 保存的时候校验分组
*/
public interface Save {
}

/**
* 更新的时候校验分组
*/
public interface Update {
}
}
复制代码

嵌套校验可以结合分组校验一起使用。还有就是嵌套集合校验会对集合里面的每一项都进行校验,例如List<Job>字段会对这个 list 里面的每一个 Job 对象都进行校验

自定义校验注解

(1)自定义校验注解 @IsMobile

1
2
3
4
5
6
7
8
9
10
11
12
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
public @interface IsMobile {

String message();

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}

(2)实现 ConstraintValidator 接口,编写 @IsMobile 校验注解的解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cn.hutool.core.util.StrUtil;
import io.github.dunwu.spring.core.validation.annotation.IsMobile;
import io.github.dunwu.tool.util.ValidatorUtil;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MobileValidator implements ConstraintValidator<IsMobile, String> {

@Override
public void initialize(IsMobile isMobile) { }

@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (StrUtil.isBlank(s)) {
return false;
} else {
return ValidatorUtil.isMobile(s);
}
}

}

自定义校验

可以通过实现 org.springframework.validation.Validator 接口来自定义校验。

有以下要点

  • 实现 supports 方法
  • 实现 validate 方法
    • 通过 Errors 对象收集错误
      • ObjectError:对象(Bean)错误:
      • FieldError:对象(Bean)属性(Property)错误
    • 通过 ObjectErrorFieldError 关联 MessageSource 实现获取最终的错误文案
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package io.github.dunwu.spring.core.validation;

import io.github.dunwu.spring.core.validation.annotation.Valid;
import io.github.dunwu.spring.core.validation.config.CustomValidatorConfig;
import io.github.dunwu.spring.core.validation.entity.Person;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Component
public class CustomValidator implements Validator {

private final CustomValidatorConfig validatorConfig;

public CustomValidator(CustomValidatorConfig validatorConfig) {
this.validatorConfig = validatorConfig;
}

/**
* 本校验器只针对 Person 对象进行校验
*/
@Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}

@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "name", "name.empty");

List<Field> fields = getFields(target.getClass());
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().getAnnotation(Valid.class) != null) {
try {
ValidatorRule validatorRule = validatorConfig.findRule(annotation);
if (validatorRule != null) {
validatorRule.valid(annotation, target, field, errors);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

private List<Field> getFields(Class<?> clazz) {
// 声明Field数组
List<Field> fields = new ArrayList<>();
// 如果class类型不为空
while (clazz != null) {
// 添加属性到属性数组
Collections.addAll(fields, clazz.getDeclaredFields());
clazz = clazz.getSuperclass();
}
return fields;
}

}

快速失败(Fail Fast)

Spring Validation 默认会校验完所有字段,然后才抛出异常。可以通过一些简单的配置,开启 Fali Fast 模式,一旦校验失败就立即返回。

1
2
3
4
5
6
7
8
9
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}

Spring 校验原理

Spring 校验使用场景

  • Spring 常规校验(Validator)
  • Spring 数据绑定(DataBinder)
  • Spring Web 参数绑定(WebDataBinder)
  • Spring WebMVC/WebFlux 处理方法参数校验

Validator 接口设计

  • 接口职责
    • Spring 内部校验器接口,通过编程的方式校验目标对象
  • 核心方法
    • supports(Class):校验目标类能否校验
    • validate(Object,Errors):校验目标对象,并将校验失败的内容输出至 Errors 对象
  • 配套组件
    • 错误收集器:org.springframework.validation.Errors
    • Validator 工具类:org.springframework.validation.ValidationUtils

Errors 接口设计

  • 接口职责
    • 数据绑定和校验错误收集接口,与 Java Bean 和其属性有强关联性
  • 核心方法
    • reject 方法(重载):收集错误文案
    • rejectValue 方法(重载):收集对象字段中的错误文案
  • 配套组件
    • Java Bean 错误描述:org.springframework.validation.ObjectError
    • Java Bean 属性错误描述:org.springframework.validation.FieldError

Errors 文案来源

Errors 文案生成步骤

  • 选择 Errors 实现(如:org.springframework.validation.BeanPropertyBindingResult
  • 调用 reject 或 rejectValue 方法
  • 获取 Errors 对象中 ObjectError 或 FieldError
  • 将 ObjectError 或 FieldError 中的 code 和 args,关联 MessageSource 实现(如:ResourceBundleMessageSource

spring web 校验原理

RequestBody 参数校验实现原理

在 spring-mvc 中,RequestResponseBodyMethodProcessor 是用于解析 @RequestBody 标注的参数以及处理@ResponseBody 标注方法的返回值的。其中,执行参数校验的逻辑肯定就在解析参数的方法 resolveArgument() 中:

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
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);

if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
// 尝试进行参数校验
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
// 如果存在校验错误,则抛出 MethodArgumentNotValidException
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}

return adaptArgumentIfNecessary(arg, parameter);
}

可以看到,resolveArgument()调用了 validateIfApplicable()进行参数校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
// 获取参数注解,如 @RequestBody、@Valid、@Validated
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
// 先尝试获取 @Validated 注解
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
// 如果标注了 @Validated,直接开始校验。
// 如果没有,那么判断参数前是否有 Valid 开头的注解。
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
// 执行校验
binder.validate(validationHints);
break;
}
}
}

以上代码,就解释了 Spring 为什么能同时支持 @Validated@Valid 两个注解。

接下来,看一下 WebDataBinder.validate() 的实现:

1
2
3
4
5
6
7
8
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(
// 此处调用 Hibernate Validator 执行真正的校验
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}

通过上面代码,可以看出 Spring 校验实际上是基于 Hibernate Validator 的封装。

方法级别的参数校验实现原理

Spring 支持根据方法去进行拦截、校验,原理就在于应用了 AOP 技术。具体来说,是通过 MethodValidationPostProcessor 动态注册 AOP 切面,然后使用 MethodValidationInterceptor 对切点方法织入增强。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean {
@Override
public void afterPropertiesSet() {
// 为所有 @Validated 标注的 Bean 创建切面
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
// 创建 Advisor 进行增强
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}

// 创建 Advice,本质就是一个方法拦截器
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
}

接着看一下 MethodValidationInterceptor

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
32
33
34
35
public class MethodValidationInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 无需增强的方法,直接跳过
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
// 获取分组信息
Class<?>[] groups = determineValidationGroups(invocation);
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;
try {
// 方法入参校验,最终还是委托给 Hibernate Validator 来校验
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
...
}
// 有异常直接抛出
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
// 真正的方法调用
Object returnValue = invocation.proceed();
// 对返回值做校验,最终还是委托给Hibernate Validator来校验
result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
// 有异常直接抛出
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
}

实际上,不管是 requestBody 参数校验还是方法级别的校验,最终都是调用 Hibernate Validator 执行校验,Spring Validation 只是做了一层封装。

问题

Spring 有哪些校验核心组件

  • 检验器:org.springframework.validation.Validator
  • 错误收集器:org.springframework.validation.Errors
  • Java Bean 错误描述:org.springframework.validation.ObjectError
  • Java Bean 属性错误描述:org.springframework.validation.FieldError
  • Bean Validation 适配:org.springframework.validation.beanvalidation.LocalValidatorFactoryBean

参考资料

Spring 国际化

Spring 国际化使用场景

  • 普通国际化文案
  • Bean Validation 校验国际化文案
  • Web 站点页面渲染
  • Web MVC 错误消息提示

Spring 国际化接口

  • 核心接口:org.springframework.context.MessageSource
  • 主要概念
    • 文案模板编码(code)
    • 文案模板参数(args)
    • 区域(Locale)

层次性 MessageSource

  • Spring 层次性接口回顾
    • org.springframework.beans.factory.HierarchicalBeanFactory
    • org.springframework.context.ApplicationContext
    • org.springframework.beans.factory.config.BeanDefinition
  • Spring 层次性国际化接口
    • org.springframework.context.HierarchicalMessageSource

Java 国际化标准实现

核心接口:

  • 抽象实现 - java.util.ResourceBundle
  • Properties 资源实现 - java.util.PropertyResourceBundle
  • 例举实现 - java.util.ListResourceBundle

ResourceBundle 核心特性

  • Key-Value 设计
  • 层次性设计
  • 缓存设计
  • 字符编码控制 - java.util.ResourceBundle.Control(@since 1.6)
  • Control SPI 扩展 - java.util.spi.ResourceBundleControlProvider(@since 1.8)

Java 文本格式化

  • 核心接口
    • java.text.MessageFormat
  • 基本用法
    • 设置消息格式模式- new MessageFormat(…)
    • 格式化 - format(new Object[]{…})
  • 消息格式模式
    • 格式元素:{ArgumentIndex (,FormatType,(FormatStyle))}
    • FormatType:消息格式类型,可选项,每种类型在 number、date、time 和 choice 类型选其一
    • FormatStyle:消息格式风格,可选项,包括:short、medium、long、full、integer、currency、
      percent
  • 高级特性
    • 重置消息格式模式
    • 重置 java.util.Locale
    • 重置 java.text.Format

MessageSource 开箱即用实现

  • 基于 ResourceBundle + MessageFormat 组合 MessageSource 实现
  • org.springframework.context.support.ResourceBundleMessageSource
  • 可重载 Properties + MessageFormat 组合 MessageSource 实现
  • org.springframework.context.support.ReloadableResourceBundleMessageSource

MessageSource 內建依赖

  • MessageSource 內建 Bean 可能来源
  • 预注册 Bean 名称为:“messageSource”,类型为:MessageSource Bean
  • 默认內建实现 - DelegatingMessageSource
  • 层次性查找 MessageSource 对象

问题

Spring Boot 为什么要新建 MessageSource Bean

  • AbstractApplicationContext 的实现决定了 MessageSource 內建实现
  • Spring Boot 通过外部化配置简化 MessageSource Bean 构建
  • Spring Boot 基于 Bean Validation 校验非常普遍

Spring 国际化接口有哪些

  • 核心接口 - MessageSource
  • 层次性接口 - org.springframework.context.HierarchicalMessageSource

Spring 有哪些 MessageSource 內建实现

  • org.springframework.context.support.ResourceBundleMessageSource
  • org.springframework.context.support.ReloadableResourceBundleMessageSource
  • org.springframework.context.support.StaticMessageSource
  • org.springframework.context.support.DelegatingMessageSource

如何实现配置自动更新 MessageSource

主要技术

  • Java NIO 2:java.nio.file.WatchService
  • Java Concurrency : java.util.concurrent.ExecutorService
  • Spring:org.springframework.context.support.AbstractMessageSource

参考资料

Spring 配置元数据

Spring 配置元信息

  • Spring Bean 配置元信息 - BeanDefinition
  • Spring Bean 属性元信息 - PropertyValues
  • Spring 容器配置元信息
  • Spring 外部化配置元信息 - PropertySource
  • Spring Profile 元信息 - @Profile

Spring Bean 配置元信息

Bean 配置元信息 - BeanDefinition

  • GenericBeanDefinition:通用型 BeanDefinition
  • RootBeanDefinition:无 Parent 的 BeanDefinition 或者合并后 BeanDefinition
  • AnnotatedBeanDefinition:注解标注的 BeanDefinition

Spring Bean 属性元信息

  • Bean 属性元信息 - PropertyValues
    • 可修改实现 - MutablePropertyValues
    • 元素成员 - PropertyValue
  • Bean 属性上下文存储 - AttributeAccessor
  • Bean 元信息元素 - BeanMetadataElement

Spring 容器配置元信息

Spring XML 配置元信息 - beans 元素相关

beans 元素属性 默认值 使用场景
profile null(留空) Spring Profiles 配置值
default-lazy-init default 当 outter beans “default-lazy-init” 属性存在时,继承该值,否则为“false”
default-merge default 当 outter beans “default-merge” 属性存在时,继承该值,否则为“false”
default-autowire default 当 outter beans “default-autowire” 属性存在时,继承该值,否则为“no”
default-autowire-candidates null(留空) 默认 Spring Beans 名称 pattern
default-init-method null(留空) 默认 Spring Beans 自定义初始化方法
default-destroy-method null(留空) 默认 Spring Beans 自定义销毁方法

Spring XML 配置元信息 - 应用上下文相关

XML 元素 使用场景
<context:annotation-config /> 激活 Spring 注解驱动
<context:component-scan /> Spring @Component 以及自定义注解扫描
<context:load-time-weaver /> 激活 Spring LoadTimeWeaver
<context:mbean-export /> 暴露 Spring Beans 作为 JMX Beans
<context:mbean-server /> 将当前平台作为 MBeanServer
<context:property-placeholder /> 加载外部化配置资源作为 Spring 属性配
<context:property-override /> 利用外部化配置资源覆盖 Spring 属

基于 XML 文件装载 Spring Bean 配置元信息

底层实现 - XmlBeanDefinitionReader

XML 元素 使用场景
<beans:beans /> 单 XML 资源下的多个 Spring Beans 配置
<beans:bean /> 单个 Spring Bean 定义(BeanDefinition)配置
<beans:alias /> 为 Spring Bean 定义(BeanDefinition)映射别名
<beans:import /> 加载外部 Spring XML 配置资源

基于 Properties 文件装载 Spring Bean 配置元信息

底层实现 - PropertiesBeanDefinitionReader

Properties 属性名 使用场景
class Bean 类全称限定名
abstract 是否为抽象的 BeanDefinition
parent 指定 parent BeanDefinition 名称
lazy-init 是否为延迟初始化
ref 引用其他 Bean 的名称
scope 设置 Bean 的 scope 属性
${n} n 表示第 n+1 个构造器参数

基于 Java 注解装载 Spring Bean 配置元信息

Spring 模式注解

Spring 注解 场景说明 起始版本
@Repository 数据仓储模式注解 2.0
@Component 通用组件模式注解 2.5
@Service 服务模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置类模式注解 3.0

Spring Bean 定义注解

| Spring 注解 | 场景说明 | 起始版本 |
| ———— | —————————————— | ———– | — |
| @Bean | 替换 XML 元素 <bean> | 3.0 |
| @DependsOn | 替代 XML 属性 <bean depends-on="..."/> | 3.0 |
| @Lazy | 替代 XML 属性 <bean lazy-init="true | falses" /> | 3.0 |
| @Primary | 替换 XML 元素 <bean primary="true | false" /> | 3.0 |
| @Role | 替换 XML 元素 <bean role="..." /> | 3.1 |
| @Lookup | 替代 XML 属性 <bean lookup-method="..."> | 4.1 |

Spring Bean 依赖注入注解

Spring 注解 场景说明 起始版本
@Autowired Bean 依赖注入,支持多种依赖查找方式 2.5
@Qualifier 细粒度的 @Autowired 依赖查找 2.5
Java 注解 场景说明 起始版本
@Resource 类似于 @Autowired 2.5
@Inject 类似于 @Autowired 2.5

Spring Bean 条件装配注解

Spring 注解 场景说明 起始版本
@Profile 配置化条件装配 3.1
@Conditional 编程条件装配 4.0

Spring Bean 生命周期回调注解

Spring 注解 场景说明 起始版本
@PostConstruct 替换 XML 元素 或 InitializingBean 2.5
@PreDestroy 替换 XML 元素 或 DisposableBean 2.5

Spring BeanDefinition 解析与注册

Spring 注解 场景说明 起始版本
XML 资源 XmlBeanDefinitionReader 1.0
Properties 资源 PropertiesBeanDefinitionReader 1.0
Java 注解 AnnotatedBeanDefinitionReader 3.0

Spring Bean 配置元信息底层实现

Spring XML 资源 BeanDefinition 解析与注册

核心 API - XmlBeanDefinitionReader

  • 资源 - Resource
  • 底层 - BeanDefinitionDocumentReader
    • XML 解析 - Java DOM Level 3 API
    • BeanDefinition 解析 - BeanDefinitionParserDelegate
    • BeanDefinition 注册 - BeanDefinitionRegistry

Spring Properties 资源 BeanDefinition 解析与注册

核心 API - PropertiesBeanDefinitionReader

  • 资源
    • 字节流 - Resource
    • 字符流 - EncodedResouce
  • 底层
    • 存储 - java.util.Properties
    • BeanDefinition 解析 - API 内部实现
    • BeanDefinition 注册 - BeanDefinitionRegistry

Spring Java 注册 BeanDefinition 解析与注册

核心 API - AnnotatedBeanDefinitionReader

  • 资源
    • 类对象 - java.lang.Class
  • 底层
    • 条件评估 - ConditionEvaluator
    • Bean 范围解析 - ScopeMetadataResolver
    • BeanDefinition 解析 - 内部 API 实现
    • BeanDefinition 处理 - AnnotationConfigUtils.processCommonDefinitionAnnotations
    • BeanDefinition 注册 - BeanDefinitionRegistry

基于 XML 文件装载 Spring IoC 容器配置元信息

Spring IoC 容器相关 XML 配置

命名空间 所属模块 Schema 资源 URL
beans spring-beans https://www.springframework.org/schema/beans/spring-beans.xsd
context spring-context https://www.springframework.org/schema/context/spring-context.xsd
aop spring-aop https://www.springframework.org/schema/aop/spring-aop.xsd
tx spring-tx https://www.springframework.org/schema/tx/spring-tx.xsd
util spring-beans beans https://www.springframework.org/schema/util/spring-util.xsd
tool spring-beans https://www.springframework.org/schema/tool/spring-tool.xsd

基于 Java 注解装载 Spring IoC 容器配置元信息

Spring IoC 容器装配注解

Spring 注解 场景说明 起始版本
@ImportResource 替换 XML 元素 <import> 3.0
@Import 导入 Configuration Class 3.0
@ComponentScan 扫描指定 package 下标注 Spring 模式注解的类 3.1

Spring IoC 配属属性注解

Spring 注解 场景说明 起始版本
@PropertySource 配置属性抽象 PropertySource 注解 3.1
@PropertySources @PropertySource 集合注解 4.0

基于 Extensible XML authoring 扩展 SpringXML 元素

Spring XML 扩展

  • 编写 XML Schema 文件:定义 XML 结构
  • 自定义 NamespaceHandler 实现:命名空间绑定
  • 自定义 BeanDefinitionParser 实现:XML 元素与 BeanDefinition 解析
  • 注册 XML 扩展:命名空间与 XML Schema 映射

Extensible XML authoring 扩展原理

触发时机

  • AbstractApplicationContext#obtainFreshBeanFactory
    • AbstractRefreshableApplicationContext#refreshBeanFactory
      • AbstractXmlApplicationContext#loadBeanDefinitions
          • XmlBeanDefinitionReader#doLoadBeanDefinitions
              • BeanDefinitionParserDelegate#parseCustomElement

核心流程

BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, BeanDefinition)

  • 获取 namespace
  • 通过 namespace 解析 NamespaceHandler
  • 构造 ParserContext
  • 解析元素,获取 BeanDefinintion

基于 Properties 文件装载外部化配置

注解驱动

  • @org.springframework.context.annotation.PropertySource
  • @org.springframework.context.annotation.PropertySources

API 编程

  • org.springframework.core.env.PropertySource
  • org.springframework.core.env.PropertySources

基于 YAML 文件装载外部化配置

API 编程

  • org.springframework.beans.factory.config.YamlProcessor
    • org.springframework.beans.factory.config.YamlMapFactoryBean
    • org.springframework.beans.factory.config.YamlPropertiesFactoryBean

问题

Spring 內建 XML Schema 常见有哪些

命名空间 所属模块 Schema 资源 URL
beans spring-beans https://www.springframework.org/schema/beans/spring-beans.xsd
context spring-context https://www.springframework.org/schema/context/spring-context.xsd
aop spring-aop https://www.springframework.org/schema/aop/spring-aop.xsd
tx spring-tx https://www.springframework.org/schema/tx/spring-tx.xsd
util spring-beans beans https://www.springframework.org/schema/util/spring-util.xsd
tool spring-beans https://www.springframework.org/schema/tool/spring-tool.xsd

Spring 配置元信息具体有哪些

  • Bean 配置元信息:通过媒介(如 XML、Proeprties 等),解析 BeanDefinition
  • IoC 容器配置元信息:通过媒介(如 XML、Proeprties 等),控制 IoC 容器行为,比如注解驱动、AOP 等
  • 外部化配置:通过资源抽象(如 Proeprties、YAML 等),控制 PropertySource
  • Spring Profile:通过外部化配置,提供条件分支流程

Extensible XML authoring 的缺点

  • 高复杂度:开发人员需要熟悉 XML Schema,spring.handlers,spring.schemas 以及 Spring API
  • 嵌套元素支持较弱:通常需要使用方法递归或者其嵌套解析的方式处理嵌套(子)元素
  • XML 处理性能较差:Spring XML 基于 DOM Level 3 API 实现,该 API 便于理解,然而性能较差
  • XML 框架移植性差:很难适配高性能和便利性的 XML 框架,如 JAXB

参考资料

Spring Bean 生命周期

Spring Bean 元信息配置阶段

BeanDefinition 配置

  • 面向资源
    • XML 配置
    • Properties 资源配置
  • 面向注解
  • 面向 API

Spring Bean 元信息解析阶段

  • 面向资源 BeanDefinition 解析
    • BeanDefinitionReader
    • XML 解析器 - BeanDefinitionParser
  • 面向注解 BeanDefinition 解析
    • AnnotatedBeanDefinitionReader

Spring Bean 注册阶段

BeanDefinition 注册接口:BeanDefinitionRegistry

Spring BeanDefinition 合并阶段

BeanDefinition 合并

父子 BeanDefinition 合并

  • 当前 BeanFactory 查找
  • 层次性 BeanFactory 查找

Spring Bean Class 加载阶段

  • ClassLoader 类加载
  • Java Security 安全控制
  • ConfigurableBeanFactory 临时 ClassLoader

Spring Bean 实例化前阶段

实例化方式

  • 传统实例化方式:实例化策略(InstantiationStrategy)
  • 构造器依赖注入

Spring Bean 实例化阶段

非主流生命周期 - Bean 实例化前阶段

InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

Spring Bean 实例化后阶段

Bean 属性赋值(Populate)判断

InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

Spring Bean 属性赋值前阶段

  • Bean 属性值元信息
    • PropertyValues
  • Bean 属性赋值前回调
    • Spring 1.2 - 5.0:InstantiationAwareBeanPostProcessor#postProcessPropertyValues
    • Spring 5.1:InstantiationAwareBeanPostProcessor#postProcessProperties

Spring Bean Aware 接口回调阶段

Spring Aware 接口:

  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware
  • EnvironmentAware
  • EmbeddedValueResolverAware
  • ResourceLoaderAware
  • ApplicationEventPublisherAware
  • MessageSourceAware
  • ApplicationContextAware

Spring Bean 初始化前阶段

已完成:

  • Bean 实例化

  • Bean 属性赋值

  • Bean Aware 接口回调

方法回调:

  • BeanPostProcessor#postProcessBeforeInitialization

Spring Bean 初始化阶段

Bean 初始化(Initialization)

  • @PostConstruct 标注方法
  • 实现 InitializingBean 接口的 afterPropertiesSet() 方法
  • 自定义初始化方法

Spring Bean 初始化后阶段

方法回调:BeanPostProcessor#postProcessAfterInitialization

Spring Bean 初始化完成阶段

方法回调:Spring 4.1 +:SmartInitializingSingleton#afterSingletonsInstantiated

Spring Bean 销毁前阶段

方法回调:DestructionAwareBeanPostProcessor#postProcessBeforeDestruction

Spring Bean 销毁阶段

Bean 销毁(Destroy)

  • @PreDestroy 标注方法
  • 实现 DisposableBean 接口的 destroy() 方法
  • 自定义销毁方法

Spring Bean 垃圾收集

Bean 垃圾回收(GC)

  • 关闭 Spring 容器(应用上下文)
  • 执行 GC
  • Spring Bean 覆盖的 finalize() 方法被回调

问题

BeanPostProcessor 的使用场景有哪些

BeanPostProcessor 提供 Spring Bean 初始化前和初始化后的生命周期回调,分别对应 postProcessBeforeInitialization 以及 postProcessAfterInitialization 方法,允许对关心的 Bean 进行扩展,甚至是替换。

加分项:其中,ApplicationContext 相关的 Aware 回调也是基于 BeanPostProcessor 实现,即 ApplicationContextAwareProcessor。

BeanFactoryPostProcessor 与 BeanPostProcessor 的区别

BeanFactoryPostProcessor 是 Spring BeanFactory(实际为 ConfigurableListableBeanFactory) 的后置处理器,用于扩展 BeanFactory,或通过 BeanFactory 进行依赖查找和依赖注入。

BeanFactoryPostProcessor 必须有 Spring ApplicationContext 执行,BeanFactory 无法与其直接交互。

而 BeanPostProcessor 则直接与 BeanFactory 关联,属于 N 对 1 的关系。

BeanFactory 是怎样处理 Bean 生命周期

BeanFactory 的默认实现为 DefaultListableBeanFactory,其中 Bean生命周期与方法映射如下:

  • BeanDefinition 注册阶段 - registerBeanDefinition
  • BeanDefinition 合并阶段 - getMergedBeanDefinition
  • Bean 实例化前阶段 - resolveBeforeInstantiation
  • Bean 实例化阶段 - createBeanInstance
  • Bean 初始化后阶段 - populateBean
  • Bean 属性赋值前阶段 - populateBean
  • Bean 属性赋值阶段 - populateBean
  • Bean Aware 接口回调阶段 - initializeBean
  • Bean 初始化前阶段 - initializeBean
  • Bean 初始化阶段 - initializeBean
  • Bean 初始化后阶段 - initializeBean
  • Bean 初始化完成阶段 - preInstantiateSingletons
  • Bean 销毁前阶段 - destroyBean
  • Bean 销毁阶段 - destroyBean

参考资料