struts2の最近の情報



Webアプリケーションフレームワークで有名になったなStrutsの後継、

 

Struts2

 

流石にStrutsの時のような爆発的に広まることはなかった印象です。

 

現在は、Webアプリケーションフレームワークも多様化しており、一時の

 

Strutsのように、これがデファクト...といったものも存在しない感じです。

 

実際、今でもまだStruts1が現役で利用されているシステムも数多くあると

 

いう話も聞きます。

 

前置きが長くなったのですが、

 

2013年7月18日のニュースで流れていました、

 

7月16日に公開されたStruts2の脆弱性問題、公開された翌日に

 

その脆弱性を突いた攻撃が急増していたという調査結果が出ています。

 

任意のJavaコードを実行される可能性があるとのことなので、もちろん

 

最新版へアップデートすることも大切なのですが、驚いたのは、

 

まだまだ利用されている割合が少ないと思っていた「Struts2」が、

 

これだけリアルタイムで攻撃の対象になっていたり、ニュースになったりと、

 

いつの間にか、Webシステムのフレームワークとして利用されている件数も

 

地道に増えてきていたのか...といった、Struts2を愛用している者としては、

 

こういったセキュリティに気を使わないといけなくなってきたと感じる反面、

 

Struts2の名前が大きく取り上げられることに対して嬉しい気持ちでもあります。

 

さぁ、Struts2でのシステム開発の腕をもっともっと磨いていかねば!

 

 

Google App Engine上でStruts2.2を動かす場合には、

Struts2を単独で動かす場合よりも、いくつかの設定変更や、

対応が必要になります。

 

今回は、GAE(Google App Engine)上で、Twitter連携アプリの

サンプルをStruts2.2で作成してみました。

Twitter連携には、Twitter4Jというライブラリを利用しています。

 

開発環境は、Eclipse3.4.2

Struts2.2.1

Google App Engineは、1.3.8

GWT(Google Web Toolkit)は、2.1.0

Twitter4J 2.1.6

を利用しています。

 

サンプルアプリは、ここを参照してください。

 パブリックタイムラインまたは、入力したIDのユーザタイムラインを表示します。

これらは全て認証の不要なAPIで作成可能になっています。

 

正常に稼働させるまでには、大きく2点障害がありました。

ローカル環境で、動作させるまでに必要な対応、

そしてデプロイ時のエラーを回避するための対応です。

 

1点目のローカル環境で動作させるまでの対応については、

先日のブログ記事で詳細を記述していますので、

そちらを参照してください。

※そしてさらに 追加でもう一点対応が必要になりました。

これは、<s:form action="sample"></s:from>のタグで、

フォーム入力の画面を作成する場合にGAEではエラーが発生するようです。

原因の詳細は不明なのですが、実行時に以下のエラーが発生します。

 

java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google  App Engine developer's guide for more details.


対応方法は、上記で見つからないと言われるクラスをソースフォルダに定義する必要があるようです。

詳細は、以下の記事の"Struts2を使うには"の2点目の対応が必要になります。

ふじやん雑記帳 - Google App Engine

 

追加するクラスのソースを念のため、以下にも載せておきます。

/*
 * Copyright (c) 2003 The Visigoth Software Society. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Visigoth Software Society (http://www.visigoths.org/)."
 *    Alternately, this acknowledgement may appear in the software
itself,
 *    if and wherever such third-party acknowledgements normally
appear.
 *
 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names
of the
 *    project contributors may be used to endorse or promote products
derived
 *    from this software without prior written permission. For written
 *    permission, please contact visigo...@visigoths.org.
 *
 * 5. Products derived from this software may not be called
"FreeMarker" or "Visigoth"
 *    nor may "FreeMarker" or "Visigoth" appear in their names
 *    without prior written permission of the Visigoth Software
Society.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Visigoth Software Society. For more
 * information on the Visigoth Software Society, please see
 * http://www.visigoths.org/
 */

package freemarker.core;

import java.io.IOException;

/**
 * A TemplateElement representing a block of plain text.
 *
 * @version $Id: TextBlock.java,v 1.17 2004/01/06 17:06:42 szegedia Exp $
 */
public final class TextBlock extends TemplateElement {
 private static final char[] EMPTY_CHAR_ARRAY = new char[0];
 static final TextBlock EMPTY_BLOCK = new TextBlock(EMPTY_CHAR_ARRAY, false);
 // We're using char[] instead of String for storing the text block because
 // Writer.write(String) involves copying the String contents to a char[]
 // using String.getChars(), and then calling Writer.write(char[]).By
 // using Writer.write(char[]) directly, we avoid array copying on each
 // write.
 private char[] text;
 private final boolean unparsed;

 public TextBlock(String text) {
 this(text, false);
 }

 public TextBlock(String text, boolean unparsed) {
 this(text.toCharArray(), unparsed);
 }

 private TextBlock(char[] text, boolean unparsed) {
 this.text = text;
 this.unparsed = unparsed;
 }

 /**
 * Simply outputs the text.
 */
 public void accept(Environment env) throws IOException {
 env.getOut().write(text);
 }

 public String getCanonicalForm() {
 String text = new String(this.text);
 if (unparsed) {
 return "<#noparse>" + text + "</#noparse>";
 }
 return text;
 }

 public String getDescription() {
 String s = new String(text).trim();
 if (s.length() == 0) {
 return "whitespace";
 }
 if (s.length() > 20) {
 s = s.substring(0, 20) + "...";
 s = s.replace('\n', ' ');
 s = s.replace('\r', ' ');
 }
 return "text block (" + s + ")";
 }

