Spring MVCでJSONパラメータ受信時の415 Unsupported Media Typeエラーの解決方法

MavenプロジェクトでSpring MVCを使用する際、XMLベースの設定を行いました。DispatcherServletの設定ファイルではannotation-drivenを以下のように設定していました。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <description>Spring設定ファイル</description>
    
    <mvc:annotation-driven />

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <context:component-scan base-package="com.example.spring.controller"
        use-default-filters="false">
        <context:include-filter type="annotation"
            expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
</beans>

バックエンドのインターフェース定義は以下の通りです:

    @RequestMapping("api/test")
    @ResponseBody
    public String handleJsonRequest(@RequestBody UserProfile userProfile) throws Exception {
        System.out.println(userProfile);
        return "SUCCESS";
    }

UserProfile.javaクラス定義

package com.example.spring.model;

public class UserProfile {

    private int userId;
    private String accountName;
    
    private String userPassword;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

}

フロントエンドからのリクエスト方法:

    $("#sendData").click(function () {
        var userData={
            userId:1,
            accountName:"testuser"
        };
        $.ajax({
            url : "/api/test",
            type : "POST",
            dataType : "json",
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(userData),
            success:function (response) {
                alert(response)
            }
        });
    })

しかし、リクエストは成功せず、以下のようなエラーが発生しました:

メディアタイプがサポートされていないというエラーです。

ソースコードをトレースしたところ、AbstractMessageConverterMethodArgumentResolver#readWithMessageConvertersで、メッセージコンバータがJSON形式を解析するためのコンバータをリストに含んでいないことが判明しました:

Spring Frameworkのドキュメント(https://docs.spring.io/spring-framework/docs/4.3.7.RELEASE/spring-framework-reference/html/mvc.html)を確認すると、デフォルトのメッセージコンバータにはJSONを処理するものが含まれていません。

MVC Java設定を有効にするには、@Configurationクラスに@EnableWebMvcアノテーションを追加します:

<em>@Configuration</em>
<em>@EnableWebMvc</em>
public class WebConfig {

}

XML設定では、DispatcherServletコンテキストに要素を使用します:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

は以下を有効にします:

  1. Spring 3スタイルの型変換(ConversionServiceインスタンスを使用)
  2. @NumberFormatアノテーションによる数値フィールドのフォーマットサポート
  3. @DateTimeFormatアノテーションによる日付、カレンダー、Long、Joda Timeフィールドのフォーマットサポート
  4. JSR-303プロバイダーがクラスパス上に存在する場合の@Validによる入力検証サポート
  5. @RequestBodyメソッドパラメータと@ResponseBodyメソッド戻り値のHttpMessageConverterサポート

mvc:annotation-drivenによって設定されるHttpMessageConverterの完全なリスト:

  1. ByteArrayHttpMessageConverter - バイト配列の変換
  2. StringHttpMessageConverter - 文字列の変換
  3. ResourceHttpMessageConverter - すべてのメディアタイプに対するorg.springframework.core.io.Resourceへの変換
  4. SourceHttpMessageConverter - javax.xml.transform.Sourceへの変換
  5. FormHttpMessageConverter - フォームデータからMultiValueMapへの変換
  6. Jaxb2RootElementHttpMessageConverter - JavaオブジェクトとXML間の変換(JAXB2が存在し、Jackson 2 XML拡張が存在しない場合)
  7. MappingJackson2HttpMessageConverter - JSONへの変換(Jackson 2がクラスパス上に存在する場合)
  8. MappingJackson2XmlHttpMessageConverter - XMLへの変換(Jackson 2 XML拡張が存在する場合)
  9. AtomFeedHttpMessageConverter - Atomフィードへの変換(Romeが存在する場合)
  10. RssChannelHttpMessageConverter - RSSフィードへの変換(Romeが存在する場合)

MappingJackson2HttpMessageConverterを使用するには、関連するJARファイルをプロジェクトに追加する必要があります。

以下の依存関係を追加します:

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.8.5</version>
        </dependency>

依存関係を追加して再度インターフェースを呼び出すと、JSON解析が有効になり、リクエストが正常に処理されるようになりました。

この問題の原因は、RequestMappingHandlerAdapterが初期化される際にmessageConvertersを読み込む際の動作にあります。

    public RequestMappingHandlerAdapter() {
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);

        this.messageConverters = new ArrayList<HttpMessageConverter<?>>(4);
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(stringHttpMessageConverter);
        this.messageConverters.add(new SourceHttpMessageConverter<Source>());
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    }

AllEncompassingFormHttpMessageConverterでは、JVMがcom.fasterxml.jackson.databind.ObjectMapperとcom.fasterxml.jackson.core.JsonGeneratorクラスをロードしているかどうかがチェックされ、これらのクラスが存在する場合にのみMappingJackson2HttpMessageConverterが作成されます。

public AllEncompassingFormHttpMessageConverter() {
        addPartConverter(new SourceHttpMessageConverter<Source>());

        if (jaxb2Present && !jackson2XmlPresent) {
            addPartConverter(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            addPartConverter(new MappingJackson2HttpMessageConverter());
        }
        else if (gsonPresent) {
            addPartConverter(new GsonHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            addPartConverter(new MappingJackson2XmlHttpMessageConverter());
        }
    }

補足情報:

受信パラメータの解析はAbstractMessageConverterMethodArgumentResolver#readWithMessageConvertersで行われます。

戻り値の解析はAbstractMessageConverterMethodProcessor#writeWithMessageConvertersで行われます。

タグ: Spring MVC Jackson JSON HttpMessageConverter Maven

7月6日 16:01 投稿