一、校验场景与技巧
业务逻辑需要通过邮件发送资料,界面提供邮件信息的配置,包含标题、模板、收件人列表等。其中收件人邮件地址均要符合通用的邮件格式。
如何简单有效的在java后端对请求中的收件人邮件地址做校验呢?
我们其实可以利用遵循JSR-303规范定义或衍生的注解来实现,示例代码:
即:
- 请求体对象中集合类型字段的泛型中增加@Email注解的校验配置
- 请求方法带@RequestBody注解的参数,增加@Valid注解
二、原理探索
经过探索分析,原理主要涵盖以下知识点,我们将从文档、源码等多角度来做到知其然知其所以然。
1.JSR-303
JSR-303是JCP (Java Community Process)发布在其官网的一种Java规范提案,称为Bean Validation规范,为Bean验证定义了元数据模型和API。
2.自动配置与事件机制
由spring-boot-auto-configure包的spring.factories中指定配置的BackgroundPreinitializer来自动承担应用监听器的角色。该对象bean注入spring容器后,监听springboot应用启动事件,监听到则异步触发多个模块的初始化,包括验证模块ValidationInitializer。该验证模块的初始化机制,稍后会详细展开。
3.SPI & Validation API
在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。而
SPI (Service Provider Interface) 是Java提供的一套用来被第三方实现或者扩展的API,核心思想就是解耦,与即插即用。
典型案例:JDBC驱动的加载,SLF4J日志接口,dubbo的扩展,sharding-jdbc 数据加密,Flink的Table加载等。
验证相关的SPI机制如下:
从上图可知,springboot-auto-configure触发调用,validation-api中提供了验证提供器的服务接口定义,hibernate-validator提供了服务接口的实现,由ServiceLoader负责将其加载。
4.ServiceLoader
JDK 1.6 引入,用于懒加载SPI服务提供者。
从源码可见,ServiceLoader负责搜索Classpath下META-INF/services目录下名称为SPI接口的文件,解析其内容(接口的实现类的路径),并加载实例化。
5.hibernateValidator
第2小节提到了ValidationInitializer,此处由Validation类的configure方法通过ServiceLoader加载到hibernateValidator。
思考:对象属性打上的验证注解,每次请求时应用程序是如何根据注解的不同采取不同的验证呢?
还是用源码来揭晓谜底:
本小节第一张截图中代码 configuration.buildValidatorFactory()即是调用上图箭头所指的方法,在该方法中会进行初始化的工作,包括验证注解及其关系的绑定,如下图,是否恍然大悟:
例如,注解@NotBlank将由NotBlankValidator验证器来检查是否该字段的值符合非空串的校验。
6.触发
让我们通过一张特殊的流程类图,来了解每次请求到达,应用程序是如何完整的完成校验及其它相关处理的。
7.源码技巧赏析
请再次开动脑筋
精髓:一个boolean成员变量 + 字节与表达式