【开发者必备】Spring Boot 2.7.x:WebMvcConfigurer配置手册来了(二)!

01 引言
上一节介绍了configurePathMatch
和configureContentNegotiation
的使用方法。
这一节,我们继续了解三个配置:
configureAsyncSupport
configureDefaultServletHandling
addFormatters
在学习配置的时候,很多配置其实都是框架预留的钩子或者为了兼容老的项目而设置的。正常使用SpringBoot
项目时,可能都用不上。
02 方法3
configureAsyncSupport
java
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
作用:配置 Spring MVC 的异步请求处理。
使用场景:
- 控制异步请求的超时时间
- 异步请求异常,降级处理
- 监控异步请求的耗时
2.1 配置
配置的详情取决于AsyncSupportConfigurer
能够做什么?我们来看看他的源码:

从配置中我们可以看到最多也就操作图中的方法。方法中还需要设置一个TaskExecutor
也就是我们所说的线程池。
java
@Bean
public AsyncTaskExecutor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("xxx-test-async-");
return executor;
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 超时时间
configurer.setDefaultTimeout(2000);
// 设置执行任务:也可以不设置,使用默认的
configurer.setTaskExecutor(asyncTaskExecutor());
// 注册Callable拦截器
configurer.registerCallableInterceptors(new CallableProcessingInterceptor(){
@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception {
log.info("beforeConcurrentHandling 执行了...");
}
@Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
log.info("preProcess 执行了...");
}
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception {
log.info("postProcess 执行了...");
}
@Override
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
log.info("afterCompletion 执行了...");
}
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
log.info("handleTimeout 执行了...");
return "test";
}
});
}
2.2 使用注意事项
配置项里面最重要的就是注册Callablel
拦截器,而Callable
拦截器是针对项目中方法返回值是Callable<T>
的所有的方法。无论你的方法写在控制层还是服务层,都会生效。
handleTimeout
和handleError
的返回值是影响方法的的返回的,可以做到服务降级。
2.3 拦截方法
java
@GetMapping("/fooTest")
public Callable<String> fooTest() {
return () -> {
try {
Thread.sleep(3000);
log.info("睡眠3s后, 返回数据 ...");
} catch (InterruptedException e) {
// throw new RuntimeException(e);
}
return "success";
};
}
2.4 测试

从执行的结果来看:
preProcess
和postProcess
使用了配置的线程。返回的结果因为超时,将success
变成了test
,实现了类似服务降级的效果。
简单来说,就是一个拦截器,拦截器能做的,他都能做。只不过拦截的方法不同而已。
03 方法4
configureDefaultServletHandling
java
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
主要作用就是启用默认Servlet
处理或者指定默认的Servlet
名称。
java
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 启用默认 Servlet 处理
// configurer.enable("default");
// 指定自定义的默认 Servlet 名称
configurer.enable("customDefaultServlet");
}
在 Spring Boot
中,通常不需要显式启用默认 Servlet,因为自动配置已经处理了大部分情况。
有兴趣的朋友可以深挖一下呀。
04 方法5
addFormatters
作用:添加自定义格式化器,用于参数绑定和显示。
java
default void addFormatters(FormatterRegistry registry) {
}
FormatterRegistry
主要用来注册Formatter
的,而Formatter
继承自Printer<T>
和Parser<T>
。

当然也可以单独设置Printer<T>
和Parser<T>
.

4.1 配置Parser
我们先来注册一个日期解析器,将字符串转化成日期。这个当然在前面的文章中通过@InitBinder
注解来实现,如图:

我们今天换一种方式实现,先定义一个Parse
。
java
public class DateParse implements Parser<Date> {
@Override
public Date parse(String text, Locale locale) throws ParseException {
return DateUtil.parse(text, "yyyy-MM-dd HH:mm:ss");
}
}
配置
java
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addParser(new DateParse());
}
效果

对比未加配置之前接受日期类型的报错:

当然框架为我们提供了现成的DateFormatter
,我们直接使用即可。

4.2 配置Printer
Parser
用来解析参数,而Printer
接口顾名思义,用户参数的展示。Printer
用起来不在顺手,需要结合Parser
一起使用。所以我们要直接定义Formatter
。
下面来实现一个手机号脱敏的效果:
java
@Component
public class OrderFormatter implements Formatter<Order> {
@Override
public Order parse(String text, Locale locale) throws ParseException {
return new Order(null, text);
}
@Override
public String print(Order object, Locale locale) {
return object.getTestStr().substring(0, 1) + "***" +object.getTestStr().substring(object.getTestStr().length() - 1);
}
}
这里需要主要的是:
- ① 当前类必须被Spring管理
- ② 这里的
Order
并没有实际的意义,相当于一个临时的容器,用来管理参数 - ③
parse
用来接收数据,而print
用来输出数据
配置
java
@Autowired
OrderFormatter orderFormatter;
@Override
public void addFormatters(FormatterRegistry registry) {
// registry.addParser(new DateParse());
registry.addFormatterForFieldType(String.class, orderFormatter);
}
这里的String.class
表示处理String
类型的参数。
案例
java
@GetMapping("/testFormatter")
public void testFormatter(Date date, String test) {
log.info("testFormatter param: date={}, test={}", date, test);
}

4.3 源码说明

源代码跟下来,parser
解析完成之后才会到this.conversionService.convert()
,该方法进入就是进入Print
方法。
这路要注意的是:并不是所有的方法都会走Print
方法。解析的结果类型和目标类型不一致时,才会触发此方法。
如案例,String
类型被解析成Order
类型,而目标类型又是String
,所以才会生效。
4.4 额外说明
这里的Print
大多数不需要配置,因为这里类似序列化,而序列化有框架自带的序列化工具,可能会忽略这里的配置。在使用此方法时,需要好好测试。有更好用的方法,评论区讨论。
05 小结
每一个方法深挖都会有很对子方法,在学习这些方法时,先搞清楚大方向,然后慢慢深入。这些方法的日常使用可能仅仅只配置一次,后续再也不会去改动了。很容易忘记。