BT

Spring 2.5の新機能を使ってアプリケーションを作る(その2)

| 作者 河村 嘉之 フォローする 0 人のフォロワー 投稿日 2008年2月4日. 推定読書時間: 26 分 |

2008年3月5日追記:本記事で掲載したソースのダウンロードが出来るようになりました。
詳細は文末に記載しておりますので、ご覧ください。

Spring Frameworkのバージョン2.5がリリースされました。このバージョンでは、様々な拡張が加えられています。そこで、この記事では、その新しい機能を使ってアプリケーションを作ることにより、どのような機能があるかを紹介しています。

従来のSpring Frameworkでは、コンテナにBeanを登録したり、コンテナが提供する機能を利用するためには、XMLを用いて設定を記述しました。しかし、特に多くのBeanを利用するようなアプリケーションでは、設定ファイルが巨大になるため、その見通しの悪さや記述の冗長性から、XMLで記述する設定ファイルは、Spring Frameworkを利用する上で多くのプログラマが不平を言うポイントのひとつでした。
当初のSpring Frameworkでは、XML以外に設定を記述する方法がなかったのですが、Spring Frameworkのバージョンも2.0、2.5と進むうちに様々な設定を、アノテーションを用いて記述できるようになりました。前回(参考記事)と今回の記事を通して、XMLを用いて設定をしていたアプリケーションを、アノテーションを用いた方式に書き換えることにより、どれだけXMLの記述量を減らせるか、どのようなところをアノテーションで記述し、どのようなところをXMLで記述する必要があるかといったポイントを検証していきます。

サンプルアプリケーション

ここで、前回(参考記事)紹介したサンプルアプリケーションをもう一度紹介します。このアプリケーションは、ユーザ名とパスワードを入力し、ログインボタンを押すと、その値を元に、ログイン成功/失敗を判定し、その結果から成功もしくは失敗ページを表示する非常に単純なアプリケーションです。
アプリケーションの内部をもう少し詳しく説明します。Webアプリケーションは、大きく分けると、Web層のコンポーネント、サービス層のコンポーネント、データアクセス層のコンポーネントで構成されています。ページ上のフォームに入力された値をWeb層での窓口となるログインコントローラが受け取り、そのデータを使ってログイン関連のサービスを受け持つログインサービスを呼び出します。ログインサービスはメンバー情報をデータベースから取得するメンバーDAOを利用してログインの可否を判定します。メンバーDAOはO/RマッピングツールのHibernateを利用してデータベースとやり取りします。これらのコンポーネントの結びつけをSpring Frameworkが受け持っています。




図:サンプルアプリケーションの概要

前回は、サービス層とデータアクセス層を説明しました。今回は、Web層を説明していきます。Web層のコンポーネントへのアノテーションの導入は、Spring Framework 2.5の新機能の大きなポイントの一つです。


従来のアプリケーション

最初に、従来のXML設定ファイルを用いて作成した場合にどのようになるかを説明します。多くの場合、Web層ではWeb MVCフレームワークを利用します。今回は、このWeb MVCフレームワークとして、Spring Frameworkのコンポーネントの一つ、Spring WebMVCを利用します。

入力フォーム用JSP (XML設定ファイル版)

はじめに、ログイン用のフォームを表示するためのJSPのソースコードを説明します。

<%@ page language="java" contentType="text/html; charset=Shift_JIS"
pageEncoding="Shift_JIS"%>
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=Shift_JIS">
<title>Login Page</title>
<style type="text/css"> input.error {background:yellow;}</style>
</head>
<body>
<h1>Login!</h1>
<p>Type your login name and password then press login!</p>
<form:form commandName="login">
<table>
<tr>
<td>Login Name:</td>
<td>
<form:input path="loginname"/>
<font color="red"><form:errors path="loginname"/></font>
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<form:password path="password"/>
<font color="red"><form:errors path="password"/></font>
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" name="Login" value="Login">
</td>
</tr>
</table>
</form:form>
</body>
</html>
リスト:入力フォームを表示するJSP (XML設定ファイル版)

このJSPでは、Spring WebMVCが提供するタグライブラリーを用いてSpring Frameworkが管理するBeanとページ上の入力フォームを関連付けています。ここでは、form:で始まるタグがそれに当たります。form:formタグに囲まれた領域がHTMLのformに相当する領域となり、そこに配置された入力フィールドの値をサーバ側に送信します。ここでは、commandNameという属性にloginという名前を指定しているため、Spring Framework側でloginという名前で管理されているコマンドオブジェクト(ブラウザから送られたデータを保持するためのBean)とこのフォームが関連付けられます。
また、各入力フィールドは、form:inputタグやform:passwordタグで表現されます。これらはそれぞれテキスト入力用のフィールドやパスワード入力用のフィールドとして表示されます。これらのフィールドに入力された値は、このタグのpath属性に指定された値と同じ名前のコマンドオブジェクトのプロパティに格納されます。