 TemplateElement postParseCleanup(boolean stripWhitespace) {
 if (text.length == 0)
 return this;
 int openingCharsToStrip = 0, trailingCharsToStrip = 0;
 boolean deliberateLeftTrim = deliberateLeftTrim();
 boolean deliberateRightTrim = deliberateRightTrim();
 if (!stripWhitespace || text.length == 0) {
 return this;
 }
 if (parent.parent == null && previousSibling() == null)
 return this;
 if (!deliberateLeftTrim) {
 trailingCharsToStrip = trailingCharsToStrip();
 }
 if (!deliberateRightTrim) {
 openingCharsToStrip = openingCharsToStrip();
 }
 if (openingCharsToStrip == 0 && trailingCharsToStrip == 0) {
 return this;
 }
 this.text = substring(text, openingCharsToStrip, text.length
 - trailingCharsToStrip);
 if (openingCharsToStrip > 0) {
 this.beginLine++;
 this.beginColumn = 1;
 }
 if (trailingCharsToStrip > 0) {
 this.endColumn = 0;
 }
 return this;
 }

 /**
 * Scans forward the nodes on the same line to see whether there is a
 * deliberate left trim in effect. Returns true if the left trim was
 * present.
 */
 private boolean deliberateLeftTrim() {
 boolean result = false;
 for (TemplateElement elem = this.nextTerminalNode(); elem != null
 && elem.beginLine == this.endLine; elem = elem
 .nextTerminalNode()) {
 if (elem instanceof TrimInstruction) {
 TrimInstruction ti = (TrimInstruction) elem;
 if (!ti.left && !ti.right) {
 result = true;
 }
 if (ti.left) {
 result = true;
 int lastNewLineIndex = lastNewLineIndex();
 if (lastNewLineIndex >= 0 || beginColumn == 1) {
 char[] firstPart = substring(text, 0,
 lastNewLineIndex + 1);
 char[] lastLine = substring(text, 1 + lastNewLineIndex);
 if (trim(lastLine).length == 0) {
 this.text = firstPart;
 this.endColumn = 0;
 } else {
 int i = 0;
 while (Character.isWhitespace(lastLine[i])) {
 i++;
 }
 char[] printablePart = substring(lastLine, i);
 this.text = concat(firstPart, printablePart);
 }
 }
 }
 }
 }
 if (result) {
 }
 return result;
 }

 /**
 * Checks for the presence of a t or rt directive on the same line. Returns
 * true if the right trim directive was present.
 */
 private boolean deliberateRightTrim() {
 boolean result = false;
 for (TemplateElement elem = this.prevTerminalNode(); elem != null
 && elem.endLine == this.beginLine; elem = elem
 .prevTerminalNode()) {
 if (elem instanceof TrimInstruction) {
 TrimInstruction ti = (TrimInstruction) elem;
 if (!ti.left && !ti.right) {
 result = true;
 }
 if (ti.right) {
 result = true;
 int firstLineIndex = firstNewLineIndex() + 1;
 if (firstLineIndex == 0) {
 return false;
 }
 if (text.length > firstLineIndex
 && text[firstLineIndex - 1] == '\r'
 && text[firstLineIndex] == '\n') {
 firstLineIndex++;
 }
 char[] trailingPart = substring(text, firstLineIndex);
 char[] openingPart = substring(text, 0, firstLineIndex);
 if (trim(openingPart).length == 0) {
 this.text = trailingPart;
 this.beginLine++;
 this.beginColumn = 1;
 } else {
 int lastNonWS = openingPart.length - 1;
 while (Character.isWhitespace(text[lastNonWS])) {
 lastNonWS--;
 }
 char[] printablePart = substring(text, 0, lastNonWS + 1);
 if (trim(trailingPart).length == 0) {
 // THIS BLOCK IS HEINOUS! THERE MUST BE A BETTER
 // WAY! REVISIT (JR)
 boolean trimTrailingPart = true;
 for (TemplateElement te = this.nextTerminalNode(); te != null
 && te.beginLine == this.endLine; te = te
 .nextTerminalNode()) {
 if (te.heedsOpeningWhitespace()) {
 trimTrailingPart = false;
 }
 if (te instanceof TrimInstruction
 && ((TrimInstruction) te).left) {
 trimTrailingPart = true;
 break;
 }
 }
 if (trimTrailingPart)
 trailingPart = EMPTY_CHAR_ARRAY;
 }
 this.text = concat(printablePart, trailingPart);
 }
 }
 }
 }
 return result;
 }

 /*
 * private String leftTrim(String s) { int i =0; while (i<s.length()) { if
 * (!Character.isWhitespace(s.charAt(i))) break; ++i; } return
 * s.substring(i); }
 */
 private int firstNewLineIndex() {
 String content = new String(text);
 int newlineIndex1 = content.indexOf('\n');
 int newlineIndex2 = content.indexOf('\r');
 int result = newlineIndex1 >= 0 ? newlineIndex1 : newlineIndex2;
 if (newlineIndex1 >= 0 && newlineIndex2 >= 0) {
 result = Math.min(newlineIndex1, newlineIndex2);
 }
 return result;
 }

 private int lastNewLineIndex() {
 String content = new String(text);
 return Math.max(content.lastIndexOf('\r'), content.lastIndexOf('\n'));
 }

 /**
 * figures out how many opening whitespace characters to strip in the
 * post-parse cleanup phase.
 */
 private int openingCharsToStrip() {
 int newlineIndex = firstNewLineIndex();
 if (newlineIndex == -1 && beginColumn != 1) {
 return 0;
 }
 ++newlineIndex;
 if (text.length > newlineIndex) {
 if (newlineIndex > 0 && text[newlineIndex - 1] == '\r'
 && text[newlineIndex] == '\n') {
 ++newlineIndex;
 }
 }
 if (new String(text).substring(0, newlineIndex).trim().length() > 0) {
 return 0;
 }
 // We look at the preceding elements on the line to see if we should
 // strip the opening newline and any whitespace preceding it.
 for (TemplateElement elem = this.prevTerminalNode(); elem != null
 && elem.endLine == this.beginLine; elem = elem
 .prevTerminalNode()) {
 if (elem.heedsOpeningWhitespace()) {
 return 0;
 }
 }
 return newlineIndex;
 }

