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>
は以下を有効にします:
- Spring 3スタイルの型変換(ConversionServiceインスタンスを使用)
- @NumberFormatアノテーションによる数値フィールドのフォーマットサポート
- @DateTimeFormatアノテーションによる日付、カレンダー、Long、Joda Timeフィールドのフォーマットサポート
- JSR-303プロバイダーがクラスパス上に存在する場合の@Validによる入力検証サポート
- @RequestBodyメソッドパラメータと@ResponseBodyメソッド戻り値のHttpMessageConverterサポート
mvc:annotation-drivenによって設定されるHttpMessageConverterの完全なリスト:
- ByteArrayHttpMessageConverter - バイト配列の変換
- StringHttpMessageConverter - 文字列の変換
- ResourceHttpMessageConverter - すべてのメディアタイプに対するorg.springframework.core.io.Resourceへの変換
- SourceHttpMessageConverter - javax.xml.transform.Sourceへの変換
- FormHttpMessageConverter - フォームデータからMultiValueMapへの変換
- Jaxb2RootElementHttpMessageConverter - JavaオブジェクトとXML間の変換(JAXB2が存在し、Jackson 2 XML拡張が存在しない場合)
- MappingJackson2HttpMessageConverter - JSONへの変換(Jackson 2がクラスパス上に存在する場合)
- MappingJackson2XmlHttpMessageConverter - XMLへの変換(Jackson 2 XML拡張が存在する場合)
- AtomFeedHttpMessageConverter - Atomフィードへの変換(Romeが存在する場合)
- 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で行われます。