SpringBoot2.X 遭遇 No converter found for return value of type: class java.util.LinkedHashMap
前幾天,在專案上線時遭遇了 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 —
如有問題,請在評論區留言交流,謝謝