 /**
 * figures out how many trailing whitespace characters to strip in the
 * post-parse cleanup phase.
 */
 private int trailingCharsToStrip() {
 String content = new String(text);
 int lastNewlineIndex = lastNewLineIndex();
 if (lastNewlineIndex == -1 && beginColumn != 1) {
 return 0;
 }
 String substring = content.substring(lastNewlineIndex + 1);
 if (substring.trim().length() > 0) {
 return 0;
 }
 // We look at the elements afterward on the same line to see if we
 // should strip any whitespace after the last newline
 for (TemplateElement elem = this.nextTerminalNode(); elem != null
 && elem.beginLine == this.endLine; elem = elem
 .nextTerminalNode()) {
 if (elem.heedsTrailingWhitespace()) {
 return 0;
 }
 }
 return substring.length();
 }

 boolean heedsTrailingWhitespace() {
 if (isIgnorable()) {
 return false;
 }
 for (int i = 0; i < text.length; i++) {
 char c = text[i];
 if (c == '\n' || c == '\r') {
 return false;
 }
 if (!Character.isWhitespace(c)) {
 return true;
 }
 }
 return true;
 }

 boolean heedsOpeningWhitespace() {
 if (isIgnorable()) {
 return false;
 }
 for (int i = text.length - 1; i >= 0; i--) {
 char c = text[i];
 if (c == '\n' || c == '\r') {
 return false;
 }
 if (!Character.isWhitespace(c)) {
 return true;
 }
 }
 return true;
 }

 boolean isIgnorable() {
 if (text == null || text.length == 0) {
 return true;
 }
 if (!isWhitespace()) {
 return false;
 }
 // trick here
 boolean atTopLevel = true;
 TemplateElement prevSibling = previousSibling();
 TemplateElement nextSibling = nextSibling();
 return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
 && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
 }

 private boolean nonOutputtingType(TemplateElement element) {
 return (element instanceof Macro || element instanceof Assignment
 || element instanceof AssignmentInstruction
 || element instanceof PropertySetting
 || element instanceof LibraryLoad || element instanceof Comment);
 }

 private static char[] substring(char[] c, int from, int to) {
 char[] c2 = new char[to - from];
 System.arraycopy(c, from, c2, 0, c2.length);
 return c2;
 }

 private static char[] substring(char[] c, int from) {
 return substring(c, from, c.length);
 }

 private static char[] trim(char[] c) {
 if (c.length == 0) {
 return c;
 }
 return new String(c).trim().toCharArray();
 }

 private static char[] concat(char[] c1, char[] c2) {
 char[] c = new char[c1.length + c2.length];
 System.arraycopy(c1, 0, c, 0, c1.length);
 System.arraycopy(c2, 0, c, c1.length, c2.length);
 return c;
 }

 boolean isWhitespace() {
 return text == null || trim(text).length == 0;
 }

}

 

次に、2点目ですが、ローカルでの実行に問題が無くても

デプロイ時に不明なエラーが発生しました。エラーの内容は、

こちらの内容を参照してください。

私の環境では、web.xml および、appengine-web.xml で、

ファイルがパース出来ないという意味合いのエラーの内容です。

 

Received IOException parsing the input stream for <該当のxmlファイルのパス>

 

上記のページでも最後の方で解決していますが、

結局のところ、該当のxmlファイル内のコメントを全て削除することで解決しました。

納得はいきませんが、とりあえず動作させる事を優先で対応しました。

 

以上で、やっと・・なんとか、

GAE+Struts2.2(Zero Configuration、Convention-Plugin)+Twitter連携アプリが

完成しました。

サンプルのアプリは、ここを参照してください。

 

Twitter連携用のAPIとしては、

 Twitter4J#getPublicTimeline メソッド

 Twitter4J# showUser メソッド

 Twitter4J#showStatus メソッド

を利用しています(いずれも認証不要)

 

今後も、様々なTwitter連携APIのサンプルを作成してみたいと思います。

 

Struts2の大きな特徴である、

Zero Configuration(アクションマッピング等の設定をファイルに記述しない)

ですが、2.0系で使用していた、Codebehindプラグインが非推奨なった。

 

現時点(2010/11/3)での、Struts2のバージョンも2.2.1であるため、

最新のバージョンへの更新および、Codebehindプラグインをやめ、

Conventionプラグインへの切換えをおこなってみた。

しかも、GAE(Google App Engine)環境で。

 

仕組み自体はそう大して変わっていないと思っていたが・・・

やはり簡単には移行できなかったので、あれこれと調べる羽目になり、

時間も結構かかったが、なんとか動作するようになり、

Conventionプラグインについても見えてきた。

 

とりあえず、つまづき度が大きかった順に・・・

※これはGAE(Google App Engine)でテストしていたのも重なり、障害の

切り分けに時間がかかってしまったような気もするが・・・

障害としては、URLで該当のアクションwp呼び出すURLを入力しても、

「アクションが見つからない」とのメッセージ。

 

"There is no Action mapped for namespace /"

 

Codebehindプラグインを使用していた時にもハマったエラー。

その際は次のように、"actionPackages"というパラメータを

web.xmlで設定すれば、指定した名前空間の下にあるアクションクラスを

参照してくれるようになった。

 

 <filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  <init-param>
   <param-name>actionPackages</param-name>
   <param-value>com.pg2se.sample.action</param-value>
  </init-param>
 </filter>

しかし、今回は同様のパラメータ設定をしていてもうまくアクションが呼び出されない・・

そして、どこかのページで、Struts.xml の配置が必要で、

"<constant name="struts.devMode" value="true" />"の設定をしろ!のようなページを

見てしまったため、

"access denied (java.io.FilePermission jar:file:\C:\Projects\gae\war\WEB-INF\lib\struts2-core-2.2.1.jar read)"

というエラーも出るようになったりで、Struts2が悪いのか、GAEか、どこがどう悪いかが見当つかなくなった。

 

もう1点が、Resultのjspファイルが見つからない旨のエラーが発生したこと。

 

"No result defined for action "のエラーメッセージ。

 

アクションは実行されたようだだが、ビューファイルが見つからない。

