SpringMVC自定義配置消息轉換器踩坑總結


問題描述

  最近在開發時候碰到一個問題,springmvc頁面向后台傳數據的時候,通常我是這樣處理的,在前台把數據打成一個json,在后台接口中使用@requestbody定義一個對象來接收,但是這次數據傳不過去,報400的錯誤,原因也很容易想到,該對象有一個屬性也是一個對象,屬性對象是用抽象類定義的,他有幾個具體實現,具體實現中的字段都是不一樣的,springmvc是不會自動識別並注入你使用的是哪一個實現類的.所以無法傳過來.

     傳遞對象如下:

@Data
public class ActivityRule {
    ...private RuleDetail ruleDetail;//注意這里的RuleDetail是一個抽象類
    ...
}

  解決方案:

  使用自定義消息轉換器,首先讓我們來了解一下spring的消息轉換器

springmvc的消息轉換器(HTTPMessageConverter)

  我們都使用過@RequestBody和@ResponseBody這兩個注解,他們的作用就是在前台向后台傳遞數據時,把請求報文中的數據通過springmvc的處理成一個我們自己定義的對象,在這個過程中首先springmvc會去請求頭中找到一個contentType的屬性,然后去匹配能夠處理這種類型的消息轉換器,而在返回數據時,再把對象轉換成響應報文.

介紹一下contentType屬性:

  contentType是requestHeader中的一個屬性,這個頭部主要用於說明body中的字符串是什么格式的,比如:text,json,xml,html等。springmvc解析請求時,首先通過此頭部,才能確定使用什么格式來解析請求體中的字符串,對於響應報文,瀏覽器也是要通過這個屬性,來確定在如何處理響應報文的返回數據。
