文章目录

  • WebMvcConfigurer主要方法
  • 路径匹配规则
  • 内容协商策略
  • 异步调用支持
  • 静态资源默认处理器
  • 格式化器和转换器
  • 拦截器
  • 静态资源处理器
  • 跨域设置
  • 视图控制器
  • 视图解析器
  • 参数解析器
  • 返回值处理器
  • 信息转化器
  • 信息转化器扩展
  • 异常处理器
  • 异常处理器扩展



WebMvcConfigurer配置接口是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制。接口提供了很多方法让我们来定制SpringMVC的配置。可以用来自定义处理器、拦截器、视图解析器、转换器、设置跨域等。

  • SpringBoot1.5版本前都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等。
  • SpringBoot2.0版本后,WebMvcConfigurerAdapter类被标记为@Deprecated。推荐下面两种方式:
  • 实现WebMvcConfigurer接口(推荐);
  • 继承WebMvcConfigurationSupport类。

WebMvcConfigurer主要方法

路径匹配规则

configurePathMatch(PathMatchConfigurer configurer);

@Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // 设置是否模糊匹配,默认真。例如/user是否匹配/user.*。如果真,也就是说"/user.html"的请求会被"/user"的Controller所拦截。
        configurer.setUseSuffixPatternMatch(false);
        // 设置是否自动后缀模式匹配,默认真。如/user是否匹配/user/。如果真,也就是说, "/user"和"/user/"都会匹配到"/user"的Controller。
        configurer.setUseTrailingSlashMatch(true);
    }

内容协商策略

configureContentNegotiation(ContentNegotiationConfigurer configurer);

@Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 自定义策略
        configurer.favorPathExtension(true)// 是否通过请求Url的扩展名来决定mediaType,默认true
                .ignoreAcceptHeader(true)// 不检查Accept请求头
                .parameterName("mediaType")
                .defaultContentType(MediaType.TEXT_HTML)// 设置默认的MediaType
                .mediaType("html", MediaType.TEXT_HTML)// 请求以.html结尾的会被当成MediaType.TEXT_HTML
                .mediaType("json", MediaType.APPLICATION_JSON)// 请求以.json结尾的会被当成MediaType.APPLICATION_JSON
                .mediaType("xml", MediaType.APPLICATION_ATOM_XML);// 请求以.xml结尾的会被当成MediaType.APPLICATION_ATOM_XML

        // 或者下面这种写法
        Map<String, MediaType> map = new HashMap<>();
        map.put("html", MediaType.TEXT_HTML);
        map.put("json", MediaType.APPLICATION_JSON);
        map.put("xml", MediaType.APPLICATION_ATOM_XML);
        // 指定基于参数的解析类型
        ParameterContentNegotiationStrategy negotiationStrategy = new ParameterContentNegotiationStrategy(map);
        // 指定基于请求头的解析
        configurer.strategies(Arrays.asList(negotiationStrategy));
    }

异步调用支持

configureAsyncSupport(AsyncSupportConfigurer configurer);

@Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // 注册callable拦截器
        configurer.registerCallableInterceptors(timeoutInterceptor());
        // 注册deferredResult拦截器
        configurer.registerDeferredResultInterceptors();
        // 异步请求超时时间
        configurer.setDefaultTimeout(1000);
        // 设定异步请求线程池callable等, spring默认线程不可重用
        configurer.setTaskExecutor(new ThreadPoolTaskExecutor());
    }

    @Bean
    public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }

测试接口

@GetMapping("test1")
    public Callable<String> test1() {
        Callable<String> callable = () -> {
            Thread.sleep(60000);
            return "test";
        };
        return callable;
    }

静态资源默认处理器

configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);

@Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
        configurer.enable("defaultServletName");
    }

