前几天,在项目上线时遭遇了 No converter found for return value of type: class java.util.LinkedHashMap 异常,异常的糟心唉。。。
场景重现:
return 基本类型木有问题,但是return 对象的时候就报出了该问题,
网上答案汇总:
1、缺失Jackson 依赖,如果有 spring-boot-starter-web
依赖可排除该问题
2、是否是 POJO (get set 方法是否有)
注
: 我使用lombok插件,同样排除了此项可能性
3、注解是否是 @RestController
以上可能性完全排除,由于上线紧急,暂时采用了
HttpServletReponse.getWriter().print()
的方式
昨天在朋友帮助下,推测出是Swagger2 的问题,便没有在继续进行下去,然而一小时前采用一个demo进行测试排查是,发现并不是,继续昨天的思路,问题可能出现在拦截器那里,于是乎,开始从拦截器分析,就在刚刚,终于分析出了问题所在…
— 2018/11/07 凌晨 00:30 重新改写,(上周,今晚抽空改写)在一位网友(微信昵称 rookie_cc )的帮助下发现之前的文章存在巨大漏洞
问题出现原因:
extends WebMvcConfigurationSupport
,这种方式会屏蔽springboot @EnableAutoConfiguration
中的设置
我的源码是这样的:
/**
* 国际化默认语言配置及编码方式修改
* @author fxbin
* @version v1.0
* @since 2018/10/23 14:38
*/
@Configuration
public class I18nConfig extends WebMvcConfigurationSupport {
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
//localeResolver.setCookieName("lang");
//设置默认区域
localeResolver.setDefaultLocale(new Locale("en", "US"));
//设置cookie有效期.
localeResolver.setCookieMaxAge(-1);
return localeResolver;
}
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
return converter;
}
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
/**
* 解决SpringBoot2.X 加入拦截器后swagger2不能访问问题
* @author fxbin
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor())
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
查看Spring Boot启动WebMVC自动配置的条件发现 :
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,只有当容器中没有WebMvcConfigurationSupport这个类型的组件的时候,才会启动自动配置。
所以当我们继承WebMvcConfigurationSupport
之后,除非在继承类中重写了一系列有关WebMVC的配置,否则可能就会遇到静态资源访问不到,返回数据不成功这些一系列问题了。
鉴于SpringBoot2.X 中 WebMvcConfigurerAdapter
已经过时,所以可以实现 WebMvcConfigurer
接口来达到相同的目的
我的实现代码如下
/**
* 国际化默认语言配置及编码方式修改
* @author fxbin
* @version v1.1
* @since 2018/8/23 14:38
* @see org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
*/
@Configuration
public class I18nConfig implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
//localeResolver.setCookieName("lang");
//设置默认区域
localeResolver.setDefaultLocale(new Locale("en", "US"));
//设置cookie有效期.
localeResolver.setCookieMaxAge(-1);
return localeResolver;
}
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(responseBodyConverter());
}
}
总结:
1、@EnableWebMvc=WebMvcConfigurationSupport,使用了@EnableWebMvc注解等于扩展了WebMvcConfigurationSupport但是没有重写任何方法。@EnableWebMvc
和WebMvcConfigurationSupport
二者只能选一个,如果同时实现,则会失效 (感谢一位网友热心帮助,开始忽略了这个问题)
2、继承WebMvcConfigurationSupport
, 需要重写一系列有关WebMVC的配置,否则可能就会遇到静态资源访问不到,返回数据不成功这些一系列问题了(我就是掉在此坑),
因为springboot已经实例化了WebMvcConfigurationSupport
, 如果使用@EnableWebMvc
注解或者继承WebMvcConfigurationSupport
会覆盖默认实现, 一般建议还是不覆盖默认的好
3、实现WebMvcConfigurer
接口
示例:
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
/**
* 实现WebMvcConfigurer接口的部分示例...
*
* @author fxbin
* @version v1.0
* @since 2018/11/7 1:21
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 添加类型转换器和格式化器
* @author fxbin
* @param registry
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldType(LocalDate.class, new DateFormatter());
}
/**
* 跨域支持
* @author fxbin
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600 * 24);
}
/**
* 添加静态资源--过滤swagger-api (开源的在线API文档)
* @author fxbin
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//过滤swagger
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("/swagger-resources/**")
.addResourceLocations("classpath:/META-INF/resources/swagger-resources/");
registry.addResourceHandler("/swagger/**")
.addResourceLocations("classpath:/META-INF/resources/swagger*");
registry.addResourceHandler("/v2/api-docs/**")
.addResourceLocations("classpath:/META-INF/resources/v2/api-docs/");
}
/**
* 配置消息转换器--这里我用的是alibaba 开源的 fastjson
* @author fxbin
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//1.需要定义一个convert转换消息的对象;
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
//2.添加fastJson的配置信息,比如:是否要格式化返回的json数据;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteDateUseDateFormat);
//3处理中文乱码问题
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
//4.在convert中添加配置信息.
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
//5.将convert添加到converters当中.
converters.add(fastJsonHttpMessageConverter);
}
/**
* 添加自定义的拦截器
* @author fxbin
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
/**
* 拦截器
* @author fxbin
* @version v1.0
* @since 2018/11/7 1:25
*/
class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor preHandler method is running !");
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor postHandler method is running !");
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor afterCompletion method is running !");
super.afterCompletion(request, response, handler, ex);
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor afterConcurrentHandlingStarted method is running !");
super.afterConcurrentHandlingStarted(request, response, handler);
}
}
— end —
如有问题,请在评论区留言交流,谢谢