Spring 笔记——核心-数据规则篇

前言

官网地址:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation

本篇的内容,spring官方说明是数据校验,绑定,类型转换。

将验证视为业务逻辑有利有弊,Spring 提供了一种验证(和数据绑定)设计,不排除其中任何一个。具体来说,验证不应绑定到 Web 层,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题,Spring 提供了一个Validator既基本又非常适用于应用程序每一层的契约。

个人浅薄经验:数据校验功能是对我们的API调用进行数据校验,防止非法参数;数据绑定功能是各个bean的属性注入,经常看到的场景就是配置文件;

数据绑定与校验

DataBinder 数据绑定器

Validator 校验器

BeanWrapper bean包装

ValidationUtils 数据校验工具类

数据类型转换

Converter 转换器定义,是个函数式接口,定义了一个转换动作 api

package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}

ConverterFactory 转换器工厂类定义,案例是 StringToEnumConverterFactory

package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}

GenericConverter 通用转换器定义,相比 Converter 他更方便写支持多种类型转换的转换器

package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

ConversionService 转换服务定义

package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

关于集合类型的处理,我们怎么告诉转换器集合内元素的数据类型。直接通过 class 对象是肯定不行的。所以这里 spring 设计了个 TypeDescriptor 来辅助。

DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));

使用方法无非就是 xml配置声明 与代码编程注入。没什么好说的
这里类型转换值得一提的是 spring 在这个功能上的设计思路,我们写自己的业务代码的时候可以借鉴。设计好了的话,具体的这几个顶层接口,看一样就明白有哪些方法是干什么的。

String与对象的互相转换

我们最常用的json传输,我们发送的是一个遵循json规范的字符串,而这个字符串在spring中是怎么转换成我们具体使用的对象的?
Printer 定义一个对象转字符串 api
Parser 定义一个字符串转对象 api
Formatter 继承这 Printer 与 Parser

// Local参数传入的是当前地区
// Printer
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}
// Parser
import java.text.ParseException;
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}
// Formatter
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
DateFormatter 案例
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
private String pattern;
public DateFormatter(String pattern) {
this.pattern = pattern;
}
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
dateFormat.setLenient(false);
return dateFormat;
}
}
通过注释的方式转换数据

spring 本身实现了一套处理 基于注释的方式声明格式转换器 的流程,我们只需按照这个流程提供的接入口接入即可使用。本来想着 Spring 自身的 @NumberFormat 注解已经够用了,然后整了半天一直失效,最后无奈,专门重写序列号与反序列化类提供序列化与反序列化处理。主要是用到 @JsonSerialize@JsonDeserialize 注解,只要配置了这两个注解,Spring 序列化前端传过来的 json 的时候就会调用相应的配置类处理
效果图:

MoneySerializer

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class MoneySerializer extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("¥ "+ o.setScale(2, RoundingMode.HALF_UP));
}
}

MoneyDeSerializer

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.io.BigDecimalParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
import java.io.IOException;
import java.math.BigDecimal;
public class MoneyDeSerializer extends NumberDeserializers.BigDecimalDeserializer {
@Override
public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
String moneyStr = deserializationContext.readValue(jsonParser, String.class);
moneyStr = moneyStr.trim().replace("¥ ", "");
return BigDecimalParser.parse(moneyStr);
}
}
配置全局日期与字符串转换

以 Converter 为入口研究了半天,没弄出来,还是直接使用过去的以 Jackson2 为入口的配置吧

import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @author ListJiang
* @class LocalDateTime序列化配置
* @remark 用于解决json转换时的格式问题
* @date 2022/01/03
*/
@Configuration
public class LocalDateTimeSerializerConfig {
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_PATTERN = "yyyy-MM-dd";
private static final String TIME_PATTERN = "HH:mm:ss";
/**
* 统一配置 LocalDate、LocalDateTime、LocalTime 与 String 之间的互相转换
* <p>
* 最终效果:
* {
* "localDate": "2022-01-03",
* "localDateTime": "2022-01-03 18:36:53",
* "localTime": "18:36:53",
* "date": "2022-01-03 18:36:53",
* "calendar": "2022-01-03 18:36:53"
* }
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
JavaTimeModule module = new JavaTimeModule();
module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));
module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(TIME_PATTERN)));
module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));
return builder -> {
builder.simpleDateFormat(DATE_TIME_PATTERN);
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));
builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_PATTERN)));
builder.modules(module);
};
}
}

bean校验

应该叫API(内部的与外部的)交互时的数据校验。系统内部的验证其实意义不大(比如 controller 调用 service ,serviceA 调用 serviceB),大部分的时候我们调用之前就会处理好。主要需要处理的是前端调用的校验与外部系统调用API的数据校验。
而 Spring 基本上把通用场景都考虑实现了,只需要使用即可。在 javax.validation.constraints 包下面,各个注解的含义基本上看一眼就能理解,实在不理解,点进去看下注释就行。使用的话,实体属性上加上注解,实体传参的时候标注 @Valid 或者 @Validated 就行,@Valid是 spring-boot-starter-validation 引入的 jakarta.validation-api-2.0.2.jar 里面的,@Validated 是 spring-context-5.3.14.jar 里面的。随便确定一个,项目整体保持一致就行。
此处主要说下自定义的数据校验。比如有个需求叫校验前端传入的地址全称必须是"xxx省xxx市xxx区",这个 省、市不定,即必须可以通过不同的配置校验一下案例
xxx省xxx市xxx区
xxx省xxx市xxx县
xxx省xxx市
市xxx区
很晚了,睡觉,有空补上

自定义参数校验注解实现,涉及两个注解相关配置类,全局异常处理类,请求实体类,请求 Controller 类

源码地址:https://gitee.com/J-dw/springboot-study.git




posted @   临渊不羡渔  阅读(183)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示