此时会注册一个默认的Handler:DefaultServerHttpRequestHandler,这个Handler也会用来处理静态文件的,它会尝试映射/*。当DispatcherServlet映射/时(/ 和/*是有区别的),并且没有找到合适的Handler来处理请求时,就会交给DefaultServletHttpRequestHandler来处理。注意:这里的静态资源是放置在web根目录下,而非WEB_INF下。

举例说明:在webroot目录下有一个图片a.png,我们知道Servelt规范中web根目录webroot下的文件可以直接访问的,但是由于DispatcherServlet配置了映射路径是:/,它几乎把所有的请求都拦截了,从而导致a.png访问不到,这时注册一个DefaultServletHttpRequestHandler就可以解决这个问题,其实可以理解为DispatchServlet破坏了Servler的一个特性(就是根目录下的文件可以直接访问),DefaultServletHttpRequestHandler是帮助回归这个特性的。

格式化器和转换器

addFormatters(FormatterRegistry registry);

@Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(booleanFormatter());// 布尔格式化器
        registry.addConverter(stringToDateConverter());// 字符串转日期转化器
    }

    @Bean
    BooleanFormatter booleanFormatter() {
        return new BooleanFormatter();
    }

    @Bean
    StringToDateConverter stringToDateConverter() {
        return new StringToDateConverter();
    }

StringToDateConverter类

public class StringToDateConverter implements Converter<String, Date> {
    private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
    private static final String shortDateFormat = "yyyy-MM-dd";
    private static final String dateFormat2 = "yyyy/MM/dd HH:mm:ss";
    private static final String shortDateFormat2 = "yyyy/MM/dd";

    @Override
    public Date convert(String source) {
        if (StrUtil.isBlank(source)) {
            return null;
        }
        source = source.trim();
        try {
            SimpleDateFormat formatter;
            if (source.contains("-")) {
                if (source.contains(":")) {
                    formatter = new SimpleDateFormat(dateFormat);
                } else {
                    formatter = new SimpleDateFormat(shortDateFormat);
                }
                Date dtDate = formatter.parse(source);
                return dtDate;
            } else if (source.contains("/")) {
                if (source.contains(":")) {
                    formatter = new SimpleDateFormat(dateFormat2);
                } else {
                    formatter = new SimpleDateFormat(shortDateFormat2);
                }
                Date dtDate = formatter.parse(source);
                return dtDate;
            }
        } catch (Exception e) {
            throw new RuntimeException(String.format("parser %s to Date fail", source));
        }

        throw new RuntimeException(String.format("parser %s to Date fail", source));

    }
}

BooleanFormatter类

public class BooleanFormatter implements Formatter<Boolean> {

    private String[] trueTag = {"true", "1", "是", "有"};


    @Override
    public Boolean parse(String s, Locale locale) {
        return Arrays.asList(trueTag).contains(s);
    }

    @Override
    public String print(Boolean aBoolean, Locale locale) {
        return aBoolean ? "true" : "false";
    }
}

测试j

@GetMapping("test2")
    public String test2(@RequestParam Date date, @RequestParam Boolean bo) {
        System.out.println("date:"+date);
        System.out.println("bo:"+bo);
        return "success";
    }

请求

http://127.0.0.1:8888/test2?date=2022-02-22 11:22:33&bo=有

日志

date:Tue Feb 22 11:22:33 CST 2022
bo:true

拦截器

addInterceptors(InterceptorRegistry registry);

@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor()).addPathPatterns("/test3").excludePathPatterns("/test4");
    }

    @Bean
    MyInterceptor myInterceptor() {
        return new MyInterceptor();
    }

MyInterceptor类

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("====== MyInterceptor.preHandle ======");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

测试

@GetMapping("test3")
    public String test3() {
        return "success";
    }

    @GetMapping("test4")
    public String test4() {
        return "success";
    }

请求1

http://127.0.0.1:8888/test3

日志1

====== MyInterceptor.preHandle ======

请求2

http://127.0.0.1:8888/test4

日志2

静态资源处理器

addResourceHandlers(ResourceHandlerRegistry registry);

@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/my/**").addResourceLocations("classpath:");
    }

spring boot 配置spring mvc springboot mvcconfugurer_拦截器

请求

http://127.0.0.1:8888/my/a.png
http://127.0.0.1:8888/my/aa.png
http://127.0.0.1:8888/my/aaa.png

跨域设置

addCorsMappings(CorsRegistry registry);

/**
     * 此种设置跨域的方式,在自定义拦截器的情况下可能导致跨域失效
     * 原因:当跨越请求在跨域请求拦截器之前的拦截器处理时就异常返回了,那么响应的response报文头部关于跨域允许的信息就没有被正确设置,导致浏览器认为服务不允许跨域,而造成错误。
     * 解决:自定义跨域过滤器解决跨域问题(该过滤器最好放在其他过滤器之前)
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
                .allowCredentials(true)
                .maxAge(3600);
    }

跨域失效

  • 使用addCorsMappings对跨域相关配置,然后再使用了自定义拦截器,可能会导致跨域配置失效。

原因解析

主要是因为请求经过的先后顺序问题,当请求到来时会先进入拦截器中,而不是进入Mapping映射中,所以返回的头信息中并没有配置的跨域信息。浏览器就会报跨域异常。

在springMvc中,路径的映射匹配是通过DispatcherServlet这个类来实现的,最终的函数执行在doDispatch这个方法中。

doDispatch方法的具体步骤:

  1. 根据请求request获取执行器链(包括拦截器和最终执行方法Handler);
    mappedHandler = getHandler(processedRequest);
    getHandler方法的具体步骤:
  1. 获取request所需执行的handler;
    Object handler = getHandlerInternal(request);
  2. 获取执行器链(把具体的执行器和整个拦截器链组成一个链队形,方便后续执行);
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  3. 获取跨域相关执行器链;
  • request是预检请求,重新生成一个执行器链,包含一个预检执行器(PreFlightHandler)和原执执行器链的拦截器链,最终在doDispatch方法的第4步骤中执行;
  • request不是预检请求,生成一个跨域拦截器加入拦截器链中,最终在doDispatch的第3步骤中执行。

关于什么是预检请求可另行了解,本文不做解释。

  1. 根据Handler获取handlerAdapter;
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  2. 执行执行器链中的拦截方法(preHandle);
    mappedHandler.applyPreHandle(processedRequest, response)
  3. 执行handler方法;
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  4. 执行执行器链中的拦截方法(postHandle);
    mappedHandler.applyPostHandle(processedRequest, response, mv);

因为拦截器是顺序执行的,如果前面执行失败异常返回后,后面的拦截器则不再执行。

所以当跨越请求在跨域请求拦截器之前的拦截器处理时就异常返回了,那么响应的response报文头部关于跨域允许的信息就没有被正确设置,导致浏览器认为服务不允许跨域,而造成错误。

解决办法

使用过滤器设置跨域允许信息,因为过滤器先于拦截器执行,所以无论是否被拦截,始终已经有允许跨域的头部信息,就不会出问题了(特别注意:自定义的跨域处理过滤器尽量放到其他过滤器前面,避免其他过滤器执行异常,导致跨域处理过滤器没执行,从而导致跨域失效)。

@Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        // 跨域配置
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "HEAD", "DELETE", "OPTION")));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);

        // 有多个filter时此处可设置改CorsFilter的优先执行顺序,保证CorsFilter在其他过滤器之前执行(避免其他过滤器执行异常,导致CorsFilter没执行,从而导致跨域失效)
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }

视图控制器

addViewControllers(ViewControllerRegistry registry);

@Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // /test5的请求重定向到/test4
        registry.addRedirectViewController("/test5", "/test4");
        // /test/**的请求返回500状态
        registry.addStatusController("/test/**", HttpStatus.INTERNAL_SERVER_ERROR);
        // /home的请求响应为返回home视图
        registry.addViewController("/home").setViewName("home");
    }

测试接口

@GetMapping("test4")
    public String test4() {
        return "test4";
    }

请求1

http://127.0.0.1:8888/test5

返回1

test4

请求2

http://127.0.0.1:8888/test/test

结果

spring boot 配置spring mvc springboot mvcconfugurer_java_02

视图解析器

configureViewResolvers(ViewResolverRegistry registry);

@Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.viewResolver(internalResourceViewResolver());
    }

    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        //请求视图文件的前缀地址
        internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
        //请求视图文件的后缀
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
    }

参数解析器

addArgumentResolvers(List resolvers);

@Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(currentUserMethodArgumentResolver());
    }

    @Bean
    CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
        return new CurrentUserMethodArgumentResolver();
    }

自定义解析器CurrentUserMethodArgumentResolver类

public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 这里可以从方法参数对象中获取到参数类型、参数的注解类型等,具体可研究MethodParameter类
        return parameter.getParameterType().isAssignableFrom(User.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        // 解析出用户信息
        // 例如:通过jwt方式解析出token信息
        // 例如:通过userId查询出用户信息
        // 这里模拟解析过程
        User user = new User();
        user.setId("123456");
        return user;
    }
}

User类

@Data
public class User implements Serializable {
    private static final long serialVersionUID = -6525322309638123441L;
    private String id;
    private String userName;
    private String passWord;
    private String email;
    private String nickName;
    private Date createTime;
}

测试接口

@GetMapping("test5")
    public User test5(User user) {
        return user;
    }

请求

http://127.0.0.1:8888/test5

响应

{
    "id": "123456",
    "userName": null,
    "passWord": null,
    "email": null,
    "nickName": null,
    "createTime": null
}

返回值处理器

addReturnValueHandlers(List handlers);

@Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        handlers.add(myReturnValueHandler());
    }

    @Bean
    MyReturnValueHandler myReturnValueHandler() {
        return new MyReturnValueHandler();
    }

MyReturnValueHandler类

public class MyReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter methodParameter) {
        return methodParameter.hasMethodAnnotation(ResponseData.class);
    }

    @Override
    public void handleReturnValue(Object o, MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest) throws Exception {
        // .setRequestHandled(true)表示此函数可以处理请求,不必交给别的代码处理
        // o 响应对象信息
        modelAndViewContainer.setRequestHandled(true);
        nativeWebRequest
            .getNativeResponse(HttpServletResponse.class)
            .getWriter()
            .write("1111");
    }
}

ResponseData注解

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseData {

}

测试接口

@GetMapping("test6")
    @ResponseData
    public String test6() {
        return "test6";
    }

请求

http://127.0.0.1:8888/test6

测试发现,自定义的返回值处理器MyReturnValueHandler并没有生效

失效原因

返回处理器执行时机:

  • DispatcherServlet.doService方法
  • 进入:DispatcherServlet.doDispatch方法
  • 进入:AbstractHandlerMethodAdapter.handle方法
  • 进入:AbstractHandlerMethodAdapter.handleInternal方法
  • 进入:RequestMappingHandlerAdapter.handleInternal方法
  • 进入:RequestMappingHandlerAdapter.invokeHandlerMethod方法
  • 进入:ServletInvocableHandlerMethod.invokeAndHandle方法
  • 进入:HandlerMethodReturnValueHandlerComposite.handleReturnValue方法
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

该方法有两个步骤:

  1. HandlerMethodReturnValueHandlerComposite.selectHandler方法
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}

该方法遍历返回值处理器集合returnValueHandlers,通过每个返回值处理器HandlerMethodReturnValueHandler.supportsReturnType方法判断该返回值处理器是否支持,得到第一个支持的返回值处理器;

spring boot 配置spring mvc springboot mvcconfugurer_spring boot_03

  1. HandlerMethodReturnValueHandler.handleReturnValue方法
    调用该返回值处理器的handleReturnValue方法执行返回值处理逻辑。

测试接口没执行自定义的MyReturnValueHandler的具体原因就是,因为该测试接口的控制器类上定义了@RestController注解,该注解包含@ResponseBody注解。而@ResponseBody注解对应支持的返回值处理器是集合中的第12个返回值处理器RequestResponseBodyMethodProcessor。所以最终执行的是这个返回值处理器的逻辑。MyReturnValueHandler因为排在RequestResponseBodyMethodProcessor的后面。导致没生效。

解决方法
返回值处理器集合定义在RequestMappingHandlerAdapter中。将自定义的返回值处理器MyReturnValueHandler排在RequestResponseBodyMethodProcessor前面。

特别注意:这样其实就修改了返回值处理器集合的内部顺序,可能会对源程序有影响,请谨慎使用。

方式1:
**实现ApplicationContextAware接口,重写setApplicationContext方法。**通过ApplicationContext得到RequestMappingHandlerAdapter,然后自定义排序。

在这里插入代码片
@Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RequestMappingHandlerAdapter requestMappingHandlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
        List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
        returnValueHandlers.add(myReturnValueHandler());
        returnValueHandlers.addAll(requestMappingHandlerAdapter.getReturnValueHandlers());
        requestMappingHandlerAdapter.setReturnValueHandlers(returnValueHandlers);
    }

方式2:
直接注入RequestMappingHandlerAdapter,然后自定义排序。

@Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    
    @PostConstruct
    public void addReturnValueHandler() {
        List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
        returnValueHandlers.add(myReturnValueHandler());
        returnValueHandlers.addAll(requestMappingHandlerAdapter.getReturnValueHandlers());
        requestMappingHandlerAdapter.setReturnValueHandlers(returnValueHandlers);
    }

注意需要加@PostConstruct注解。该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

信息转化器

configureMessageConverters(List<HttpMessageConverter<?>> converters);

参数解析器和返回值处理器中使用到的request和response的body类型转换器HttpMessageConverter。该种方式添加自定义转化器会覆盖默认转换器。

如果未添加转换器,则会注册转换器的默认列表。请注意,向列表中添加转换器将关闭默认转换器注册。
源码解析:

protected final List<HttpMessageConverter<?>> getMessageConverters() {
		if (this.messageConverters == null) {
			this.messageConverters = new ArrayList<>();
			//添加WebMvcConfigurer接口实现类configureMessageConvertersfang方法增加的自定义转换器(使默认转换器失效)
			configureMessageConverters(this.messageConverters);
			//存在自定义转换器,则默认不再添加默认转换器
			if (this.messageConverters.isEmpty()) {
				//添加默认的转换器(一共8个)
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			//添加WebMvcConfigurer接口实现类extendMessageConverters方法增加的自定义转换器(在默认的基础上新增)
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}
@Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();

        // Long转String,因为js中得数字类型不能包含所有的java long值
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);

        // Date转String
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(0, jackson2HttpMessageConverter);
    }

转换器失效
通过configureMessageConverters自定义的信息转换器可能并没有起作用。

产生原因

自定义的转化器放在默认转换器的后面,如果使用匹配上了默认的转换器,则自定义转化器不会被使用。

spring boot 配置spring mvc springboot mvcconfugurer_spring boot_04

解决方法

将自定义转化器放到默认转换器前面。

spring boot 配置spring mvc springboot mvcconfugurer_跨域_05

信息转化器扩展

extendMessageConverters(List<HttpMessageConverter<?>> converters);

参数解析器和返回值处理器中使用到的request和response的body类型转换器HttpMessageConverter。该种方式添加自定义转化器不会覆盖默认转换器。

推荐使用extendMessageConverters来自定义信息转换器而不是configureMessageConverters。

public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        // Integer转String
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Integer.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Integer.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);

        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(0, jackson2HttpMessageConverter);
    }

异常处理器

configureHandlerExceptionResolvers(List resolvers);
该方法添加异常处理解析器会覆盖默认系统默认的异常处理解析器。

原因见源码:

@Bean
    public HandlerExceptionResolver handlerExceptionResolver() {
        List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();
        // 添加WebMvcConfigurer接口实现类configureHandlerExceptionResolvers方法增加的异常处理器(会使默认异常处理器失效)
        configureHandlerExceptionResolvers(exceptionResolvers);
        // 存在自定义的异常处理器,则默认不再添加默认异常处理器
        if (exceptionResolvers.isEmpty()) {
            addDefaultHandlerExceptionResolvers(exceptionResolvers);
        }
        // 添加WebMvcConfigurer接口实现类extendHandlerExceptionResolvers方法增加的异常处理器(在默认的基础上新增)
        extendHandlerExceptionResolvers(exceptionResolvers);
        HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
        composite.setOrder(0);
        composite.setExceptionResolvers(exceptionResolvers);
        return composite;
    }

自定义异常处理器
继承AbstractHandlerExceptionResolver,该类实现了HandlerExceptionResolver接口

public class MyExceptionResolver extends AbstractHandlerExceptionResolver {

    /**
     * 异常解析器的顺序, 数值越小,表示优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return -999999;
    }

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("hello from MyExceptionResolver");
        try {
            response.getWriter().write(ex.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

添加自定义异常处理器

@Bean
    MyExceptionResolver myExceptionResolver() {
        return new MyExceptionResolver();
    }
    
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(myExceptionResolver());
    }

异常处理器失效
有时候你会发现自定义的异常处理器不会生效,这是为何?原因在于,异常注册器是按顺序执行的,当前一个返回的ModelAndView对象不为null时就会中断后续的异常处理器执行,而我们自定义的异常处理器在默认异常处理器之后,没执行到。

解决办法

自定义的异常处理器实现Ordered接口,重新getOrder方法(该方法是用来排序的),上面就是这种写法。

异常处理器扩展

extendHandlerExceptionResolvers(List resolvers);

该方法添加异常处理解析器不会覆盖默认系统默认的异常处理解析器。

扩展异常处理器

@Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(myExceptionResolver());
    }