2014年度版 Eclipse + Struts2 による Java Web アプリ開発入門

オラクル管理下になって以降めっきり話題が減った感のある Java ですが、最近、隣の席の新人 SE さんが研修で Struts2 ベースの Web アプリを構築をしてるようで、時々質問を受けたりします。Java と言えば Struts に次ぐ定番フレームワークが長らく待望されてましたが、結局どうなったのでしょう?
Struts の冠のある Struts2 がネーミング的には有利な気もするので便乗して勉強してみることにしました。

Google App Engine に Struts2 のデモをデプロイしました!(GAE めんどい・・・)
http://hello-gae-struts2.appspot.com/HelloStruts2/login.action

開発環境の準備

32ビット版 Windows 環境に開発環境を構築します。次のものを準備します。

  • Eclipse Kepler(4.3)
  • Pleiades(Eclipse の日本語化プラグイン)
  • JDK(Java SE 7u51)
  • Tomcat(7.0.50)
  • Struts2(2.3.16)

Eclipse 4.3(Kepler)の導入

http://www.eclipse.org/downloads/ にアクセスします。

www.eclipse.org

[Eclipse IDE for Java EE Developers」を選択します。

www.eclipse.org

矢印のアイコンをクリックしダウンロードします。

ダウンロードした eclipse-jee-kepler-SR1-win32.zip を解凍します。(今回は c:\IDE\eclipse\keplerに解凍)

Pleiades(Eclipse の日本語化プラグイン)の導入

http://mergedoc.sourceforge.jp/ にアクセスします。

http://mergedoc.sourceforge.jp/

最新版の 1.4.x をダウンロードします。

http://mergedoc.sourceforge.jp/

pleiades.zip を解凍し、features フォルダ、plugins フォルダ、[eclipse.exe -clean.cmd]ファイルを、Eclipse のフォルダ(c:\IDE\eclipse\kepler)に上書きコピーします。

http://mergedoc.sourceforge.jp/

Eclipse のフォルダ(c:\IDE\eclipse\kepler)にある[eclipse.ini]をテキストエディタで開き、最後の1行に以下を追記します。

  1. -javaagent:plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar

JDK(Java SE 7u51)の導入

http://www.oracle.com/technetwork/java/javase/downloads/index.html にアクセスします。

JDK

最新版の Java Platform (JDK) 7u51を選択します。

JDK

[Accept License Agreement]を選択し、[jdk-7u51-windows-i586.exe]をダウンロードします。

jdk-7u51-windows-i586.exe を実行し、JDK をインストールします。(今回は C:\IDE\jdk1.7.0_51 にインストール)

Tomcat(7.0.50)の導入

http://tomcat.apache.org/ にアクセスします。

Tomcat

Tomcat7.0 を選択します。

Tomcat

最新 7.0.50 の zip をダウンロードします。

ダウンロードした apache-tomcat-7.0.50.zip を解凍します。(今回は C:\IDE\tomcat\apache-tomcat-7.0.50 に解凍)

Eclipse の環境設定

Eclipse を起動します。初回起動時のみ[eclipse.exe -clean.cmd]を実行します。(初回以降は eclipse.exe)

eclipse

[Workbench]をクリックします。

eclipse

作業フォルダを指定します。(今回は C:\IDE\eclipse_workspace)

eclipse

ツールバーの[ウィンドウ][設定]を選択し、[一般][ワークスペース]の[テキスト・ファイルのエンコード]に[UTF8]を指定します。

eclipse

同様に設定画面において、[Java][インストール済のJRE]で[追加]ボタンをクリックします。ダイアログが表示されるので[標準VM]を選択した状態で[次へ]ボタンをクリックします。

eclipse

[JREホーム]に JDK をインストールしたフォルダのパスを入力すると、[JRE名][JREシステムライブラリー]が表示されます。[完了]ボタンをクリックします。

eclipse

[インストール済みのJRE]に指定した JDK が表示されてるので、チェックボックスをチェックし、[OK]をクリックします。

サーブレットを動かしてみる

最新の開発環境では Tomcat プラグインの導入や web.xml のサーブレットの記述も不要です。

ツールバーの[ファイル][新規][プロジェクト]を選択。

eclipse

[動的 Web プロジェクト]を選択し、[次へ]をクリック。

eclipse

[プロジェクト名]に[HelloServlet]と入力し、[新規ランタイム]をクリック。

eclipse

[Apache Tomcat v7.0]を選択し、[次へ]をクリック。

eclipse

[Tomcat インストール・ディレクトリー]に Tomcatを解凍したフォルダのパス(C:\IDE\tomcat\apache-tomcat-7.0.50)を入力し、[完了]をクリック。

eclipse

[ターゲット・ランタイム]に[Apache Tomcat v7.0]が表示されたことを確認し、[次へ]をクリック。

eclipse

[次へ]をクリック。

eclipse

[web.xmlデプロイメント記述しの生成]をチェックし、[完了]をクリック。

eclipse

[プロジェクトエクスプローラー]に[HelloServlet]プロジェクトが表示される。

eclipse

[HelloServlet]プロジェクトを選択した状態で、ツールバーの[ファイル][新規][サーブレット]を選択し、[Java パッケージ]に[hello]、[クラス名]に[MyServlet]と入力し、[完了]をクリック。

eclipse

[MyServlet.java]が生成され、ソースが表示される。[doGet]メソッドに以下を追記する。

  1. response.getWriter().write("Hello, MyServlet !");

eclipse

[プロジェクトエクスプローラー]の[MyServlet.java]を選択した状態で、[右クリック][実行][サーバで実行]を選択し、ダイアログを表示したら[完了]をクリック。

eclipse

実行結果が Eclipse 内に表示されます。http://localhost:8080/HelloServlet/MyServlet をブラウザから指定しても同様の処理結果が得られます。

Struts2 を動かしてみる

Googleで「Struts2 入門」で検索すると以下のようなさまざまな入門サイトがひっかかりますが、バージョンアップ毎の仕様変更が活発なせいなのか現時点の最新版 2.3.16 の Struts2 で動かそうとするうまくいきません。

Struts2 と言えば規約に従えば設定ファイル不要で動かせるのが売りですが、下記手順で設定ファイルを書かずに(厳密にはちょっと書きますが)Struts2 を動かすことができました。

http://struts.apache.org/ にアクセスします。

struts2

[Download]をクリックします。

struts2

[struts-2.3.16-all.zip]をダウンロードし解凍します。

struts2

[HelloServlet]の作成手順と同様に、動的 Web プロジェクトで[HelloStruts2]プロジェクトを作成し、[HelloStruts2/WebContent/WEB-INF/lib]に、[struts-2.3.16-all.zip]の lib フォルダにある以下ファイルをコピーします。(エクスプローラ上で Ctrl+c し Eclipse 上で Ctrl+v)

  • asm-3.3.jar
  • asm-commons-3.3.jar
  • asm-tree-3.3.jar
  • commons-fileupload-1.3.jar
  • commons-io-2.2.jar
  • commons-lang3-3.1.jar
  • commons-logging-1.1.3.jar
  • freemarker-2.3.19.jar
  • javassist-3.11.0.GA.jar
  • log4j-1.2.17.jar
  • ognl-3.0.6.jar
  • struts2-core-2.3.16.jar
  • xwork-core-2.3.16.jar

[HelloStruts2/WebContent/WEB-INF/web.xml]を以下のように書き換えます。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app id="HelloStruts2" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  3.  
  4. <display-name>Hello Struts2</display-name>
  5.  
  6. <filter>
  7. <filter-name>struts2</filter-name>
  8. <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
  9. <init-param>
  10. <param-name>actionPackages</param-name>
  11. <param-value>hello</param-value>
  12. </init-param>
  13. </filter>
  14.  
  15. <filter-mapping>
  16. <filter-name>struts2</filter-name>
  17. <url-pattern>/*</url-pattern>
  18. </filter-mapping>
  19.  
  20. <welcome-file-list>
  21. <welcome-file>index.html</welcome-file>
  22. </welcome-file-list>
  23.  
  24. </web-app>

actionPackages に hello と指定しているのは、アクションクラスを hello パッケージで管理することを明示してます。

[HelloStruts2/Java リソース/src]の直下に下記内容で struts.xml を配置します。これにより設定ファイルの記述不要で規約ベースで動作できるようになります。

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE struts PUBLIC
  3. "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
  4. "http://struts.apache.org/dtds/struts-2.3.dtd">
  5. <struts>
  6. <constant name="struts.enable.DynamicMethodInvocation" value="true" />
  7. </struts>

[HelloStruts2/Java リソース/src]の直下に下記内容で struts.properties を配置します。これにより jsp にてカスタムタグを使用した際、身勝手な(笑)マークアップを出力させないようにします。

  1. struts.ui.theme=simple

ログイン画面の作成

簡単なログイン画面を Bootstrap3 を使って作成してみます。ログインに成功した場合、セッションにユーザIDをセットし、別画面にリダイレクトさせるようにします。

下記内容の login.jsp, header.jsp, footer.jsp を [HelloStruts2/WebContents] 直下に作成します。

login.jsp

  1. <%@ page contentType="text/html; charset=UTF-8" %>
  2. <%@ taglib prefix="s" uri="/struts-tags" %>
  3. <jsp:include page="header.jsp"/>
  4. <s:form cssClass="form-horizontal">
  5. <div class="form-group">
  6. <label class="col-sm-2 control-label">USER:</label>
  7. <div class="col-sm-10">
  8. <s:textfield name="userId" cssClass="form-control"/>
  9. </div>
  10. </div>
  11. <div class="form-group">
  12. <label class="col-sm-2 control-label">PASSWORD:</label>
  13. <div class="col-sm-10">
  14. <s:textfield name="password" type="password" cssClass="form-control"/>
  15. </div>
  16. </div>
  17. <div class="form-group">
  18. <div class="col-sm-offset-2 col-sm-10">
  19. <s:submit method="login" value="LOGIN" cssClass="btn btn-primary"/>
  20. </div>
  21. </div>
  22. </s:form>
  23. <p class="err"><s:property value="errmsg" /></p>
  24. <jsp:include page="footer.jsp"/>

header.jsp

  1. <%@ taglib prefix="s" uri="/struts-tags" %>
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  6. <title>Hello Struts2</title>
  7. <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"/>
  8. <style>
  9. div.container{
  10. max-width:800px;
  11. }
  12. p.err{
  13. color:red;
  14. }
  15. div.result dd{
  16. margin-bottom:32px;
  17. }
  18. </style>
  19. </head>
  20. <body>
  21. <div class="container">
  22. <div class="page-header">
  23. <h1><a href="/HelloStruts2/login.action">Hello Struts2</a> <small>ver 2.3.16</small></h1>
  24. </div>

footer.jsp

  1. </div>
  2. </body>

[HelloStruts2/Java リソース/src] 直下にパッケージ名を hello とし AbstractAction.java, LoginAction.java を作成します。

AbstractAction.java

  1. package hello;
  2.  
  3. import java.util.*;
  4.  
  5. import javax.servlet.http.HttpServletResponse;
  6.  
  7. import com.opensymphony.xwork2.ActionSupport;
  8.  
  9. import org.apache.struts2.interceptor.ServletResponseAware;
  10.  
  11. import org.apache.struts2.interceptor.SessionAware;
  12.  
  13. public class AbstractAction extends ActionSupport implements ServletResponseAware,SessionAware {
  14.  
  15. private static final long serialVersionUID = 1L;
  16.  
  17. public HttpServletResponse response;
  18.  
  19. public void setServletResponse(HttpServletResponse response) {
  20. this.response = response;
  21. }
  22.  
  23. public Map sessionMap;
  24.  
  25. public void setSession(Map sessionMap) {
  26. this.sessionMap = sessionMap;
  27. }
  28. }

LoginAction.java

  1. package hello;
  2.  
  3. import org.apache.struts2.config.Result;
  4. import org.apache.struts2.dispatcher.*;
  5.  
  6. @Result(name = "main", value = "main.action", type = ServletRedirectResult.class)
  7.  
  8. public class LoginAction extends AbstractAction {
  9.  
  10. private static final long serialVersionUID = 1L;
  11.  
  12. public String errmsg;
  13. public String userId;
  14. public String password;
  15.  
  16. public String execute() throws Exception {
  17. this.sessionMap.put("userId", null);
  18. this.userId = "Struts2";
  19. return "success";
  20. }
  21.  
  22. public String login() throws Exception {
  23. if(this.password == null || !this.password.equals("pass")){
  24. this.password = null;
  25. this.errmsg = "PASSWORDは「pass」と入力してください";
  26. return "error";
  27. }
  28. this.sessionMap.put("userId", this.userId);
  29. return "main";
  30. }
  31. }

Tomcat サーバを起動し、http://localhost:8080/HelloStruts2/login.action にアクセスすると以下画面が表示されます。

struts2

パスワードに “pass” 以外の値を入力しログインするとエラーメッセージが表示され、”pass” と入力すると main アクションが見つからないというシステムエラーが表示されます。

以下の順で処理されます。

  • login.action にアクセス時、LoginAction クラスの execute() メソッドが起動。メンバー変数の userId に “Struts2″ をセット。
  • execute() メソッドの返却値が “success” のため、login-success.jsp を探すが、見つからないのでlogin.jsp の呼び出される。
  • カスタムタグの <s:textfield name=”userId> がメンバー変数に対応しており、初期値として “Struts2″ と出力される。
  • パスワードに “pass” と入力しログインボタンをクリックすると、アクションクラスの login() メソッドが起動。
  • パスワードが正しいため this.sessionMap.put() メソッドで、userId をセッションに保持し返却値として “main” を返す。
  • @Result アノテーションにて返却値が “main” の場合は、”main.action” へリダイレクトすると定義してるが、MainAction クラスが無いためシステムエラーとなる。

Struts2 の挙動ポイント

  • 初期化処理として execute() メソッドが実行される。
  • <s:submit> の method 属性がアクションクラスのメソッドに対応。
  • <s:textfield> の name 属性に対応したアクションクラスのメンバー変数値がバインドされる。
  • セッションを使用する場合は SessionAware を implements し、setServletResponse() メソッドを定義する。
  • 命名規約と無関係の URL にリダイレクトする場合は、@Result アノテーションを使用する。(Query String を付与する方法が分からない。アノテーションを使用しなければ可。後述。)

メイン画面の作成

ログイン後に遷移するメイン画面を作成します。

下記内容の main.jsp, main-send.jsp を [HelloStruts2/WebContents] 直下に作成します。

main.jsp

  1. <%@ page contentType="text/html; charset=UTF-8" %>
  2. <%@ taglib prefix="s" uri="/struts-tags" %>
  3. <jsp:include page="header.jsp"/>
  4.  
  5. <s:form cssClass="form-horizontal">
  6. <div class="form-group">
  7. <label class="col-sm-2 control-label">USER:<br/>
  8. <s:submit method="gotoProfile" value="PROFILE" cssClass="btn btn-primary btn-xs"/>
  9. </label>
  10. <div class="col-sm-10">
  11. <s:textfield name="userId" cssClass="form-control"/>
  12. </div>
  13. </div>
  14. <div class="form-group">
  15. <label class="col-sm-2 control-label">DATE:<br/>
  16. <s:submit method="resetDate" value="RESET" cssClass="btn btn-success btn-xs"/>
  17. </label>
  18. <div class="col-sm-10">
  19. <s:textfield name="sendDate" cssClass="form-control" rows="10"/>
  20. </div>
  21. </div>
  22. <div class="form-group">
  23. <label class="col-sm-2 control-label">COMMENT:<br/>
  24. <s:submit method="resetComment" value="RESET" cssClass="btn btn-success btn-xs"/>
  25. </label>
  26. <div class="col-sm-10">
  27. <s:textarea name="comment" cssClass="form-control" rows="10"/>
  28. </div>
  29. </div>
  30. <div class="form-group">
  31. <div class="col-sm-offset-2 col-sm-10">
  32. <s:submit method="send" value="SEND" cssClass="btn btn-primary"/>
  33. </div>
  34. </div>
  35. </s:form>
  36. <jsp:include page="footer.jsp"/>

main.send.jsp

  1. <%@ page contentType="text/html; charset=UTF-8" %>
  2. <%@ taglib prefix="s" uri="/struts-tags" %>
  3. <jsp:include page="header.jsp"/>
  4. <div class="result">
  5. <div class="col-sm-6">
  6. <h3>Result</h3>
  7. <dl class="result">
  8. <dt>USER:</dt>
  9. <dd><s:property value="userId" /></dd>
  10. <dt>DATE:</dt>
  11. <dd><s:property value="sendDate" /></dd>
  12. <dt>COMMENT:</dt>
  13. <dd><s:property value="comment" /></dd>
  14. </dl>
  15. </div>
  16. <div class="col-sm-6">
  17. <h3>Reference Method</h3>
  18. <dl>
  19. <dt>s:property value="comment"</dt>
  20. <dd><s:property value="comment" /></dd>
  21. <dt>s:property value="%{comment}"</dt>
  22. <dd><s:property value="%{comment}" /></dd>
  23. <dt>request.getAttribute("comment")</dt>
  24. <dd><%=request.getAttribute("comment") %></dd>
  25. <dt>request.getParameter("comment")</dt>
  26. <dd><%=request.getParameter("comment") %></dd>
  27. </dl>
  28. </div>
  29. <p><a href="main.action">Back</a></p>
  30. </div>
  31. <jsp:include page="footer.jsp"/>

[HelloStruts2/Java リソース/src] 直下に hello パッケージに MainAction.java を作成します。

MainAction.java

  1. package hello;
  2.  
  3. import java.util.*;
  4. import java.text.*;
  5.  
  6. public class MainAction extends AbstractAction {
  7.  
  8. private static final long serialVersionUID = 1L;
  9.  
  10. public String userId;
  11. public String sendDate;
  12. public String comment;
  13.  
  14. private String getDefaultDate(){
  15. Date date = new Date();
  16. SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd k:m:s");
  17. return sdf.format(date);
  18. }
  19.  
  20. private String getDefaultMessage(){
  21. return "Hello !\nMy Name is " + this.userId;
  22. }
  23.  
  24. public String execute() throws Exception {
  25. this.userId = (String)this.sessionMap.get("userId");
  26. this.sendDate = getDefaultDate();
  27. this.comment = getDefaultMessage();
  28. return "success";
  29. }
  30.  
  31. public String resetDate() throws Exception {
  32. this.sendDate = getDefaultDate();
  33. return "success";
  34. }
  35.  
  36. public String resetComment() throws Exception {
  37. this.comment = getDefaultMessage();
  38. return "success";
  39. }
  40.  
  41. public String send() throws Exception{
  42. this.userId = "User is [ " + this.userId + " ]";
  43. this.sendDate = "Date is [ " + this.sendDate + " ]";
  44. this.comment = "Comment is [ " + this.comment + " ]";
  45. return "send";
  46. }
  47.  
  48. public String gotoProfile() throws Exception{
  49. this.response.sendRedirect("/HelloStruts2/profile.action?userId=" + this.userId);
  50. return "success";
  51. }
  52.  
  53. }

ログイン処理を行うと以下画面が表示されます。

struts2

[DATE:]、[COMMENT:] の [RESET] ボタンを押すと他のフィールドの値は維持しつつ初期値を再設定します。また [SEND] ボタンを押すと以下のようフォームの送信データが表示されます。

struts2

[PROFILE] ボタンを押すとシステムエラー画面が表示されます。

以下の順で処理されます。

  • main.action にアクセス時、MainAction クラスの execute() メソッドが起動。
  • メンバー変数の userId にはセッションに記録された userId をセットし、sendDate, comment にはそれぞれ初期値をセット。
  • execute() メソッドの返却値が “success” のため、main-success.jsp を探すが、見つからないのでmain.jsp の呼び出される。
  • カスタムタグの がメンバー変数に対応しており、初期値が出力される。
  • [DATE:]、[COMMENT:] の [RESET] ボタンを押すとそれぞれ resetDate(), resetComment() メソッドが実行され、初期値が再セットされる。
  • [SEND] ボタンを押すと send() メソッドが実行され、各メンバー変数の値を加工し返却値として “send” を返す。
  • main-send.jsp が呼び出され、メンバー変数の各種値が出力される。
  • [PROFILE] ボタンを押すと gotoProfile() メソッドが実行され、AbstractAction.java で定義された response オブジェクトを使用し profile.action へリダイレクトしようとするが、ProfileAction.java が無いためシステムエラーとなる。

Struts2 の挙動ポイント

  • カスタムタグを使用せずメンバー変数を参照する場合は、requestオブジェクトの getAttribute() か getParameter() を使用する。getParameter()ではメンバー変数の値ではなく送信データそのもが取得される。
  • Query String 付きでリダイレクトする方法がよく分からないので、response.sendRedirect() を使用する。
  • response オブジェクトを使用する場合は ServletResponseAware を implements する。

更新系画面などでは、POST 処理後の更新ボタンによるフォームの再送信を防ぐために表示系の画面にリダイレクトさせることがよくあるかと思いますが、こようなケースの場合、往々にして対象データのキー値を示す Query String が必要になりますが、@Result アノテーションによるリダイレクト時の Query String の指定方法が分からなかったので、response.sendRedirect() でリダイレクトするようにしてます。

プロフィール画面の作成

[PROFILE] ボタンクリック時に遷移する画面を作成します。

下記内容の profile.jsp を [HelloStruts2/WebContents] 直下に作成します。

profile.jsp

  1. <%@ page contentType="text/html; charset=UTF-8" %>
  2. <%@ taglib prefix="s" uri="/struts-tags" %>
  3. <jsp:include page="header.jsp"/>
  4.  
  5. <h2><s:property value="userId"/>'s Profile Page</h2>
  6. <p><a href="main">Back</a></p>
  7.  
  8. <jsp:include page="footer.jsp"/>

[HelloStruts2/Java リソース/src] 直下に hello パッケージに ProfileAction.java を作成します。

ProfileAction.java

  1. package hello;
  2.  
  3. public class ProfileAction extends AbstractAction {
  4.  
  5. private static final long serialVersionUID = 1L;
  6.  
  7. public String userId;
  8.  
  9. public String execute() throws Exception {
  10. return "success";
  11. }
  12.  
  13. }

[PROFILE] ボタンクリックすると http://localhost:8080/HelloStruts2/profile.action?userId=Struts2 という URL で以下画面が表示されます。

struts2

感想

軽量版の Teeda といった感じでしょうか。IDE に強く依存してないところがいいですね。JSF の仕様の影響なのかカスタムタグのデフォルトの出力がすごい乱暴なところが HTML5 全盛の今の時代にそぐわない気がしますけど、それ以外のところは結構好きかもしれません^^; カスタムタグじゃ何ともならない部分は変にがんばらず CSS と JavaScript でなんとかすればいいんで

サンプルソース

こちらからダウンロードできます。

参考にしたサイト