【Spring】Spring哪些源码解决了哪些问题P1
欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。
目录
- Spring是怎么处理请求的?
- Spring请求方法参数与返回怎么处理?
- Spring如何自定义 JSON 的序列化/反序列化行为
- Spring是怎么处理事务的?
- Spring Boot的“约定大于配置”是如何实现的?
- Spring Boot的自动配置是如何发生的?
Spring是怎么处理请求的?
对处理请求感兴趣的可以看之前的这篇Spring的请求处理
Spring请求方法参数与返回怎么处理?
请求方法中的参数,特别是 @RequestBody 标记的对象是怎么处理的呢?之前的请求篇有提及请求处理,但是没有提及参数,这里补充一下。
在RequestMappingHandlerAdapter调用方法前,它会遍历方法的参数,为每个参数找到合适的HandlerMethodArgumentResolver来解析参数值。
对于标记了@RequestBody的参数,则使用RequestResponseBodyMethodProcessor
。
RequestResponseBodyMethodProcessor在解析@RequestBody参数时,会从HttpServletRequest中获取输入流,然后遍历注册的HttpMessageConverter列表,找到一个能处理请求Content-Type(如application/json)并且能将输入流转换为目标参数类型的转换器。MappingJackson2HttpMessageConverter就是利用Jackson库将JSON字符串反序列化为Java对象。
Spring如何自定义 JSON 的序列化/反序列化行为
所以,如果需要自定义 JSON 的序列化/反序列化行为(比如日期格式、null值的处理),我们可以从Jackson2ObjectMapperBuilder / ObjectMapper入手。
Spring Boot 自动配置MappingJackson2HttpMessageConverter时,会使用Jackson2ObjectMapperBuilder来构建底层的ObjectMapper。
可以自定义一个Jackson2ObjectMapperBuilderCustomizer Bean,从而可以对Spring Boot创建的ObjectMapper进行细粒度调整,而不用完全替换它。
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
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 org.springframework.core.Ordered; // 如果需要排序import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;@Configuration
public class JacksonCustomizerConfig {private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";private static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";@Beanpublic Jackson2ObjectMapperBuilderCustomizer customJackson() {return builder -> {// --- 通用配置 ---// 1. 时区配置:序列化和反序列化日期时都使用指定时区// 注意:更推荐在Java对象中使用带时区的类型(如ZonedDateTime),或在DTO转换时处理builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai")); // 或 TimeZone.getDefault()// 2. Null值处理:序列化时,null值的字段不参与序列化builder.serializationInclusion(JsonInclude.Include.NON_NULL);// 其他选项:// JsonInclude.Include.ALWAYS (默认)// JsonInclude.Include.NON_EMPTY (null或空集合、空字符串等不序列化)// JsonInclude.Include.NON_DEFAULT (如果字段值等于其默认值,则不序列化)// 3. 反序列化特性:JSON中有多余的未知字段时,不报错,而是忽略builder.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);// 如果想开启,则用 featuresToEnable// 4. 序列化特性:对于只有一个元素的数组,序列化时不作为数组,而是作为单个对象(谨慎使用)// builder.featuresToEnable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);// 反之,如果想禁用,则用 featuresToDisable// 5. 序列化特性:日期序列化为时间戳(ms)还是ISO 8601格式字符串// Spring Boot 默认对于Java 8 Date/Time API是序列化为ISO字符串,// 对于 java.util.Date 是序列化为时间戳。// 禁用时间戳,强制使用ISO格式(或你自定义的格式)builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);// 6. 序列化特性:对于空对象(没有任何字段或getter),序列化时不抛出异常builder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);// 7. Mapper特性:枚举值使用toString()方法进行序列化/反序列化// builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);// builder.featuresToEnable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);// 8. Mapper特性:反序列化枚举时忽略大小写builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);// --- Java 8 日期时间 API (java.time.*) 格式化 ---// Spring Boot 默认会通过 jackson-datatype-jsr310 模块支持Java 8日期时间类型// 以下是更细致的格式化配置JavaTimeModule javaTimeModule = new JavaTimeModule();// LocalDateTimejavaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));// LocalDatejavaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));// LocalTimejavaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));builder.modulesToInstall(javaTimeModule); // 注册自定义配置的JavaTimeModule// 或者 builder.modules(javaTimeModule); 效果类似,modules会替换现有模块,modulesToInstall会添加// --- 传统日期 (java.util.Date) 格式化 ---// 如果你还在使用 java.util.Date,可以这样配置builder.simpleDateFormat(DEFAULT_DATE_TIME_FORMAT);// --- 其他类型的自定义序列化器/反序列化器 ---// 例如,将Long、BigInteger序列化为字符串,以防止前端JS精度丢失问题builder.serializerByType(Long.class, ToStringSerializer.instance);builder.serializerByType(Long.TYPE, ToStringSerializer.instance); // long基本类型builder.serializerByType(BigInteger.class, ToStringSerializer.instance);// 你还可以添加自定义的序列化器和反序列化器// builder.serializerByType(YourCustomClass.class, new YourCustomSerializer());// builder.deserializerByType(YourCustomClass.class, new YourCustomDeserializer());};}// 如果有多个Customizer,可以使用@Order指定顺序// @Bean// @Order(Ordered.HIGHEST_PRECEDENCE)// public Jackson2ObjectMapperBuilderCustomizer anotherCustomJackson() {// return builder -> {// // ...// };// }
}
Spring是怎么处理事务的?
可以看这篇Spring事务全面解析。
Spring Boot的“约定大于配置”是如何实现的?
Spring Boot 的“约定大于配置”(Convention over Configuration)是一种设计理念,旨在减少开发人员需要做出的决策数量,从而简化开发流程、提高开发效率。
其通过Starters引入一组完成特定功能所需的经过测试和版本兼容的依赖,Starters里面会包含默认bean。
通过自动配置,Spring Boot 会在 classpath 中查找特定的库,并根据找到的库来自动配置你的应用程序。条件化配置@ConditionalOn…在其中发挥了重要作用。
SPI 机制 (通过 spring.factories 或Spring Boot 2.7+ 及 3.x+开始的新的 .imports 文件) 是自动配置用来发现和加载候选配置类的重要手段。Spring Boot 读取这些文件,找到所有潜在的自动配置类,然后通过条件注解(@ConditionalOn…)来决定哪些配置实际生效。
还有就是配置项的合理默认值。Spring Boot 为许多配置项提供了合理的默认值。例如:
- 内嵌 Tomcat 服务器默认监听 8080 端口。
- 静态资源默认从 classpath:/static/, classpath:/public/, classpath:/resources/, classpath:/META-INF/resources/ 等路径提供。
- 日志默认使用 Logback,并配置了控制台输出。
Spring Boot的自动配置是如何发生的?
SpringBoot的启动注解@SpringBootApplication是一个复合注解,其包含@EnableAutoConfiguration。
@EnableAutoConfiguration它通过 @Import(AutoConfigurationImportSelector.class) 导入了一个特殊的 ImportSelector。
AutoConfigurationImportSelector的核心方法是selectImports。selectImports的主要职责是:
- 收集候选的自动配置类 (Candidate Configurations):
- 它会去扫描classpath下所有jar包中的 META-INF/spring.factories 文件 (Spring Boot 2.7以前) 或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件 (Spring Boot 2.7+及3.x)。
- 这些文件列出了所有可用的自动配置类的全限定名,以 org.springframework.boot.autoconfigure.EnableAutoConfiguration 为key。
- 例如,spring-boot-autoconfigure.jar 中的 spring.factories 文件包含了大量的自动配置类,如 DataSourceAutoConfiguration, WebMvcAutoConfiguration, JacksonAutoConfiguration 等。