1. 程式人生 > >SpringBoot2.X 遭遇 No converter found for return value of type: class java.util.LinkedHashMap

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但是沒有重寫任何方法。@EnableWebMvcWebMvcConfigurationSupport 二者只能選一個,如果同時實現,則會失效 (感謝一位網友熱心幫助,開始忽略了這個問題)
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 —

如有問題,請在評論區留言交流,謝謝