この記事について
この記事は 2011年に自分のブログに書いていた記事を発掘したものです。10年以上の文章なので、表現が稚拙だったり、時代が古いところもありますが、何かの役に立つかもしれないと考え、Qiitaに持ってくることにしました。
本連載の 「SpringとMyBatisを使ったアプリケーション開発のメモ -その1-」 と 「SpringとMyBatisを使ったアプリケーション開発のメモ -その2-」 で、SpringとMyBatisを使ったアプリケーション開発について調べ、Springを使うとDI (Dependency Injection) という技法使ってサーバサイドアプリケーションをすっきりと記述できるのがわかりました。
ただ、前回までに作成したアプリケーションはmain()メソッドからサービス層を呼び出すだけのもので、きちんとしたサーバサイドアプリになっていませんでした。
今回はそのアプリケーションをRESTFulなアプリケーションとするためにJAX-RSというテクノロジを使ってみたいと思います。JAX-RSに対応したフレームワークにはいろいろあるのですが、今回はRESTEasyというフレームワークを使ってみます。
なお、各モジュールのバージョンは以下のものを使っています。
- Spring 3.0.5.RELEASE
- MyBatis 3.0.4
- RESTEasy 2.1.0.GA
JavaはJava6の文法を使っています。Servlet APIは2.5です。
JAX-RSってなに?
JAX(ジャックス)で始まるテクノロジには以下のようなものがあります。
- JAXB (Java Architecture for XML Binding)
- JavaのXMLバインディング(JavaのオブジェクトをXMLで表現する)技術
- JAX-RPC (Java API for XML-based RPC)
- JavaでXMLを使ったRPCサービスを実現する技術
- JAX-WS (Java API for XML Web Services)
- JavaでXMLを使ったWebサービスを実現する技術
JAX-RSもこれらの仲間で「Java API for RESTful Web Services」つまり「JavaでRESTfulなWebサービスを実現する技術」という事になります。
ちなみにJAXの"X"は元々XMLの"X"だったはずなのですが、JAX-RSについては"X"に対応する単語がありませんね。つまり、JAX-RSは「別にXMLには限らないのよ」という事を言っています(たぶん)。
ちなみに、JAX-RSについてはオライリーの「JavaによるRESTfulシステム構築」が非常によく書かれていてオススメです。後にご紹介するRESTEasyの作者の方が書かれています。
RESTfulとRPCの違い
RESTfulという技術はHTTPプロトコルを使ってサーバ上のサービスをクライアントに提供する技術です。サーバ上ではクライアントに提供したいメソッドを定義して、それをHTTPプロトコル経由で呼び出せるようにするのですが、一般的にはサーバサイドのメソッドを呼ぶ技術をRPC (Remote Procedure Call)と言います。しかし、RPCについてはJavaではJAX-RPCなどのテクノロジがありますし、JAX-WSでも同じような事ができます。
JAX-RSはそれらよりも後から出てきた技術なのですが、どうしてJAX-RSというRESTfulな考え方が必要になってきたのでしょうか。
RESTfulの哲学についての詳細は他にお任せしますが、一言で言ってしまえば「RESTfulな考え方はWebブラウザと相性が良い」という事につきると思います。
例えば多くのRPCサービスでは、サーバサイドでエラーが起きた時でもHTTPの "200 OK"が返却され、レスポンスボディに「エラーが起きたよ」という内容を入れるという仕様になっていたりします。エラーが起きたのに「OK」が返ると言うのはいろいろなところで不整合を引き起こす可能性があります。例えば「キャッシュ」です。Webページを高速化するために、同一の内容を返すと分かっているコンテンツに対しては、2回目以降の呼び出しにキャッシュされたコンテンツを返す事が良く行なわれます。
キャッシュサーバにRPCの呼び出しをキャッシュさせようとした場合、"たまたま失敗した結果"をキャッシュされては困ります。しかし、HTTPレスポンスが"200 OK"だったという事実だけでは、それが正常な結果なのかエラーなのかを区別できません。キャッシュサーバはレスポンスボディの内容をパースして、「エラーだった場合はキャッシュしない」という動作をしなければなりません。これは非常に煩雑です。
RESTfulな考え方に基づくと、エラーが起きた場合には200番台のステータスコードを返さないようになります。もし認証にエラーがあったのなら「401 Unauthorized」が返却されますし、サーバ内部で問題が起きたのならば「500 Internal Server Error」が返ります。これは人間がWebサイトをブラウズした時に遭遇する現象と同じです。
このように、サーバ間の呼び出しにおいてもHTTPの仕様をそのまま利用する事で、サーバ間でのHTTPのやり取りと人間がWebサイトをブラウズする行為の違いが少なくなり、通常のWebサイトに適用される技術をそのままストレートに利用できるようになります。
RESTfulのメリットは他にもありますが、今はこのくらいにしておきましょう。
JAX-RS最初の一歩
JAX-RSは次のJavaインターフェースを見るだけでその単純さが分かってしまいます。
package jp.ohnaka.springmybatis.jaxrs;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import jp.ohnaka.springmybatis.model.Card;
@Path("/card")
@Consumes("application/xml")
@Produces("application/xml")
public interface CardRestService {
@GET
@Path("/{id}")
Card getCard(@PathParam("id") String id);
@POST
@Path("/")
Card createCard();
@PUT
@Path("/{id}")
void updateCard(@PathParam("id") String id, Card card);
@DELETE
@Path("/{id}")
void deleteCard(@PathParam("id") String id);
@GET
@Path("/")
@Produces("text/plain")
int getNumOfCards();
@POST
@Path("/twocards/")
void createTwoCards();
}
このインターフェースは、JAX-RSを使って6つのメソッドを公開するように書かれています。
@Pathアノテーション
まず、CardRestServiceというインターフェース全体に対して@Path("/card")というアノテーションが指定されています。このアノテーションをJAX-RSのライブラリが認識すると、「このインターフェースがRESTfulなサービスを定義しているんだな」と解釈します。値として"/card"が与えられているので、この場合は、「このWebアプリケーションのコンテクストルートを基点として /card というパスの下にAPIを公開する」という意味になります。
アプリケーションが http://localhost:8080/app/
に配備されていたとすると、CardRestServiceのAPIは http://localhost:8080/app/card/
以下に作られます。ただし、後述するようにweb.xmlのcontext-param
で resteasy.servlet.mapping.prefix
にパス値をセットする事で、オフセットを変更する事もできます。
@Consumesアノテーションと@Producesアノテーション
@Consumesアノテーションは、APIが受け入れるContext-Typeを宣言します。引数に"application/xml"と書かれているので、「これらのAPIはHTTPのContext-Typeヘッダが "application/xml" のものを受け入れます」という意味になります。
@Producesはその逆で、APIが返却するContext-Typeを宣言します。HTTPレスポンスのContext-Typeヘッダが "application/xml"になります。
@Consumesも@Producesも、それだけではXMLのどういうスキーマが使われるのかまでは規定しません。つまり、CardクラスをXMLで表現した場合に、
<Card>
<Name>hoge</Name>
</Card>
とするのか、
<Card name="hoge" />
とするのかは、このアノテーションだけでは決まりません。
このように、コンピュータのメモリ上にあるJavaのオブジェクト(上記の例の場合はCardオブジェクト)をXMLなどの別の形式で表現し直す事を「データ バインディング」と言います。「XMLバインディング」と言えば「XMLで表現します」という意味ですし、「JAXBバインディング」と言えば「JAXBの仕様に従ってXMLで表現します」という意味です。
ちなみに、実際にあるオブジェクトをXMLなどに変換する事を「マーシャリング」、その逆を「アンマーシャリング」と言ったりします。
話を戻しますが、CardRestServiceの層から見ると、データバインディングの層は下位に位置しています。つまり、CardRestServiceの層では「別にXMLでどう表現されても気にしないんだよね〜。Javaのオブジェクトになったものを渡してよ」という立場なわけです。 この「CardRestServiceに下位レイヤの仕様を記述しない」というのは、「他のコンポーネントの依存を極力切る」というDIの考え方に相通ずるものがありますね。
一方、getNumOfCards()メソッドには、メソッドに対して@Producesアノテーションが付いています。メソッドに対して@Producesアノテーションを付けると、クラスに対してセットしたアノテーションを上書きする事ができます。この場合は、getNumOfCards()が返すHTTPレスポンスのContent-Typeが text/plainになり、int型が単純に文字列として返却される事が期待されます。このあたりのデータ表現に関しては次回解説します。
@GET、@POST、@PUT、@DELETEアノテーション
CardRestServiceの各メソッドを見ると、@GET、@POSTなどのアノテーションが書かれています。これらがHTTPのメソッドを指定しているという事は言うまでもありません。 同時に各メソッドにも@Pathアノテーションが書かれています。つまり、- /card/{id} に対して HTTPの GETメソッドを発行したら getCard()を呼び出す
- /card/ に対して HTTPの POSTメソッドを発行したら createCard()を呼び出す
- /card/{id} に対してHTTPの PUTメソッドを発行したら updateCard()を呼び出す
- /card/{id} に対してHTTPのDELETEメソッドを発行したら deleteCard()を呼び出す
@PathParamアノテーション
ちなみに、PUTとDELETEメソッドのパスにある{id}という指定は、パスのその部分に書かれた文字列を「id」という引数としてメソッドに渡すという事を意味しています。updateCardの第一引数には@PathParam("id") というアノテーションが付いているため、その引数に渡されます。
つまり、/card/1234 にDELETEメソッドを発行すると、deleteCard("1234") が呼び出される訳です。PUTメソッドに対応した updateCard()にはCardクラスの第二引数がありますが、こちらの引数に渡るオブジェクトはHTTPのリクエストボディから生成されます。cardオブジェクトがHTTPリクエストボディ内でどのようなフォーマットになるかについては後述します。
JAX-RSのサービスを具体的に記述する
あとはこのCardRestServiceインターフェースを実装したクラスを作るだけです。前回の記事で作ったCardServiceを使えば次のように簡単に書く事ができます。
package jp.ohnaka.springmybatis.jaxrs;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
import jp.ohnaka.springmybatis.model.Card;
import jp.ohnaka.springmybatis.service.CardService;
public class CardRestServiceImpl implements CardRestService {
private CardService cardService_;
public void setCardService(CardService cardService) {
this.cardService_ = cardService;
}
@Override
public Card getCard(String id) {
return cardService_.getCard(id);
}
@Override
public Card createCard() {
return cardService_.createCard();
}
@Override
public void updateCard(String id, Card card) {
if (!id.equals(card.getId())) {
throw new WebApplicationException(Status.BAD_REQUEST);
}
cardService_.updateCard(card);
}
@Override
public void deleteCard(String id) {
cardService_.deleteCard(id);
}
@Override
public int getNumOfCards() {
return cardService_.getNumOfCards();
}
@Override
public void createTwoCards() {
cardService_.createTwoCards();
}
}
笑っちゃうくらい単純ですね。
この例の場合、CardRestServiceと CardServiceはインターフェースが全く同じなので、両者を混ぜてしまう事も可能です。ただ、将来JAX-RS以外のAPIも提供したい場合に、CardServiceにJAX-RSのアノテーションが入ってしまうのは少し気持ち悪いので、今回は分離して記述しています。
なお、updateCard()メソッドは第一引数に変更したいオブジェクトのidが入り、第二引数に変更内容のオブジェクトが渡ります。間違ったパスでオブジェクトをアップデートしてしまうのを避ける為に、idの値とcardオブジェクトのgetId()の結果が等しい事を調べ、等しく無い場合はHTTPのBad Requestを返却するようにしています。このようにエラー処理もかなり直感的に記述できるようになっています。
RESTEasyを使って実際にサービスを提供する
では実際に CardRestService
をHTTPプロトコルから叩けるようにしてみましょう。今回はRESTEasyというフレームワークを使います。
pom.xml
RESTEasyのライブラリをMavenから使う場合は、以下のようなpom.xmlを用意します。前回までの分も含めて全体を示しておきます。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jp.ohnaka</groupId>
<artifactId>spring-mybatis-resteasy-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Spring and MyBatis RESTEasy test</name>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.0.4</version>
</dependency>
<!-- MyBatis-Spring adapter -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.0.0</version>
</dependency>
<!-- DB -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.12</version>
</dependency>
<!-- RESTEasy -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>2.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring</artifactId>
<version>2.1.0.GA</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
web.xml
前回と違いpom.xmlのpackagingが "war"になっているとおり、このプロジェクトはWebアプリケーションです。ということで web.xmlの記述は以下のようにしてください。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>spring-mybatis-resteasy</display-name>
<!-- RESTEasy初期化用リスナ -->
<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<!-- RESTEasyをSpringで初期化する為のリスナ -->
<listener>
<listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
</listener>
<!-- RESTEasyのサーブレット -->
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
</servlet>
<!-- RESTEasyのサーブレットを/rest 以下に配置 -->
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<!-- RESTEasyのサーブレットをコンテクストルート直下に置かなかった場合に、それを知らせる -->
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest/</param-value>
</context-param>
<!-- Springの設定ファイルをデフォルトの WEB-INF/applicationContext.xml意外にする場合の設定 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring-setting.xml</param-value>
</context-param>
</web-app>
spring-setting.xml
Springの設定は以下のようになります。前回までと比べると、CardRestServiceImplの指定が増えているのが分かります。
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- データソースの定義. アプリケーションサーバ上で動かす場合はJNDIから取ってくるようにしても良い -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://hostname/card" />
<property name="username" value="hogehoge" />
<property name="password" value="passpass" />
</bean>
<!-- MyBatisのSqlSeessionFactoryの定義 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- MyBatisの設定ファイルの場所. TypeAliasを使いたいので読み込ませている. -->
<property name="configLocation"
value="classpath:/jp/ohnaka/springmybatis/persistence/mybatis-config.xml" />
</bean>
<!-- CardServiceで使う CardクラスのMapperを定義 -->
<bean id="cardMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface"
value="jp.ohnaka.springmybatis.persistence.CardMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<!-- サービスクラスの定義 -->
<bean id="cardService" class="jp.ohnaka.springmybatis.service.CardServiceImpl">
<property name="cardMapper" ref="cardMapper" />
</bean>
<!-- RESTfulサービスクラスの定義 -->
<bean id="cardRestService" class="jp.ohnaka.springmybatis.jaxrs.CardRestServiceImpl">
<property name="cardService" ref="cardService" />
</bean>
<!-- アノテーションベースのトランザクション管理を有効にする -->
<tx:annotation-driven transaction-manager="txManager" />
<!-- トランザクションマネージャの指定。データソースを使っているのでDataSourceTransactionManagerを使います -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
RESTEasyとSpringのコラボレーション
上記設定をするだけでCardRestServiceは動作してしまいます。いったい何がどうなっているのでしょう?それに、データバインディングについて何も指定していませんが、どんなフォーマットでHTTP上にデータが流れるのでしょうか?
詳しく見て行きます。
web.xmlの解説
まずはweb.xmlから見てください。ContextListenerが二つ登録されています。
- 一つ目はRESTEasyの初期化をする為のブートストラップ用リスナです。
- 二つ目はresteasy-springライブラリに含まれるリスナで、RESTEasyを簡単にSpringと連携させる為のものです。
RESTEasyを単体で使う場合、通常は@Pathアノテーションを指定したサービスクラス(インターフェース)がどのクラスであるかを設定しなくてはいけません。このリスナはSpringのBeanとして登録されているクラスを全て走査し、自動的に検出してくれます。今回の例ではCardRestServiceImplがcardRestServiceというidでSpringに登録されているので、これを自動的に見つけてサービスとして登録してくれます。
次に、RESTEasyのサーブレット、HttpServletDispatcherを配置しています。これはSpringを使わない場合でも用いる通常のサーブレットです。
このサーブレットはコンテクストルート直下ではなく、/rest/*と、一階層下げて配置しています。これは将来RESTful以外のRPCサービスを提供したい場合などに備えてのものです。また、WicketやStrutsを使って/debug以下にデバッグ用のUIを配備するといった際にも便利です。
ただ、これだとCardRestServiceに定義した@Pathアノテーションとの整合が取れなくなってしまいますので、context-param
で resteasy.servlet.mapping.prefixという名前のパラメータに "/rest/"と指定し、開始点を一階層下げた事をRESTEasyに教えてあげる必要があります。
spring-setting.xmlの解説
spring-setting.xmlは前回説明したものとほとんど違いがありません。唯一違うのは以下のCardRestServiceのBean定義が増えた事だけです。
<!-- RESTfulサービスクラスの定義 -->
<bean id="cardRestService" class="jp.ohnaka.springmybatis.jaxrs.CardRestServiceImpl">
<property name="cardService" ref="cardService" />
</bean>
これは簡単ですね。cardRestServiceというidでCardRestServiceImplクラスをインスタンス化し、そのcardServiceプロパティに cardServiceをセットしているだけです。
先ほど述べたように、このWebアプリケーションが起動すると、起動時にSpringContextLoaderListenerというリスナがこのBean定義を見つけてくれます。そして、CardRestServiceImplが実装しているCardRestServiceインターフェースに@Pathアノテーションがある事から、このクラスがJAX-RSのRESTfulサービスを定義したものだという事まで自動的に判断し、RESTEasyのHttpServletDispatcherサーブレットに伝えてくれます。
SpringのApplicationContextは誰が初期化しているの?
さて設定は以上なのですが、何か忘れている気がします、、、。前回までに説明したアプリケーションはmainメソッドで spring-setting.xmlを読み、ApplicationContextを生成していました。
今回はwebアプリケーションのため、mainメソッドは呼ばれません。一体誰がApplicationContextを生成しているのでしょうか?
そうです。先ほどのSpringContextLoaderListenerがその処理をやっています。実はRESTEasyだけに限らず、SpringではWebアプリケーションがApplicationContextを初期化する共通の方法を定めていて、SpringContextLoaderListenerもその方法に則った初期化を行っています。
詳しくはSpring 3のマニュアルの「3.14 Additional Capabilities of the ApplicationContext」やPart V「The Web」内の「17.2 Common configuration」を参照して頂きたいのですが、簡単に説明すると以下のようになります。
- WebアプリケーションではContextLoaderListenerというContextListenerを使うと初期化が簡単にできる
- ContextLoaderListenerはデフォルトで /WEB-INF/applicationContext.xmlを読み込み、ApplicationContextを生成する
- 読み込む設定ファイルを変更する際はcontext-paramでcontextConfigLocationという名前のパラメータに設定ファイルの場所をセットする
- ContextLoaderListenerが生成したApplicationContextは、WebApplicationContextUtilsを使うと ServletContextから取得できる
- WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
- WebApplicationContextは ApplicationContextとして使える
なお、今回使用したSpringContextLoaderListenerはSpring標準のものではなく、RESTEasyが用意したものですが、上記標準のContextLoaderListenerと同じ動きをします。
という訳で、今回のアプリケーションでは以下のように初期化が行われている事になります。
HttpServletDispatcher (RESTEasyのサーブレット)
↑ SpringContextLoaderListenerによって注入
CardRestService
CardRestServiceImpl
↑ Springにより注入
CardService
CardServiceImpl
↑ Springにより注入
CardMapper
CardMapperImpl (CardMapper.xmlを元にMapperFactoryBeanによって自動的に作られたインスタンス。僕らは意識する必要はない)
↑ Springにより注入
SqlSessionFactory
↑ Springにより注入
DataSource
このように、明示的にApplicationContextを操作するコードを書く事無く、全ての注入が自動的に行われてしまうのです。
とっても便利ですね!
サービスを実際に呼び出してみる
wgetコマンドを使って実際に createCard を呼び出してみましょう。アプリケーション名は spring-mybatis-resteasy-test としたので、URLは http://localhost:8080/spring-mybatis-resteasy-test/rest/card/
になります。
このURLに対して空のデータをPOSTしてみます。
# wget -q -O - --header="Content-Type: application/xml" --post-data="" http://localhost:8080/spring-mybatis-resteasy-test/rest/card
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Card><id>d1ee93af-1b8b-4430-9e47-790842f3b0fc</id><name>No Name</name></Card>
作成されたカードの結果が返ってきました!
次に、作られたカードの総枚数を調べる為には同じURLに対してGETリクエストを発行します。すると、getNumOfCards()が呼ばれるはずです。
# wget -q -O - --header="Content-Type: application/xml" http://localhost:8080/spring-mybatis-resteasy-test/rest/card
1
「1」という結果が返ってきました!getNumOfCards()には@Produces("test/plain")を付けたため、XMLではなく単純な文字列として結果が返ってきています。
でも createCard()
が返却してきたXMLフォーマットは 一体なんでしょう??? どこでも設定した覚えはありません。 aこれらのデータフォーマットがクライアントに合わず、別の形式にしたい場合はどうしたらいいんでしょう? JSONにしたい場合とか、、、。
次回はJAX-RSにおけるデータ表現、データバインディングについて解説します。