神枪行动小游戏
233.1MB · 2025-10-26
【开发者必备】Spring Boot 2.7.x:WebMvcConfigurer配置手册来了(二)!
上一节介绍了configurePathMatch和configureContentNegotiation的使用方法。
这一节,我们继续了解三个配置:
configureAsyncSupportconfigureDefaultServletHandlingaddFormatters在学习配置的时候,很多配置其实都是框架预留的钩子或者为了兼容老的项目而设置的。正常使用SpringBoot项目时,可能都用不上。
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
作用:配置 Spring MVC 的异步请求处理。
使用场景:
配置的详情取决于AsyncSupportConfigurer能够做什么?我们来看看他的源码:
从配置中我们可以看到最多也就操作图中的方法。方法中还需要设置一个TaskExecutor也就是我们所说的线程池。
@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";
}
});
}
配置项里面最重要的就是注册Callablel拦截器,而Callable拦截器是针对项目中方法返回值是Callable<T>的所有的方法。无论你的方法写在控制层还是服务层,都会生效。
handleTimeout和handleError的返回值是影响方法的的返回的,可以做到服务降级。
@GetMapping("/fooTest")
public Callable<String> fooTest() {
return () -> {
try {
Thread.sleep(3000);
log.info("睡眠3s后, 返回数据 ...");
} catch (InterruptedException e) {
// throw new RuntimeException(e);
}
return "success";
};
}
从执行的结果来看:
preProcess和postProcess使用了配置的线程。返回的结果因为超时,将success变成了test,实现了类似服务降级的效果。
简单来说,就是一个拦截器,拦截器能做的,他都能做。只不过拦截的方法不同而已。
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
主要作用就是启用默认Servlet 处理或者指定默认的Servlet名称。
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 启用默认 Servlet 处理
// configurer.enable(“default”);
// 指定自定义的默认 Servlet 名称
configurer.enable("customDefaultServlet");
}
在 Spring Boot 中,通常不需要显式启用默认 Servlet,因为自动配置已经处理了大部分情况。
有兴趣的朋友可以深挖一下呀。
作用:添加自定义格式化器,用于参数绑定和显示。
default void addFormatters(FormatterRegistry registry) {
}
FormatterRegistry主要用来注册Formatter的,而Formatter继承自Printer<T>和Parser<T>。
当然也可以单独设置Printer<T>和Parser<T>.
Parser我们先来注册一个日期解析器,将字符串转化成日期。这个当然在前面的文章中通过@InitBinder注解来实现,如图:
我们今天换一种方式实现,先定义一个Parse。
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");
}
}
配置
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addParser(new DateParse());
}
效果
对比未加配置之前接受日期类型的报错:
当然框架为我们提供了现成的DateFormatter,我们直接使用即可。
PrinterParser用来解析参数,而Printer接口顾名思义,用户参数的展示。Printer用起来不在顺手,需要结合Parser一起使用。所以我们要直接定义Formatter。
下面来实现一个手机号脱敏的效果:
@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);
}
}
这里需要主要的是:
Order并没有实际的意义,相当于一个临时的容器,用来管理参数parse用来接收数据,而print用来输出数据配置
@Autowired
OrderFormatter orderFormatter;
@Override
public void addFormatters(FormatterRegistry registry) {
// registry.addParser(new DateParse());
registry.addFormatterForFieldType(String.class, orderFormatter);
}
这里的String.class表示处理String类型的参数。
案例
@GetMapping("/testFormatter")
public void testFormatter(Date date, String test) {
log.info("testFormatter param: date={}, test={}", date, test);
}
源代码跟下来,parser解析完成之后才会到this.conversionService.convert(),该方法进入就是进入Print方法。
这路要注意的是:并不是所有的方法都会走Print方法。解析的结果类型和目标类型不一致时,才会触发此方法。
如案例,String类型被解析成Order类型,而目标类型又是String,所以才会生效。
这里的Print大多数不需要配置,因为这里类似序列化,而序列化有框架自带的序列化工具,可能会忽略这里的配置。在使用此方法时,需要好好测试。有更好用的方法,评论区讨论。
每一个方法深挖都会有很对子方法,在学习这些方法时,先搞清楚大方向,然后慢慢深入。这些方法的日常使用可能仅仅只配置一次,后续再也不会去改动了。很容易忘记。