入力されたデータを処理するBean (XML設定ファイル版)

次に上記のページに入力された値を受け取りサーバ側で処理するBeanを説明します。

package sample.controller;


public class LoginController extends SimpleFormController {

 private LoginService loginService;

 public LoginService getLoginService() {
  return loginService;
 }

 public void setLoginService(LoginService loginService) {
   this.loginService = loginService;
 }

  @Override
  protected ModelAndView onSubmit(Object command) throws Exception {
    LoginBean login = (LoginBean) command;
    if (loginService.login(login.getLoginname(), login.getPassword())) {
      Map
リスト:入力されたデータを処理するBean (XML設定ファイル版)

従来のSpring WebMVCでは、サーバ側で処理を受け持つBeanは、org.springframework.web.servlet.mvc.Controllerインターフェースを実装したクラスである必要がありました。どのようにリクエストを処理するかに合わせてこのインターフェースを実装した様々なクラスがフレームワークから提供されているため、通常は、これらのクラスを継承し、そのBeanが独自に行う処理を実装したBeanを作成します。ここでは、フォームに入力されたデータを取得し処理を行うため、SimpleFormControllerクラスを利用し、これを継承したクラスを作成します。このクラスでは、onSubmitメソッドでPOSTメソッドで送られたデータの処理を実装します。このメソッドへは、引数としてコマンドオブジェクトが渡されます。コマンドオブジェクトには、フォームの入力フィールドに入力されたデータが格納されています。このデータを用いて、サービス層のBeanを呼び出し、その結果を元に表示を担当するコンポーネントに処理を委譲します。
このクラスでは、前回作成したログイン処理を担当するサービス層のBeanを利用するため、これを保持するフィールドとそのアクセスメソッドを作成します。このアクセスメソッドを通して、コンテナによってサービス層の Beanへの参照が注入されます。
このクラスは、Spring WebMVCが提供するフレームワークにそって実装されています。フレームワークを利用しているため、少ないコードで行いたい処理を実装できるのですが、フレームワークで隠ぺいされる処理も多く、個々のBeanで実装する部分は断片的になるため、どのように動作しているかを理解するのが難しくなる面は否めません。


設定ファイル (XML設定ファイル版)

次に、Spring Frameworkの設定ファイルを説明します。Spring Frameworkの設定ファイルは、XMLで記述します。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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-2.0.xsd">
<!-- (1)URLとのマッピング -->
<bean id="handlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/login.form=loginController
</value>
</property>
</bean>

<!-- (2) 外部モジュールの設定 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages" />
</bean>

<!-- (3) フォーム処理用Bean -->
<bean name="loginController"
class="sample.controller.LoginController">
<property name="sessionForm">
<value>true</value>
</property>
<property name="commandName">
<value>login</value>
</property>
<property name="commandClass">
<value>sample.bean.LoginBean</value>
</property>
<property name="validator">
<ref bean="loginValidator" />
</property>
<property name="formView">
<value>login</value>
</property>
<property name="successView">
<value>success</value>
</property>
<property name="loginService">
<ref bean="loginService"/>
</property>
</bean>
<bean id="loginValidator" class="sample.validator.LoginValidator" />
</beans>
リスト:設定ファイル (XML設定ファイル版)

このファイルでは、最初にURLのパスとそれを処理するBeanのマッピングを設定しています(1)。ここでは、SimpleUrlMappingHandlerを利用し、単純にURLのパスとBeanを関連付けています。
次に、Web層のコンポーネントに関連するリソースを設定しています(2)。ここでは、Beanの実行結果をJSPを用いて表示するコンポーネントと、言語設定ごとのラベルなどを定義するメッセージリソースを設定しています。
最後に、フォーム処理用のBeanを設定します。ここでは、ログイン処理を呼び出すためのWeb層のBeanを設定しています。このBeanはSimpleFormControllerを継承したBeanです。この設定では、作成したクラスのプロパティよりも多くのプロパティを設定しています。これらのプロパティは、このクラスの親クラスのSimpleFormController(とその上位のクラス)が持つプロパティです。このようにSimpleFormControllerを継承したクラスを利用する場合には多くの設定を必要とします。また、ここでは、このクラスから利用される入力値の検証を行う処理を実装したBeanも登録しています。

Spring WebMVCを利用したWebアプリケーションでは、開発者が作成したプロパティ以外にも親クラスが持つプロパティへの設定も必要です。この親クラスが持つプロパティへの設定も必要なため、自然とXMLの記述量は増えていきます。アプリケーションの規模が大きくなるとBeanの数も増えるため、XMLの記述量が増大していきます。


アノテーションを利用した設定

ここからは、ここまでで説明したアプリケーションを、動作を変えずにアノテーションを用いることにより、全体の構成がどのように変わるか見ていきます。

入力フォーム用JSP (アノテーション利用版)

まず、入力フォーム用JSPに変更を加えます。

<%@ page language="java" contentType="text/html; charset=Shift_JIS"
pageEncoding="Shift_JIS"%>
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=Shift_JIS">
<title>Login Page</title>
<style type="text/css"> input.error {background:yellow;}</style>
</head>
<body>
<h1>Login!</h1>
<p>Type your login name and password then press login!</p>
<form:form modelAttribute="login">
<table>

</table>
</form:form>
</body>
</html>
リスト:フォーム入力用JSP (アノテーション利用版)

前半で説明した例と比べて変更した点は、form:formタグにcommandName属性を指定する代わりにmodelAttribute属性を指定しているところです。ここでは、この属性にloginという値を指定しています。

入力されたデータを処理するBean (アノテーション利用版)

次に、入力されたデータを処理するBeanに変更を加えます。

package sample.controller;


@Controller
@RequestMapping("/login.form")
public class LoginController {

private LoginService loginService;
private LoginValidator loginValidator;

@Autowired
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
public LoginService getLoginService() {
return loginService;
}

@Autowired
public void setLoginValidator(LoginValidator loginValidator) {
this.loginValidator = loginValidator;
}
public LoginValidator getLoginValidator() {
return loginValidator;
}

@RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
model.addAttribute("login", new LoginBean());
return "login";
}

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("login")LoginBean login,
BindingResult result, SessionStatus status) {
loginValidator.validate(login, result);
if (result.hasErrors()) {
return "login";
} else {
if (loginService.login(login.getLoginname(), login.getPassword())) {
login.setRealname("Test User");
result.getModel().put("login", login);
status.setComplete();
return "success";
} else {
status.setComplete();
return "fail";
}
}
}
}

リスト:入力されたデータを処理するBean (アノテーション利用版)

従来のSpring WebMVCを利用したアプリケーションでは、ブラウザなどのWebクライアントからのリクエストを処理するクラスは、Controllerインターフェースを実装したクラスでなければいけませんでした。しかし、このクラスは、そのような特定のインターフェースを実装したり、そのインターフェースを実装したフレームワークが提供するクラスを継承したりはしていないシンプルなクラス(POJO)です。
このクラスのクラスレベルのアノテーションに@Controllerを指定して、このクラスがWebクライアントからのリクエストを処理するクラスであることを指定します。また、クラスレベルの@RequestMappingアノテーションにパスを指定することにより、ここで指定したパスにアクセスがあった場合にこのクラスが処理を担当することを示します。
このクラスのsetupFormメソッドとprocessSubmitメソッドには、それぞれ@RequestMappingアノテーションが付与されています。それぞれのアノテーションのmethod属性に、RequestMethod.GETとRequestMethod.POSTが指定されています。これは、クラスレベルの@RequestMappingアノテーションで指定したパスにHTTPのGETメソッドでアクセスしたときにsetupFormメソッドが、POSTメソッドでアクセスした時にprocessSubmitが呼ばれることを表します。もう少し簡単な言い方をすると、setupFormが入力フォームを持つページを最初に表示する際に呼ばれ、processSubmitがそのフォームに値が入力されてデータが送られてきたときに呼ばれるようになります。
processSubmitメソッドでは、@ModelAttributeというアノテーションの付いたLoginBean型の引数を取っています。このアノテーションによって、JSPのform:formタグのmodelAttribute属性で指定した名前のform領域と、ここでパラメータとして渡されるBeanの関連付けを行っています。
また、@Autowiredアノテーションを指定することによって、loginServiceプロパティとloginValidatorプロパティにその型にあったBeanがコンテナによって注入されます。
従来、Spring WebMVCでは、Controllerインターフェースを実装したフレームワークが提供するクラスを利用してリクエストの処理をしていました。このようなクラスの継承関係は難しく、なかなか使いこなすには敷居が高いものでした。また、複雑な継承関係があるクラスを利用していたため、POJOを中心にアプリケーションを組み立てる傾向のSpring Frameworkの中で、Web層はフレームワークへの依存も強く、その傾向に合わない層でした。しかし、アノテーションを用いることにより、Web層もPOJOでBeanを作成できるようになりました。複雑な継承関係を持つクラスではなく、POJOを用いることにより、そのBeanでどのような処理を行うか把握することが容易になりました。しかし、例えばprocessSubmitメソッドの中でloginValidatorを呼び出して入力値の検証をしているように従来のSpring WebMVCではフレームワークが機能を提供してくれた処理をそれぞれのクラスで実装しなくてはいけなくなり、多少利便性が下がってしまった面は否めません。また、このメソッドでは、BindingResultとSessionStatusを引数に取っていますが、このようにフレームワークが期待する引数を受け取らなければいけないように、暗黙の決まり事を理解していなければいけない側面があることは否めません。

設定ファイル (アノテーション利用版)

最後に、設定ファイルに変更を加えます。

<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan
base-package="sample.controller,sample.validator"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages" />
</bean>
</beans>
リスト:設定ファイル (アノテーション利用版)

アノテーションを用いなかった時の設定ファイルでは、どのパスにアクセスがあった場合はどのBeanが処理するかなどの設定や、アプリケーションで使う個々のBeanの設定が必要でした。アノテーションを用いるパターンでは、context:component-scan要素により、指定されたパッケージに属する@Controllerアノテーションなどのアノテーションを付与したBeanが自動的にコンテナに登録されます。また、パスについても個々のBeanのアノテーションで記述するため、この設定ファイルで記述する必要はありません。特にWeb層では個々のBeanに設定しなくてはいけないプロパティが多かったため、これを記述せずに済むことで、利用するBeanが多いときは記述量を大きく減らせるでしょう。
そして、XML設定ファイルには、メッセージリソースやJSPとの連携など、外部との連携に関連する設定のみが残るようになります。

XMLによる設定とアノテーションを利用した設定の違い

以上で、前回と今回を通し、Web層、サービス層、データアクセス層に渡って、従来のXML設定ファイルですべてを設定する方式からできるだけアノテーションを利用して設定する方式にアプリケーションを書き換えました。今回はアプリケーションの規模が小さいのでXML設定ファイルの記述量が減ったとあまり感じられないかもしれません。しかし、アプリケーションの規模が大きくなるとその違いは明確になると思います。
また、アノテーションの導入により、Web層のコンポーネントもPOJOで記述できるようになったため、よりシンプルなアプリケーション作成が可能になったと思います。
アノテーションの利用により、外部リソースとのやり取りをするBeanはXMLファイルで設定し、個々のコンポーネントの組み合わせはアノテーションで記述することが可能になります。

この記事では、アノテーションによる設定方式では、XML設定ファイルの記述量を減らしたり設定の種別による役割分担ができたりといったアノテーションを利用した際の利点を中心に説明してきました。ここで、欠点についても考えてみます。まず、設定がソースコードに入ってしまうため、個々の設定を確認するためにはソースコードをチェックしなければいけなかったり、変更する時もソースコードの再コンパイルが必要であったりといった欠点があることも否めません。また、XMLのみで記述する方式ではXMLファイルに設定がすべて書かれるため、設定を一カ所で管理することができたのですが、アノテーションを利用するとそれが各クラスに分散してしまいます。
どちらの方式が正しいという結論はないと思いますが、選択肢が増えたということは、開発者にとってとても重要なことでしょう。この記事が、どの設定にアノテーションを用いてどの設定をXMLで記述するかを考えるスタートポイントになれば幸いです。

 

著者について

河村 嘉之(かわむら かずゆき)
ウルシステムズ株式会社所属
メーカー系SI会社にてJavaを用いたシステム開発、新技術評価を担当後、現在はウルシステムズにてオープンソースに関連するビジネスに従事。
日本Springユーザ会、日本Javaユーザ会などでも中心メンバーとして活動中。

2008年3月5日追記:本記事で紹介しているソースがダウンロードできるようになりました。
ダウンロードはこちらからお願いします(ダウンロードの際にInfoQへのログイン、もしくは会員登録が必要になりますのでご了承ください)。

この記事に星をつける

おすすめ度
スタイル

こんにちは

コメントするには InfoQアカウントの登録 または が必要です。InfoQ に登録するとさまざまなことができます。

アカウント登録をしてInfoQをお楽しみください。

あなたの意見をお聞かせください。

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする
コミュニティコメント

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

ディスカッション

InfoQにログインし新機能を利用する


パスワードを忘れた方はこちらへ

Follow

お気に入りのトピックや著者をフォローする

業界やサイト内で一番重要な見出しを閲覧する

Like

より多いシグナル、より少ないノイズ

お気に入りのトピックと著者を選択して自分のフィードを作る

Notifications

最新情報をすぐ手に入れるようにしよう

通知設定をして、お気に入りコンテンツを見逃さないようにしよう!

BT