これも今までは普通にjspを配置すれば動いていたのだが・・・

 

結局、順番にひとつづつ試していってわかったこと。

1.Struts.xml は無くても動作はした。

 

2.Struts.xml の"<constant name="struts.devMode" value="false" />"でvalueはfalseにする。

  ※これはGAE環境だからかもしれない・・

 

3.GAE環境では、セキュリティマネージャを無効にする必要がある

  ※web.xmlにリスナーを設定してセキュリティマネージャを無効にする。

 

     <listener><!-- GAE関連で必要なListenerを設定 -->

         <listener-class>com.pg2se.google.InitListener</listener-class>

     </listener>

 

    public class InitListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {

        @override

        public void contextInitialized(ServletContextEvent arg0) {
            OgnlRuntime.setSecurityManager(null);
        }

    }

  こんな感じ

 

4.これもGAE環境でのエラーのはず。

  クラスが見つからない旨のエラー。→xalan関連のクラス。

  こちらはxalanのライブラリをダウンロードして手動で追加する必要があった。

 

5.アクションクラスが見つからない件

  上記の設定で、すべて再起動、再ビルドするとアクションは実行されるようになった。

  ※因みにConventionプラグインでは、web.xmlでの、"actionPackages"パラメータは

  記述しなくて良く、アクションクラスの格納されたパッケージは自動検出される。

  →action,actions,struts,struts2の名前のついたパッケージが対象

 

6.ビューファイルが見つからない件

  Conventionプラグインでは、ビューファイル(jsp等)の配置場所もデフォルトで設定

  されている。それが、/WEB-INF/content/ です。

  ※そりゃ、今までどおりでは、resultのファイルが呼び出されない訳だ。

  取り急ぎ、jspのビューファイルを、上記場所に移動。

 

以上で、なんとかこれまでどおり、Struts2が動作するようになった。(しかもGAE上で)

Conventionプラグインン・・・なかなか癖があり、手ごわかった。

 

さて、これまでは、Conventionプラグインのデフォルトの設定を生かしたが、

カスタマイズするには?だけど・・・、方法はあります。

 

struts.xmlファイルに<constant>タグで設定を記述することによって、既定値を変更することが可能です。

例)<constant name="struts.convention.result.path" value="/WEB-INF/jsp/"/>

  <constant name="struts.convention.package.locators" value="act"/>

 

他にも設定変更の詳細や、今回ハマった内容のほとんどについては、

以下のページで詳しく解説されています。

Struts 2入門(6)?XML不要のZero Configuration?

Google App Engine/Struts2の連携

 

今後、Struts2.2、GAEを連携させてより深いところまで、

Struts2を追及していこうと思います。

twitterと連携するアプリケーション作成のためのAPI群です。

 

OAuthというツイッターの認証機能を利用できる仕組みがあるので、

お手軽に、しかもユーザにとっても安心・安全に利用できる仕組みになってる。

 

OAuthを利用するためには、アプリケーションを登録しないといけないので、

http://twitter.com/apps

上記より、登録します。

 

登録すると、Consumer key、Consumer secret という情報を取得できるので、

その情報を利用して、ユーザを、twitterの認証ページに誘導し、アプリの利用の

同意を頂くと、アプリから、そのユーザができる操作を代行できるようになる。

 

今回は、好きな"お酒"のことに特化したつぶやきをするためのアプリ、

「つい飲み」というWebアプリを作成してみた。

"いまなにのんだ?"をコンセプトに、飲んだお酒と"つぶやき"を投稿するアプリ。