介紹一下@RequestBody/@ResponseBody注解
  當用該注解標注一個對象時,在請求過程中進行數據映射時,spring會根據Request對象header部分的content-Type類型,逐一匹配合適的HttpMessageConverter來讀取數據,而在響應時,spring會根據Request對象header部分的Accept屬性(逗號分隔),逐一按accept中的類型,去遍歷找到能處理的HttpMessageConverter.

  到這里我們就有了一種思路,能不能讓我們來接管請求報文到對象映射這個過程,只要我們得到了json字段,根據內容我們就知道需要去映射哪個類,至此,我們有了思路就可以去實現了,我們可以通過spring的消息轉換器來實現我們的想法,

  默認情況下,spring使用HttpMessageConverter來負責求信息轉換為一個象(為 T),並且象(為 T為響應信息,如果自定義我們自己的消息轉換器,則需要新建一個類,繼承

AbstractHttpMessageConverter<T>,下面看我針對上面的ActivityRule對象定義的一個消息轉換器.

/**
 * 自定義消息轉換器 ActivityRule
 * @author yogo.wang
 * @date 2017/10/25-下午5:43.
 */
public class RuleMessageConverter extends AbstractHttpMessageConverter<ActivityRule> {

    public RuleMessageConverter(){
        super(new MediaType("application","x-rule", Charset.forName("UTF-8")));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return ActivityRule.class.isAssignableFrom(clazz);
    }

    @Override
    protected ActivityRule readInternal(Class<? extends ActivityRule> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        String temp= StreamUtils.copyToString(inputMessage.getBody(),Charset.forName("UTF-8"));
        Map<String,Object> map = (Map<String,Object>)JSON.parse(temp);
        RuleType ruleType = RuleType.valueOf((String)map.get("ruleType"));
        String ruleDetail = StringUtils.substringAfter(temp, "ruleDetail\":");
        ruleDetail=ruleDetail.substring(0,ruleDetail.length()-1);
        ActivityRule rule=new ActivityRule();
        rule.setName((String)map.get("name"));
        rule.setRuleType(ruleType);
        switch (ruleType){
            case LOGIN:
                rule.setRuleDetail(JSON.parseObject(ruleDetail, LoginRuleDetail.class));
                break;
            case ROLE_UPGRADE:
                rule.setRuleDetail(JSON.parseObject(ruleDetail, UpgradeRuleDetail.class));
                break;
            case PAY_AMOUNT:
                rule.setRuleDetail(JSON.parseObject(ruleDetail, RechargeRuleDetail.class));
                break;
            default:
                rule.setRuleDetail(null);
        }
        return rule;
    }

    @Override
    protected void writeInternal(ActivityRule activityRule, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    }
}

  在上面的類中,在繼承抽象類AbstractHttpMessageConverter時,我們將泛型指定為@RequestBody標注的了類,即ActivityRule類,然后在該類中的構造器中,我們創建了一個新的媒體類型"x-rule",名稱可以自定義,並且指定相應的編碼方式,一般都是utf-8。在重寫的support()方法中,我們來判斷所支持的Class是否與ActivityRule的Class相同,只有相同,才會走下面的方法readInternal,在這個方法里,我們就需要從請求頭里拿到json字符串,然后自己手動將json映射成對象.

  寫完這個類還沒完,還有兩步操作是必須的,第一,在spring的配置文件將消息轉換器配置上,如下:

 <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="com.ximalaya.cms.games.operation.activity.service.converter.RuleMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json</value>
                        <value>application/x-rule</value>
                    </list>
                </property>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>

   第二,在controller接口中,需要手動指定哪個接口可以接收我們自定義的媒體類型.如下:

 /**
     * 保存規則對象
     * @param rule
     * @return
     */
    @RequestMapping(method = RequestMethod.POST,produces = { "application/x-rule"})
    @ResponseBody
    public ResponseMessage save(@RequestBody ActivityRule rule) {
        LOG.info("begin to save ActivityRule:{}",rule);
        try{
            ruleService.saveAvtivityRule(rule);
            return ResponseMessage.ok();
        }catch (Exception e){
            return ResponseMessage.fail(e.getMessage());
        }
    }

  以上操作完成后,在我測試的時候,踩了兩個坑,需要特別說明一下.在我運行時,數據還是過不來,報415的錯誤,說不支持的媒體類型,后來發現在前端的Ajax調用中,發現contentType沒改,改后如下:

                    $.ajax({
                        method: $form.attr('method'),
                        traditional: true,
                        url: $form.attr('action'),
                        data: JSON.stringify(rule),
                        contentType: "application/x-rule",
                        dataType:"json",
                        success: function (ret) {
                            //,......
                        },
                        error: function (message) {
                            alert('ERROR:' + JSON.stringify(message));
                        }
                    });

 再次運行,沒問題,json字段如願映射成了我們想要的對象,但在前端返回的時候,仍然有錯誤,報406,根據網上的解決方案,說是缺少fastjson相關包,於是引入了相關jar報,還是沒解決,卡了大半天,終,修改了一下@RequestMapping()的內容,神奇的解決了問題,如下:

 /**
     * 保存規則對象
     * @param rule
     * @return
     */
    @RequestMapping(method = RequestMethod.POST,produces = { "application/x-rule","application/json"})
    @ResponseBody
    public ResponseMessage save(@RequestBody ActivityRule rule) {
        LOG.info("begin to save ActivityRule:{}",rule);
        try{
            ruleService.saveAvtivityRule(rule);
            return ResponseMessage.ok();
        }catch (Exception e){
            return ResponseMessage.fail(e.getMessage());
        }
    }

  我猜測,原因可能是這樣的,由於設置了@ResponseBody,要把對象轉換成json格式,但是注意看我的代碼,@ResponseBody標注的類是ResponseMessage,不是ActivityRule!!!!,而coontentType被我設成了application/x-rule,所以在返回的時候,仍然走了我自定義的那個消息轉換器,而兩個類肯定是不同的,support返回了false,而我添加了application/json以后,ResponseMessage對象就沒有走我自定義的消息轉換器,而是以json的contentType進入了spring的默認消息轉換器,並且成功映射到響應體中.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM