解决Long类型到web端失精度引发的API接口空响应

事故代码
@Configuration
public class LongToStringJsonConfig extends WebMvcConfigurationSupport {

    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(jackson2HttpMessageConverter);
    }
}
  • 我最开始项目里,单纯解决Long转String需求时,写的是如上代码。好吧,诚实一点,抄的是以上代码。然而这段代码导致我所有的接口,都是空响应。
触发的问题点
  • 1,Spring的request和response的stream只能读一次,再次读取的时候,stream就为空了。
  • 2,通过debug,项目引入了一个底层包,经过了一个filter。而这个filter做了一件事:
        ContentCachingResponseWrapper wrapperResponse = new ContentCachingResponseWrapper(response);
        chain.doFilter(wrapperRequest, wrapperResponse);
  • filter将response包入到了ContentCachingResponseWrapper里边
  • 3,在底层包里,后续有一个interceptor 在afterCompletion方法中,会将ContentCachingResponseWrapper的内容写出去。
  • 4,再次debug,发现写出ContentCachingResponseWrapper的interceptor并未被注入。
解决空响应
  • 最开始的尝试chain.doFilter(wrapperRequest, wrapperResponse);将filter的这行操作,换成chain.doFilter(wrapperRequest, response);此时,接口正常响应。问题是:我项目中的空响应解决了,但底层包里interceptor想要拦截读取一些信息记录的需求实现就被破坏了。PS:虽然我连这个interceptor都没注册上吧。 所以,这个方案,是行不通的。 问题又指向于:为什么没有触发后续的interceptor去调用responseWrapper.copyBodyToResponse()方法写出响应。
  • 慢慢回顾,发现就是多了我新增的一个类,就出现空响应。去掉的话,就是正常响应。仔细看,我的类没什么特别,唯一特别的就是 extends WebMvcConfigurationSupport。 那么WebMvcConfigurationSupport被继承,会发生啥呢?
为什么写出response的拦截器没注册上
  • 我的config代码唯一的不通,就是继承了WebMvcConfigurationSupport。那么这个WebMvcConfigurationSupport是怎么工作的呢?
  • 仔细查代码,或者debug,就可以发现,在WebMvcConfigurationSupport里有个接口获取拦截器:
	/**
	 * Provide access to the shared handler interceptors used to configure
	 * {@link HandlerMapping} instances with.
	 * <p>This method cannot be overridden; use {@link #addInterceptors} instead.
	 */
	protected final Object[] getInterceptors() {
		if (this.interceptors == null) {
			InterceptorRegistry registry = new InterceptorRegistry();
			addInterceptors(registry);
			registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
			registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
			this.interceptors = registry.getInterceptors();
		}
		return this.interceptors.toArray();
	}
  • 这个接口不可以被重写。而中间有个addInterceptors(registry)方法,所以,interceptor要注册上,至少也得是实现了这个addInterceptors(registry)方法的。 PS:这里还可以看一下WebMvcConfigurationSupport类的作用说明:

This is the main class providing the configuration behind the MVC Java config. It is typically imported by adding {@link EnableWebMvc @EnableWebMvc} to an application {@link Configuration @Configuration} class. An alternative more advanced option is to extend directly from this class and override methods as necessary, remembering to add {@link Configuration @Configuration} to the subclass and {@link Bean @Bean} to overridden {@link Bean @Bean} methods.

  • 在WebMvcConfigurationSupport里,可以看到的是It is typically imported by adding {@link EnableWebMvc @EnableWebMvc} to an application {@link Configuration @Configuration} class。所以,第一个解决拦截器未注册上的方案,也就出来了。就是在我的类上,除了@Configuration外,再加一个注解@EnableWebMvc。 至此,空响应问题解决。第二个解决方案,在我的类中,实现addInterceptors(registry)的方法注入底层包里可以打印出response的interceptor。
  • 通过WebMvcConfigurationSupport 找到@EnableWebMvc和接口WebMvcConfigurer。仔细看WebMvcConfigurer接口的定义。第三个解决方案出来了,让我的类不再继承WebMvcConfigurationSupport类,而改为implement WebMvcConfigurer。
添加@EnableWebMvc和implement WebMvcConfigurer的区别
  • 区别主要从WebMvcConfigurer的addInterceptors(InterceptorRegistry registry)的方法声明定义中可以看出:

Add Spring MVC lifecycle interceptors for pre- and post-processing of controller method invocations. Interceptors can be registered to apply to all requests or be limited to a subset of URL patterns.

Note that interceptors registered here only apply to controllers and not to resource handler requests.To intercept requests for static resources either declare a {@link org.springframework.web.servlet.handler.MappedInterceptor MappedInterceptor} bean or switch to advanced configuration mode by extending {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport WebMvcConfigurationSupport} and then override {@code resourceHandlerMapping}.

  • 通过WebMvcConfigurer的方式,无法拦截静态资源,但可以通过其他方式进行扩展实现。
为什么单纯extends WebMvcConfigurationSupport会引发拦截器未注册
  • 在@SpringBootApplication中,会引入@EnableAutoConfiguration,在什么都不做的时候,指:即不加@EnableWebMvc也不搞implement WebMvcConfigurer的时候,那些拦截器是怎么注册的呢?
  • 通过@EnableWebMvc找到了如下类:
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
  • 可以看到假如没有自定义的WebMvcConfigurationSupport类,类WebMvcAutoConfiguration就会执行。在此类中通过extends DelegatingWebMvcConfiguration ,再有DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport,在DelegatingWebMvcConfiguration类中,实现了addInterceptors(registry)方法。
  • 所以,关键就是WebMvcConfigurationSupport类中的addInterceptors(registry)一定要有实现。如果不继承此类,那么有默认的实现方式,如果用户自己实现类此类,就需要处理addInterceptors(registry)的实现。
其他
  • 我准备再搞搞Spring boot的自动装配,为啥@Configuration一加就妥了呢
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值