(また、#sake というカテゴリが自動で設定されるようになってます)

 

1.ログインボタンを押す

2.Consumer key、Consumer secretを 設定しtwitterの認証ページにリダイレクト

3.認証が成功すると、事前に設定したアプリのページに自動で戻ってくる

4.AccessTokenという、認証が成功したユーザを操作するための権限を取得する

5.AccessTokenを利用して、"つぶやく" など、必要な処理を呼び出す。

 

今回は、Java(Struts2)を利用して、Webアプリケーションを作成した。

twitter4jというOAuthも手軽に利用できるライブラリがあったので。

 

要点のコードを抜粋

//最初にtwitterのサイトへ誘導し認証させる

Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer("xxxxxxxxxx", "yyyyyyyyyy");
RequestToken token = twitter.getOAuthRequestToken();
sessionMap.put("twitter", twitter);
response.sendRedirect(token.getAuthenticationURL());

 

//次に認証が成功すると任意のページへリダイレクトされるので権限を取得

Twitter twitter = (Twitter)sessionMap.get("twitter");
if (twitter != null) {
 AccessToken token = twitter.getOAuthAccessToken();
 sessionMap.put("token", token);

}

 

//権限を利用してアプリから"つぶやき"を投稿

AccessToken token = (AccessToken)sessionMap.get("token");
if (token != null) {
 Twitter twitter = (Twitter)sessionMap.get("twitter");
 twitter.updateStatus("つぶやきを投稿");
}

 

※これでひととおりの操作が可能です。

つぶやき以外にも様々な操作がAPIで提供されてます。

 

※携帯からの利用についてですが、twitterの認証機能は、https対応ですが

携帯で表示時にtwitterの証明書ではエラーになるようです。

なので、HTTPリクエストのヘッダーで、携帯からのアクセスかを判断して、

携帯からの場合は、HTTPSを、HTTPへ書き換えてあげるとうまくいきます。

セキュリティが弱くなるけれど、twitterで通常の携帯ログインもHTTPのよう

なので、とりあえず問題はないかと...。


 とりいそぎ最低限のラインで作成してみたので、

また機能アップしていきたいと思います。

 

続 Struts 2入門

| コメント(0) | トラックバック(0)

CodeZineで、Struts2入門(1)?(8) の続編、

続 Struts 2入門が始まりました!!

 

Strutsは以前から利用しているので、Struts 2になった今後も是非とも

発展し続けていってほしいと思う。

自分自身でもStrtuts2でWebアプリケーションを作っているので、

いろいろと情報発信して、Struts2の発展に貢献できればなと思う。

 

Strutsの時よりも、情報源が少なく感じ、なかなか活用しきれていない

面があるので、こういった連載が始まるのはありがたい!!

記事を見てもらうほうが詳細に理解できますが、さわりだけわかれば

良い方は以下を参照してください。

 

続 Struts 2入門

基本機能のおさらいから2.1系で採用された機能の説明や実装例を紹介

(1)Struts 2の同期処理を手助けするExecuteAndWaitインターセプタ

 

処理に時間がかかるActionクラスの処理状況を監視し、その結果を自動的に

判断するためのExecuteAndWaitインターセプタの使用例を解説。

 

Actionクラスにはメインのロジックを記述するだけで、あとは設定のみで、

同期処理を実現することが可能になります。

 

具体的な手法としては、

ブラウザの自動リフレッシュ機能を利用し、Actionクラス内の時間の

かかる処理を、自動で途中中断して、リフレッシュを設定したページを表示。

(Actionの実行は継続されセッションに維持される)

 

再度リフレッシュしてサーバにアクセスすると、中断したActionの状況を

再度監視できるようになっている。

まだ処理中の場合は、再度リフレッシュページへ、処理が完了した場合は

完了用のページが表示されるという仕組みです。

 

これらを、上記のExecuteAndWaitインターセプタ(アノテーションの機能)

を利用することで設定をおこなうことで、簡単に利用することができます。

 

インターセプタ(汗)

まだぜんぜん利用できていないので、実用できるように勉強します。

次回もインターセプタの機能についての記事のようです。

 

<html:base>タグ

<html:base>タグは、Struts1で用意されていたタグライブラリである。

このタグは、HTMLの<base>タグを出力するためのタグです。

<base>タグは、<head>タグ内で記述し、<base href="[絶対パス]"> という書式になります。このタグは、HTMLファイルに相対パスで記述されたURIの基準となるURIを指定します。基準となるURIは、[絶対パス] 部分で指定された絶対パスです。

 

Struts1

このタグは、リクエスト時にフィルターの機能を用い、URL書き換えをおこなう場合に便利です。URL書き換えをした場合に、見かけ上のURLの階層が変わってしまったりすると、相対パスで記述しているリンクや、画像ファイルが正しくリンクされないようになってしまうからです。

この時に<html:base/>タグを記述していると、相対パスの基準となるURIが固定される(出力されるファイルの階層に固定される)ため、URL書き換えで見た目のURLの階層がいくら変化しても、各リソースのリンクには全く問題がなくなります。

 

Struts2

Struts2のタグライブラリには残念ながら、<html:base>に代わるものが存在しません。これは、画像やリンクなどの各リソースのURLの指定をするために、<s:url value="[リソース]"> タグを使うことで、<base> タグを指定する必要がなくなるためだからだろうと思いました。

しかしながら、各リソースのURLを動的に指定する必要がある場合、value属性に、<s:property> タグを入れ込んだりしての、URLの動的作成がうまくいきませんでした(もしかしたら、うまくやれば可能なのかもしれません...) さらに、フィルターによるURLの動的書き換えを組み合わせると、URLが2重になったりと先が見えなかったので、Struts1のように<base>タグを自前で出力させる方法を考えました。

 

<base>タグの出力

発想はいたって単純ですが、フィルターの処理中に、ベースとなるURLをrequestオブジェクトに保存しておき、viewの部分でそれを取り出し、<base>タグのhref属性に指定してあげるという方法です。

ポイントはベースとなるURLをどのように作成するところですが、以下のようにすることで簡単に求められそうです。

 

  //ベースURLの設定
  String url = req.getRequestURL().toString();
  int len = url.length() - req.getRequestURI().length();
  req.setAttribute("htmlbase",  url.substring(0, len) + req.getContextPath() + "/");

 

そして、viewでは以下のように呼び出すだけです。

   

    <%@taglib prefix="s" uri="/struts-tags" %>

    <head>

        <base href="<s:property value="#request.htmlbase"/>"/>

    </head>

 

以上で、Struts1の<html:base/>の処理と同様のことができるようになりました。

※他にもっと良い方法があるかもしれませんが、リソースのURLを動的に作成したり、フィルターにてリクエストのURLを書き換えてフォワードさせたりという処理を組み合わせると、思うような処理ができませんでした。

 

また、Struts2で、リクエストのURLを書き換え、フォワードしてアクションを実行させようとしてもできないトラブルの解決方法は以下を参照ください。

Struts2 RequestDispatcher の動作について

 

getRequestDispatcher foward

Strutsをはじめ、上記のようにリクエストのフォワーディングをおこなうことは少なくないのではないでしょうか?URLReWriteのような形で特定のURLパターンにマッチした場合にアドレスを変換したり、*.html のアドレスでアクセスされた場合に内部的には、*.jsp や、*.do などの動的な処理に転送したい場合などが考えられる。

また、サイトにアクセス制御がかけられている場合は、認証されていない場合にログイン画面に転送するなどにも利用される。

 

Struts2での問題

ここで今まで利用してきた Struts1では、上記のような処理を、通常、web.xml で設定された、フィルターの機能を利用しておこなっていた方も多いのでしょうか?これは非常に簡単なコーディングにてリクエストの転送を問題なくおこなえていました。

 

web.xml

  <filter>
    <filter-name>accessFilter</filter-name>
    <filter-class>com.pg2se.AccessFilter</filter-class>
  </filter>
 
  <filter-mapping>
    <filter-name>accessFilter</filter-name>
    <url-pattern>/security/*</url-pattern>
  </filter-mapping>

  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

com.pg2se.accessFilter#doFilter

  if (checkLogin == false) {

    request.getRequestDispatcher("/login.do").forward(req, res);

  } else {

    chain.doFilter(req, res); 

  }

 

同様の処理を Struts2 に移植または実装しようとした際に、何も考えずコーディングした場合に、リクエストのフォワードがうまく動作しない現象が発生してしまうのではないでしょうか?

 

具体的には、

request.getRequestDispatcher("/login.action").forward(req, res);

 

と記述しても、

"/logon.action" is not available

 

となってしまいます。最初は原因はわからなくて戸惑うかも知れませんが、これは、Struts1 と Struts2 のアクションが起動される仕組みが変更されているからです。

 

web.xml

 <filter>
  <filter-name>AccessFilter</filter-name>
  <filter-class>com.pg2se..AccessFilter</filter-class>
 </filter>

 

 <filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
 </filter>

 

 <filter-mapping>
  <filter-name>AccessFilter</filter-name>
  <url-pattern>/security/*</url-pattern>
 </filter-mapping>
 

 <filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

 

Struts2 では、一般的に、上記のように記述しているのではないでしょうか?

この、Struts1 の記述と、Struts2 での記述の違いをみると、アクションの起動が、Struts1 では、servlet-mapping が利用されているのに対し、Struts2 は、filter-mappping を利用しているという点です。

 

AccessFilter の中で、Struts1と同じように、getRequestDispatcherで、foward をおこなっても、アクションは起動されないようになっている。なぜなら、Struts2 では、アクションはフィルターで動作するようになっおり、デフォルトでは、リクエスト時にはフィルターが呼び出されるが、foward時にはフィルターが呼び出されないようになっているからである。

 

これを回避するのは意外と簡単で、web.xml で設定できる。

設定の詳細はこちらのページで詳しく書かれています。

 

web.xml

 <filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>REQUEST</dispatcher>
 </filter-mapping>

 

アクションの、filter-mapping にて、<dispatcher>要素を記述する。

記述できる値は、REQUEST、FORWARD、INCLUDE、ERROR である。

ここで、FORWARDを指定すれば、getRequestDispatcher#foward をおこなった際にも、

該当のフィルターが起動され目的の処理をおこなうことができるようになる。

 

 

はじめに

今回はStruts2を利用した、より実践的なアプリケーションの作成をおこなってみたいと思います。今回も前回と同様、ZeroConfigurationで開発をおこなっています。

Struts2の開発の準備がまだ整っていない場合は以下も参考にしてください。

 

このアプリケーションの解説の中では今後さらにしっかりとしたアプリケーションを開発していくにあたってのポイント、改良点なども記述していますので是非参考にしてください。

 

アプリケーションの概要

このアプリケーションは、シンプルな掲示板アプリケーションです。

複数の端末から共通の掲示板に情報を書き込み、その内容を共有するという単純なものです。情報には、投稿者の名前とメッセージ、そして投稿日時と投稿した端末のIPアドレスを自動で取得した内容が含まれます。

アプリケーションの仕様について簡単にまとめます

  • 掲示板のURLにアクセスすると現在の掲示板の情報が一覧表示されます。
  • "更新"ボタンを押すと、掲示板の情報が最新表示されます。
  • 掲示板の情報は、投稿日が新しいものから順に表示されます。
  • "名前"と"メッセージ"を任意に入力し、"投稿"ボタンを押すと、掲示板に情報が
    登録され、掲示板の内容が最新表示されます。
  • "名前"、"メッセージ"のいずれかが未入力の場合はエラーメッセージが表示され
    掲示板に情報は登録されません。

 

  • アプリケーション画面

simple_board.jpg 

 

ソースコード解説

シンプルな機能の掲示板ではありますが、アプリケーションとしてはMVCモデルを意識し、今後の拡張性も含めながら作成してみました。

 

  • モデル

BoardData .java

掲示板へ登録する情報を格納するデータを保持するクラスです。

データの保持以外の機能は特にありません。  

package com.pg2se.sample.model.board;

public class BoardData {

    private String postDate;
    private String name;
    private String message;
    private String remoteAddress;
   

 public String getPostDate() {
  return postDate;
 }
 public void setPostDate(String postDate) {
  this.postDate = postDate;
 }
 
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 
 public String getMessage() {
  return message;
 }
 public void setMessage(String message) {
  this.message = message;
 }
 
 public String getRemoteAddress() {
  return remoteAddress;
 }
 public void setRemoteAddress(String remoteAddress) {
  this.remoteAddress = remoteAddress;
 }

}

 

Board.java

複数の端末で共有する掲示板のオブジェクトと掲示板に情報を登録する機能を持ったクラスです。

package com.pg2se.sample.model.board;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Vector;

public class Board {

    private static List<BoardData> board = new Vector<BoardData>();
    private static SimpleDateFormat sdformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    
    public static List<BoardData> addChatData(String name, String message, String remoteAddress){
     BoardData data = new BoardData();
     data.setName(name);
     data.setMessage(message);
     data.setRemoteAddress(remoteAddress);
     data.setPostDate(sdformat.format(new Date()));
     board.add(0, data);
     return board;
    }
    
    public static List<BoardData> getChatData(){
     return board;
    }

}

 

  • ビュー
  • board/board.jsp

    掲示板への情報の投稿と掲示板の情報の表示をおこないます。

     

    入力エラーの内容は<s:actionerror/>タグによって出力されます。

     

    "ValueStack"をコードで記述している部分ですが、board.jspをアドレスに指定されて呼び出された場合にアクションが呼び出されず掲示板の最新情報が取得できていないため無理やり取得しています。Struts1の場合は、フォームに関連付けられたformbeanが初期化されるため初期表示も可能だったんですが...。もっと良い方法あればいいんですが。

     

    また、"remoteAddress"をここで取得していますが詳細は後述します。

     

    ボタンは情報投稿用のボタンと、最新情報取得用の更新ボタンがありますが、method属性を指定して、呼び出すアクションクラスのメソッドを指定できます。

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@page import="com.pg2se.sample.model.board.Board"%>
    <%@page import="com.opensymphony.xwork2.util.ValueStack"%>
    <%@page import="org.apache.struts2.ServletActionContext"%><html>
    <%@taglib prefix="s" uri="/struts-tags" %>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>シンプル掲示板</title>
    </head>
    <body>
        <h1>シンプル掲示板</h1>
        <h2>struts2で作成するサンプルアプリケーション</h2><hr/>
       
        <s:form action="board.action" theme="simple">
          <input type="hidden" name="remoteAddress" value="<%= request.getRemoteAddr() %>"/>
          <s:text name="名前"/> <s:textfield name="name" size="10"/>
          <s:text name="メッセージ"/> <s:textfield name="message" size="60" value=""/>
          <s:submit value="投稿"/><s:submit value="更新" method="update"/>
        </s:form><hr/>
       
        <s:actionerror/>
       
        <table border="1">
     <%
      ValueStack stack = (ValueStack)request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
      if(stack!=null && stack.findValue("data")==null){
       stack.set("data", Board.getChatData());
      } 
     %>
        <s:iterator value="data" status="stat">
            <s:if test="#stat.first">
              <th>日時</th>
              <th>名前</th>
              <th>テキスト</th>
              <th>IPアドレス</th>
            </s:if>
            <tr>
              <td><s:property value="postDate"/></td>
              <td><s:property value="name"/></td>
              <td><s:property value="message"/></td>
              <td><s:property value="remoteAddress"/></td>
            </tr>
        </s:iterator>
        </table>
    </body>
    </html>

     

    • コントローラ

    BoardAction.java

    掲示板の情報の登録と最新表示をおこなうためのアクションです。

     

    メッセージの投稿用にはデフォルトのアクションexecuteメソッドを、最新情報取得用のアクションにはupdateを使用します。

     

    ActionSupportクラスを継承することで、validation(バリデーション:検証)やメッセージ・エラーメッセージを簡単に設定することができます。validateメソッドを実装するだけでよいのですが今回は使用しませんでした。理由は、validateメソッドはすべてのアクションに対して実行されてしまうため、今回バリデーションの必要がないupdateメソッドに適用されたくなかったためです。今回は自前のisValidメソッドで検証をおこなっています。※xmlファイルでバリデーションの定義をする場合は検証を除外するメソッドを設定できたりするようです。他にも方法はあるようなのですがわかりませんでした、

     

    "remoteAddress"についてはjspファイルから渡されるようにしています。本来はアクションクラスで、requestオブジェクトから取得したいのですがActionSupportクラスからは取得できないようです。取得するためには、ServletRequestAwareインターフェイスを実装する必要があります。そうすると実行時にHttpServletRequestオブジェクトが注入されるようです。

     

    今後の拡張として、掲示板の情報の編集・削除等のメンテナンスや掲示板の情報をデータベースへ保存し永続化するという点が考えられます。

    package com.pg2se.sample.action.board;

    import java.util.List;
    import com.opensymphony.xwork2.ActionSupport;
    import com.pg2se.sample.model.board.Board;
    import com.pg2se.sample.model.board.BoardData;

    public class BoardAction extends ActionSupport {

     private String name;
        private String message;
        private String remoteAddress;
        private List<BoardData> data;
     private static final long serialVersionUID = 1L;
       
     public String getName() {return name;}
     public void setName(String name) { this.name = name;}

     public String getMessage() {return message;}
     public void setMessage(String message) {this.message = message;}
     
        public String getRemoteAddress() {return remoteAddress;}
     public void setRemoteAddress(String remoteAddress) {this.remoteAddress = remoteAddress;}

     public List<BoardData> getData() {return data; }
     public void setData(List<BoardData> data) {this.data = data;}
     
     //execute
     public String execute() {

      if(isValid()){
      
          data = Board.addChatData(name, message, remoteAddress);
         
         } else {

          data = Board.getChatData();
         }
      
         return "success";
        }
     

     //update
     public String update() {
      
      data = Board.getChatData();
      
      return "success";
        }
     
     //validate
     public boolean isValid() {
      if(name == null || name.equals("")){
       addActionError("名前を入力してください");
      }
      if(message == null || message.equals("")){
       addActionError("メッセージを入力してください");
      }
      
      return !hasActionErrors();
     }
    }

    Zero Configuration

    それでは、Struts2での最初のアプリケーションを作成してみたいと思います。

    Struts2の特徴の一つに"Zero Configuration"があります。

    これは、Struts1で、xml地獄と呼ばれた、一連のアクションを実行するため、

    各コンポーネントを連携するために必要な設定を、xmlの設定ファイルで記述し、

    その設定ファイルが膨大になってしまいがちになる短所を改善するための機構

    です。

     

    Struts1で、JSP-ActionForm-Action-JSPの連携を全てstruts-config.xmlで設定

    していたものを、Struts2では、JSP-POJO-JSPの最小構成で、連携の設定を

    記述することなく動作させることができます。

     

    では具体例を見ていきましょう。

     

    Hello World!!

    例の如く、簡単なHello World!!アプリケーションを作成していきます。

    ポイントを絞って説明しますので不足点あればコメント欄で質問ください。

    (環境)

    eclipse3.4.1(tomcatPlugin)

    jdk1.6.0_02

    tomcat-6.0.14

    struts2.0.14

     

    20090118.jpg

    1. プロジェクトの作成
      "動的Webプロジェクト"を選択しプロジェクトを作成します。
      注)デフォルトの、classesディレクトリがWEB-INFの外にあり、そこに後述の
      struts.xmlを配置しても認識されなかったので、classesは、WEB-INFの下に
      設定してください。

      ライブラリを配置します。struts2のファイルの中から以下をlibに配置します。
      ・commons-logging-1.0.4.jar
      ・freemarker-2.3.8.jar
      ・ognl-2.6.11.jar
      ・struts2-codebehind-plugin-2.0.14.jar
      ・struts2-core-2.0.14.jar
      ・xwork-2.0.7.jar
      ※codebehindプラグインが、Zero Configurationのミソで、JSPが配置されている
      ディレクトリとアクション名より自動的にアクションクラス(POJOでも可)を探します。
      またアクションクラス名と戻り値にて自動的にJSPファイルを探します。

    2. Web.xml
      ポイントは、Struts1と同様、フィルターおよびパターンにてリクエストをStruts2で処理
      できるように設定を書いている点と、またまたZero Configurationの肝になる、
      actionPackagesというパラメータ設定です。ここにパッケージ名を記述しますが、
      指定したパッケージが、Webアプリケーションのトップディレクトリと同じ階層となり
      JSPとアクションの自動での紐づけの対象となります。
      例えば、/webapp/index.jsp の中で、"index.action"を呼び出すと、
      (actionPackages).IndexAction クラスの execute メソッドを実行しようとします。
      下の私の例では、com.pg2se.sample.action を指定していますので、
      com.pg2se.sample.action.IndexAction#executeメソッドが呼び出されます。
      ※何も設定していなければ"execute"メソッドが呼び出されます。

      <?xml version="1.0" encoding="UTF-8"?>
      <web-app id="WebApp_ID" 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">
       
      <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
        <init-param>
          <param-name>actionPackages</param-name>
          <param-value>com.pg2se.sample.action</param-value>
        </init-param>
      </filter>
      <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>

      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
      </web-app>


    3. Struts.xml
      Struts.xmlは、classesの直下に置くことで認識されます。冒頭で書いたように、
      WEB-INF以下に配置しないとうまく動作しませんでしたので適宜設定を変更
      してください。
      ※Zero Configurationで作成するため、記述は最低限の以下だけでOKです。

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE struts PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
          "
      http://struts.apache.org/dtds/struts-2.0.dtd">
      <struts>
          <constant name="struts.enable.DynamicMethodInvocation" value="true" />
          <constant name="struts.devMode" value="false" />
      </struts>


    4. index.jsp
      今回は、Webアプリケーションのトップディレクトリのindex.jspにアクセスし、ボタンを
      押すと、"Hello World!!"のメッセージが表示される簡単なアプリケーションにしています。
      ポイントは、Struts2のタグライブラリ(プレフィックス:s)の宣言をしている点と、
      Formのアクション設定に"index.action"を設定している点、プロパティ"message"を
      表示している点です。最初にJSPにアクセスした場合はプロパティは設定されていま
      せんのでボタンのみ表示されます。

      <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
      <%@ taglib prefix="s" uri="/struts-tags" %>
      <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "
      http://www.w3.org/TR/html4/loose.dtd">
      <html>
        <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
          <title>Struts2</title>
        </head>
        <body>
          <s:form action="index.action" theme="simple">
            <s:property value="message"/><br/>
            <s:submit value="メッセージ"/>
          </s:form>
        </body>
      </html>


    5. IndexAction.java
      ここでのポイントは、パッケージの設定を、actionPackagesパラメータに
      合わせている点と、Actionの実行設定をしていない際のデフォルトで実行
      される、executeメソッドを実装している点と、ActionFormが無くなった
      代わりに、このクラスにプロパティを設定し、executeメソッドで、プロパティ
      に値をセットしている点、そしてクラスがPOJOである点です。
      ここまでの設定が正しければ、JSPよりボタンが押されると、execute
      メソッドが実行されるはずです。

      package com.pg2se.sample.action;

      public class IndexAction {
       
        private String message;

        public String getMessage() {
          return message;
        }

        public void setMessage(String message) {
          this.message = message;
        }
       
        public String execute() {
          message = "hello world!!";
          return "success";
        }

      }


    6. 最後に
      Actionが実行した最後に"success"を戻り値で返していますが、今回の場合、
      index-success.jspが優先で呼ばれるのですが、ファイルが存在しない場合は、
      次にindex.jspが呼ばれます。(結果的に再度呼び出し元のJSPが実行される
      ことになります)
      この際に、今度はActionでmessageプロパティが設定されていますので、その
      内容が、JSPの、

      <s:property value="message"/>

      の部分で表示されます。

      以上で、超簡単Struts2アプリが完成しました。

      次回は、より実践的にシンプルな掲示板アプリを作成して見たいと思います。

    最初に

    まずStruts2をインストールする前提条件ですが、

    Java SE(JDK)がインストールされていること、

    Tomcatがインストールされていることを前提としています。

    ※アプリケーションサーバについてはTomcatではなくても問題ないですが、

    その場合は適宜、Tomcatに関連する説明文は読み替えてください。

     

    Struts2について

    Struts2についての説明についてはこちらを参照してください。

    Struts2はフレームワークですので、それの単体では特に意味をなさないものですが、

    サンプルのアプリケーションが添付されているので、サンプルの動作確認と、

    実際に、Strtuts2のフレームワークを利用した最初のアプリケーションを

    作成して見たいと思います。

     

    ダウンロード

    Struts2のダウンロードはここから行います。

    2008年12月22日時点での最新版は、struts-2.0.14です。

    ここでは、Full Distribution である、struts-2.0.14-all.zip を

    上記リンクよりダウンロードして任意の場所に解凍しておきます。

     

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

    解凍した場所の/apps/struts2-showcase-2.0.14.warを短い名前に変更します。

     →showcase.war に変更

    変更したファイルを、Tomcatのアプリケーションを配備場所にコピーします。

     →TOMCAT_HOME/webapps/showcase.war

     

    以上でTomcatを起動すると、標準の設定では、

    http://localhost:8080/showcase/ でアクセスすると以下の画面が表示されるはずです。

    20081222_01.jpg

    以下のように様々なサンプルが公開されていますので、

    まずはサンプルを色々と触ってみて動作の確認をしてみてください。

    • Home
    • Ajax Theme for Struts Tags
    • Ajax Chat
    • Action Chaining
    • Config Browser
    • Conversion
    • CRUD
    • Execute & Wait
    • File Download
    • File Upload
    • Freemarker
    • Hangman
    • JavaServer Faces
    • Person Manager
    • Tags
    • Tiles
    • Token
    • Validation
    • Help 

     

    次回は実際に、Struts2のフレームワークを利用して、

    オリジナルのアプリケーションを作成してみます。