GAEでSpringのValidation機能を使おうとしてハマった

GAE上でSpringMVCを動かしています。formからの入力にValidationを試してみました。で、どっぷりハマったので、とりあえず原因を速報。

“hibernate-validator 5.*” don’t work on GAE!!

————————————————————–

使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)
Spring Framework 3.2.2.RELEASE
Commons Collections 3.2.1
Commons Lang 2.6
Commons Logging 1.1.2
Apache Velocity 1.7
hibernate-validator-4.3.0.Final ( + jboss-logging-3.1.0.CR2.jar, validation-api-1.0.0.GA.jar)


(1)NG編

まず、現時点での最新版である、
hibernate-validator 5.0.0.Final
を使ってみました。I try to use latest version “hibernate-validator 5.0.0.Final”.

ダウンロード先はこちら。
Downloads – JBoss Community

ダウンロードしたhibernate-validator-5.0.0.Final-dist.tar.gzの中にある、
hibernate-validator-5.0.0.Final.jar
validation-api-1.1.0.Final.jar
jboss-logging-3.1.1.GA.jar

をライブラリに追加して、アプリ起動、以下のエラーが出ました。NoClassFoundError has occured.

java.lang.NoClassDefFoundError: com/fasterxml/classmate/Filter

こちらのサイトから
SpringSource Enterprise Bundle Repository

FasterXML ClassMate 0.5.4をダウンロードして、com.springsource.com.fasterxml.classmate-0.5.4.jarをライブラリに追加しました。
アプリ起動してみると、またエラーが出ました。NoClassFoundError has occured again !

java.lang.ClassNotFoundException: de.odysseus.el.ExpressionFactoryImpl

そのため、今度はこちらのサイトから、
Java Unified Expression Language

juel-2.2.6をダウンロードしてきて、juel-impl-2.2.6.jarをライブラリに追加しました。
そうすると、アプリが正常に起動するようになりました。

しかし!

実際にValidation機能を作動させると、INTERNAL_SERVER_ERRORが発生。
このような謎のエラーが出てました。InternalServerError has occured !!

java.lang.ExceptionInInitializerError
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolateExpression(ResourceBundleMessageInterpolator.java:227)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolateMessage(ResourceBundleMessageInterpolator.java:187)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolate(ResourceBundleMessageInterpolator.java:120)
   :   :
   :   :

Caused by: javax.el.ELException: Could not create expression factory instance
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:225)
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:183)
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:89)
    at org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm.<clinit>(InterpolationTerm.java:60)
    ... 68 more
Caused by: java.security.AccessControlException: access denied (java.io.FilePermission C:\Program Files\Java\jdk1.6.0_35\jre\lib\el.properties read)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
    at java.security.AccessController.checkPermission(AccessController.java:546)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
    at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:283)
    at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
    at java.io.File.exists(File.java:731)
    at de.odysseus.el.ExpressionFactoryImpl.loadDefaultProperties(ExpressionFactoryImpl.java:258)
    at de.odysseus.el.ExpressionFactoryImpl.loadProperties(ExpressionFactoryImpl.java:283)
    at de.odysseus.el.ExpressionFactoryImpl.<init>(ExpressionFactoryImpl.java:162)
    at de.odysseus.el.ExpressionFactoryImpl.<init>(ExpressionFactoryImpl.java:147)
   :   :
   :   :

el.propertiesとかいうファイルのことをいろいろ調べたものの、情報少なく、原因不明。

おそらく、de.odysseus.el.ExpressionFactoryImplがファイルを読み込もうとしている。で、GAEではファイルの読み書きは禁止されているのでエラーとなっている。
I think “juel-2.2.6” try to read el.properties. But, on GAE, it doesn’t permit to read files. So Error has occured. I don’t know Why “juel” try to read.

しかし、SpringのTutorialをみると、きちんと動いている!
But , according to “SpringTutorial”,  It’s work well !
Spring Security in Google App Engine | SpringSource Team Blog


(2)Success!!
最終的に分かったのは、どうもhibernate-validator-5.*からちょっとつくりが変わっているみたい。
I think that  hibernate-validator-5.* has changed any architecture.

hibernate-validator-4.3.0.Finalを使ったらうまく動くようになりました。
I try to use “hibernate-validator-4.3.0.Final”, it’s work well !!
Don’t use “hibernate-validator-5.*” on GAE !!  Use “hibernate-validator-4.3.0″

また、hibernate-validator-4.3.0を使った場合、FasterXML、juelは無くてもOKです。
You don’t need “FasterXML and juel”, when you use “hibernate-validator-4.3.0” on GAE.

Advertisements

GAEでSpringMVCを動かしてみる4(続 Spring Security を試してみる)

前回のエントリー(GAEでSpringMVCを動かしてみる3(Spring Security を試してみる) | Walk on apps.)からの続きです。

SpringSecurityを利用して、ログイン・ログアウト機能を試しています。
前回は、「とりあえず、まずは動かしてみる、」ってことをやってみました。
今回は、パスワード暗号化や、ユーザごとのアクセス制御、HTTPS対応、とかを追加して「もうちょっと、きちんと動かしてみる」、つもりです。

前回のエントリーまでは、ソースコードをBlog上にまるごと貼り付けていたんですが、大変なのでGitHubで公開することにしました。
GitHubからDownload

Blogでは、ポイントに絞って書いていきます。


(6)もうちょっと、きちんと動かしてみる
次のようなことを、設定してみようと思います。
・パスワードの暗号化
・アクセス制御
・ログインユーザ名の取得
・オリジナルのLoginページの作成
・Logout時の設定
・通信の暗号化(HTTPS設定)
・日本語の文字化け対応

・パスワードの暗号化
StandardPasswordEncoderを利用して、パスワードを暗号化します。SpringSecurityのチュートリアルを参考にしました。「Using encoded passwords」のところに設定例が載っています。
Spring Security — Tutorial

マニュアルによると、StandardPasswordEncoderは、SHA-256でハッシュ化を行うようです。あと、saltとして、ランダムな8Byteデータを付与してくれる。
25. Spring Security Crypto Module

SpringSecurityのBean定義ファイルには、以下のように書きました。ユーザを2つ(admin, bob)定義しています。パスワードは、ユーザ名と同じにしています。

spring-security-dispatcher.xml への追記内容

    <beans:bean id="encoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>

    <authentication-manager>
        <authentication-provider>
            <password-encoder ref="encoder"/>
            <user-service>
                <user name="admin" password="d4c4f5f190abf98b0d5224b549a1f2645acf975c75ec5d2d51895b0a7ca35949fc33576cc8294769" authorities="supervisor, user" />
                <user name="bob" password="cb60b076bebc371a3e5f6097399a82eb65948513639f4b090e70e2d49b6808fa3b02aa9bcd25668f" authorities="user" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

ハッシュ値の計算方法は、StandardPasswordEncoderのencodeメソッドを使います。チュートリアルでは、scalaのインタプリタを使っていますが、僕は地道にJavaのコードを書きました。以下のようにして、ハッシュ値を計算できます。

File名:PasswordEncode.java

import org.springframework.security.crypto.password.StandardPasswordEncoder;

public class PasswordEncode {
        public static void main(String[] args) throws Exception {
            StandardPasswordEncoder encoder = new StandardPasswordEncoder();
            System.out.println(encoder.encode("admin"));
            System.out.println(encoder.encode("bob"));
        }
}

・アクセス制御
SpringSecurityのBean定義ファイルで、use-expressions=”true”の設定をして、アクセス制御の設定をします。
これも、SpringSecurityのチュートリアルを参考にしました。
Spring Security — Tutorial

以下のような権限を、設定してみることにしました。
・index.htmlと、ログイン・ログアウト画面には、全員に許可。
・コンテキストパス「/velocity/admin」はadminだけ許可。
・コンテキストパス「/velocity/user」は、admin, bobに許可。
・その他のパスは、アクセス禁止。

spring-security-dispatcher.xml への追記内容は、こんな感じ。

<http use-expressions="true">
    <intercept-url pattern="/index.html" access="permitAll" />
    <intercept-url pattern="/velocity/login" access="permitAll"/>
    <intercept-url pattern="/velocity/loginfailed" access="permitAll" />
    <intercept-url pattern="/velocity/logout" access="permitAll" />

    <intercept-url pattern="/velocity/admin/**" access="hasRole('supervisor')" />
    <intercept-url pattern="/velocity/user/**" access="isAuthenticated()" />
    <intercept-url pattern="/**" access="denyAll" />

   :    :
   (中略)

</http>

access=”permitAll”といった、権限の書き方一覧は、以下のマニュアルの「16.1.1 Common Built-In Expressions」「16.2 Web Security Expressions」に載っていました。
16. Expression-Based Access Control
上記マニュアルによると、permitAllといった書き方は、WebSecurityExpressionRootクラスで定義されているとのこと。一応JavaDocも見てみた。
WebSecurityExpressionRoot (Spring Security 3.2.0.M1 API)

・ログインユーザ名の取得
現在ログインしているユーザ名の取得を行い、ブラウザ画面に表示できるようにします。
Controllerのコードを修正します。こちらのサイトを参考にしました。
Get current logged in username in Spring Security

ControllerのメソッドにPrincipalインターフェースを追加して、それを利用してユーザ名を取得しています。
修正ファイル:VelocityController.java

    (中略)
   :    :

@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    public void setMessage(String message) {
        this.message = message;
    }

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model, Principal principal) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        String name = principal.getName(); //get logged in username
        model.addAttribute("username", name);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }

   :    :
   (中略)

参考にしたサイトによると、ほかにも2つやり方があるらしく、ちょっと書き方変えるだけなので、参考に載せておきます。

まず、「Authentication.getName()」を使うパターン。ControllerのメソッドにPrincipalインターフェースを追加しなくても動きます。

   :  (中略)  :
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
   :  (中略)  :

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String name = auth.getName(); //get logged in username
        model.addAttribute("username", name);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }
   :    :
   (中略)

次に「User.getUsername()」を使うパターン。こちらも、ControllerのメソッドにPrincipalインターフェースを追加しなくても動きます。

   :  (中略)  :

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
   :  (中略)  :

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String name = user.getUsername(); //get logged in username
        model.addAttribute("username", name);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }
   :    :
   (中略)

・オリジナルのLoginページの作成
こちらのサイトを参考にしました。
Spring Security form login using database
このサイトからダウンロードしたサンプルに、ログイン画面のサンプルがあります。

まず、SpringSecurityのBean定義ファイルに、form-login設定をします。

login-pageでは、ログイン画面を指定します。authentication-failure-urlでは、認証エラー時に表示する画面を指定します。
default-target-urlは、リダイレクト先が指定されていないときに、ログイン後に表示する画面を指定します。

認証が必要な画面/velocity/userにアクセスするとログイン画面/velocity/loginが表示されます。ログイン完了すると /velocity/userにリダイレクトされます。ですが、ログイン画面/velocity/loginに直接アクセスする場合もあります。このとき は、ログイン完了後にリダイレクトする宛先がありません。こういったときに、default-target-urlで指定された画面が表示されます。

spring-security-dispatcher.xml への追記内容

<http use-expressions="true">
   :  (中略)  :
    <form-login login-page="/velocity/login" default-target-url="/index.html"
        authentication-failure-url="/velocity/loginfailed" />
   :  (中略)  :
</http>

次に、Controllerのメソッドにログイン画面への遷移を追加します。
ログイン画面/velocity/loginと、認証エラー画面/velocity/loginfailedは、同じHTMLテンプレートを用いています。認証エラー時は、error変数にtrueを設定し、テンプレートに返しています。

修正ファイル:VelocityController.java

:  (中略)  :
@Controller
@RequestMapping("/velocity")
public class VelocityController {

:  (中略)  :
    @RequestMapping(value="/login", method = RequestMethod.GET)
    public String login(ModelMap model) {

        return "login";
    }

    @RequestMapping(value="/loginfailed", method = RequestMethod.GET)
    public String loginerror(ModelMap model) {

        model.addAttribute("error", "true");
        return "login";
    }
:  (中略)  :

}

次にログイン画面のテンプレートを作成します。
認証エラー時に、SPRING_SECURITY_LAST_EXCEPTION.messageにエラー内容が入るみたいなので、認証エラー時はそれを表示するようにしています。

作成したFile:login.vm

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Login Page</title>

<style>
.errorblock {
    color: #ff0000;
    background-color: #ffEEEE;
    border: 3px solid #ff0000;
    padding: 8px;
    margin: 16px;
}
</style>

</head>

<body onload='document.f.j_username.focus();'>
    <h3>Login with Username and Password ログインテスト</h3>

    #if(${error}=="true")
        <div>
            Your login attempt was not successful, try again.<br /> Caused :
            $SPRING_SECURITY_LAST_EXCEPTION.message
        </div>
    #end

    <form name='f' action="j_spring_security_check"
        method='POST'>

        <table>
            <tr>
                <td>User:</td>
                <td><input type='text' name='j_username' value=''>
                </td>
            </tr>
            <tr>
                <td>Password:</td>
                <td><input type='password' name='j_password' />
                </td>
            </tr>
            <tr>
                <td colspan='2'><input name="submit" type="submit"
                    value="submit" />
                </td>
            </tr>
            <tr>
                <td colspan='2'><input name="reset" type="reset" />
                </td>
            </tr>
        </table>

    </form>
</body>
</html>

「認証エラー時に、SPRING_SECURITY_LAST_EXCEPTION.messageにエラー内容が入る」と書きましたが、その値がApacheVelocity側に引き渡されるように、VelocityViewResolverクラスへの設定追加が必要です。

Bean定義ファイルspring-security-dispatcher.xml の設定修正し、exposeSessionAttributesをtrueに設定します。

   :  (中略)  :
    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="contentType" value="text/html;charset=UTF-8" />
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
        <property name="exposeSessionAttributes" value="true"/>
    </bean>
   :  (中略)  :

・Logout時の設定

まず、SpringSecurityのBean定義ファイルに、logout設定をします。

logout-success-urlで、ログアウト画面を指定します。delete-cookiesでログアウト時にJSESSIONIDをcookieから削除するようにしています。

spring-security-dispatcher.xml への追記内容

<http use-expressions="true">
:  (中略)  :
    <logout logout-success-url="/velocity/logout" delete-cookies="JSESSIONID"/>

</http>

次に、Controllerのメソッドにログアウト画面への遷移を追加します。

修正ファイル:VelocityController.java

:  (中略)  :
@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    public void setMessage(String message) {
        this.message = message;
    }

:  (中略)  :

    @RequestMapping(value="/logout", method = RequestMethod.GET)
    public String logout(ModelMap model) {

        model.addAttribute("message", this.message);
        return "logout";
    }

}

次にログアウト画面のテンプレートを作成します。

作成したFile:logout.vm

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Hello App Engine</title>
  </head>

  <body>

    message: ${message}<br/>
    <p>Logoutしました。</p>
  </body>
</html>

・通信の暗号化(HTTPS設定)
SpringSecurityで特定のURLに対してHTTPSを要求する設定ができます。ただし、HTTPS自体をSpringSecurityで制御するわけでないので、HTTPSの入り口は、APサーバ等で用意しておく必要があります。

そのため、まず、GoogleAppEngine側でHTTPSの入り口を用意する必要があります。GAEでHTTPSを使う場合、web.xmlにsecurity-constraint設定を追加すると利用できます。
配備記述子: web.xml – Google App Engine — Google Developers

注意点としては、Google App Engine SDKでは、開発環境ではHTTP接続、GAE上ではHTTPS接続になります。
SpringSecurityでHTTPSを要求する設定をした場合、開発環境でもHTTPSが使えるようにしておかないと、アクセスができません。そのため、僕の場合、HTTPS関連設定はGAE側にすべて任せることにします。

web.xmlに、security-constraint設定を追加します。

  :  (中略)  :
    <!-- GAE Setting -->
    <security-constraint>
        <web-resource-collection>
            <url-pattern>/velocity/login*</url-pattern>
            <url-pattern>/velocity/loginfailed*</url-pattern>
            <url-pattern>/velocity/logout*</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
  :  (中略)  :

参考までに、SpringSecurityで設定する場合の内容を書いておきます。
マニュアルはこちら。3. Security Namespace Configuration

requires-channelでプロトコルを指定すると設定できます。
port-mappingは必要に応じて設定します。開発環境などで、通常のポートが使えないときに指定します。

  <http>
    <intercept-url pattern="/secure/**" access="ROLE_USER" requires-channel="https"/>
    <intercept-url pattern="/**" access="ROLE_USER" requires-channel="any"/>
    ...

    <port-mappings>
      <port-mapping http="9080" https="9443"/>
    </port-mappings>
    ...
  </http>

・日本語の文字化け対応
HTMLテンプレートに日本語があると、ブラウザで表示させたときに、文字化けがおきます。
そのため、Bean定義ファイルと、web.xmlに設定追加しておく必要があります。
こちらのサイトを参考にしました Spring-MVCのビューにVelocityを使いたい – PukiWiki

今回はUTF-8に統一することにします。
まず、Bean定義ファイルspring-security-dispatcher.xmlに設定追加します。

VelocityConfigurerクラスのinput.encoding、output.encodingにUTF-8を設定。
→ たぶん、テンプレートファイル読込と、HTML出力の設定だと思う

VelocityViewResolverクラスの、contentTypeにUTF-8を設定。
→ ブラウザに返却するContent-Typeを設定してる。HTTPヘッダですかね。

 
   :  (中略)  :
    <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/view/"/>

        <property name="velocityProperties">
            <props>
            <prop key="input.encoding">UTF-8</prop>
            <prop key="output.encoding">UTF-8</prop>
            </props>
        </property>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="contentType" value="text/html;charset=UTF-8" />
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
        <property name="exposeSessionAttributes" value="true"/>
    </bean>
   :  (中略)  :

次にweb.xmlに設定追加します。

CharacterEncodingFilterクラスで、encodingにUTF-8を設定します。
→HTMLフォームからの入力された文字を、UTF-8にしてサーバ側で処理することになります。

   :  (中略)  :
    <!-- UTF-8 Setting -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
   :  (中略)  :

上記設定には、forceEncodingというパラメータもあるみたいだけど、今回は設定しませんでした。

   :  (中略)  :
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
   :  (中略)  :

(7)アプリ起動
GAE上で動かしてみました。
コンテキストパス/velocity/user にhttpでアクセスすると、ログイン画面にリダイレクトされます。httpsになってます。
spring4_01

bobでログインすると、画面が返ってきました。httpにもどっています。
ユーザ名bobが、画面に表示されています。
spring4_02

bobユーザのままで、/velocity/admin にアクセスすると、アクセス拒否されます。
spring4_03

/velocity/user 画面でログアウトをクリックすると、ログアウトします。httpsになっています。
spring4_04

ログイン画面でパスワードを間違えると、エラーメッセージが表示されます。
spring4_05


GAEでSpringMVCを動かしてみる3(Spring Security を試してみる)

GoogleAppEngineの勉強中です。
前回のエントリーで、SpringMVCにテンプレートエンジンのApache Velocityを組み合わせて動かしてみました。
今回は、SpringSecurityを利用して、ログイン・ログアウト機能を試してみます。

とりあえず、ユーザの管理は設定ファイルに書き込んでおくシンプルな形を試します。(ユーザデータをDBに保存したり、外部の認証サービス(GoogleAccountとかTwitterとか)を利用する、みたいなことはやらない。)

————————————————————–
使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

Spring Framework 3.2.2.RELEASE
Commons Collections 3.2.1
Commons Lang 2.6
Commons Logging 1.1.2
Apache Velocity 1.7
Spring Security 3.2.0.M1


(1)はじめに
今回はSpring Securityの機能を使って、ユーザ認証とアクセス制御を試してみます。
「SpringMVC + Apache Velocity」については、過去のエントリーを参照してください。
GAEでSpringMVCを動かしてみる | Walk on apps.
GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む) | Walk on apps.

進め方としては、「とりあえず、まずは動かしてみる、」ってことをやってみます。
そのあと、パスワード暗号化や、ユーザごとのアクセス制御、HTTPS対応、とかを追加して「もうちょっと、きちんと動かしてみる」、つもりです。


(2)SpringFramework、Commons Loggingのダウンロード
こちらについては、前回のエントリーに書いてあるのでそちらを参照してください。
GAEでSpringMVCを動かしてみる | Walk on apps.


(3)velocity関連ライブラリのダウンロード
以下の3種類必要になります。
Apache Velocity
Commons Collections
Commons Lang

こちらについては、前回のエントリーに書いてあるのでそちらを参照してください。
GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む) | Walk on apps.


(4)ライブラリ追加
ライブラリに追加するjarファイルは、以下の15個

commons-collections-3.2.1.jar
commons-lang-2.6.jar
commons-logging-1.1.2.jar
spring-aop-3.2.2.RELEASE.jar
spring-beans-3.2.2.RELEASE.jar
spring-context-3.2.2.RELEASE.jar
spring-context-support-3.2.2.RELEASE.jar
spring-core-3.2.2.RELEASE.jar
spring-expression-3.2.2.RELEASE.jar
spring-security-config-3.2.0.M1.jar
spring-security-core-3.2.0.M1.jar
spring-security-web-3.2.0.M1.jar
spring-web-3.2.2.RELEASE.jar
spring-webmvc-3.2.2.RELEASE.jar
velocity-1.7.jar

これらのjarファイルを、Eclipseの“war/WEB-INF/lib”フォルダに、配置します。
また、Eclipseの、「Java Build Path」に追加します。


(5)まずは動かしてみる。
Springのマニュアルと、
3. Security Namespace Configuration

こちらのサイトを参考にしました。
Spring Security hello world example
Spring Security logout example

まずは、Controllerを作成します。
リクエスト「/velocity/***」に対して、テンプレートsample.vmを返す想定にしています。
テンプレートの置き場所や、ファイル名の拡張子については、SpringのBean定義ファイルの中で定義しています。

File名:src/sample/controller/VelocityController.java

package sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

次に、テンプレート(.vmファイル)を作成します。

リクエスト「/velocity/***」を受け取ったControllerが、***の部分を抜き出して、path変数に入れて、テンプレートに引き渡しています。

message変数は、SpringのBean定義ファイルの中でControllerに対して初期設定しています。Controllerがその値をテンプレートに引き渡しています。

File名:war/WEB-INF/view/sample.vm

<html>
<body>
path: ${path}<br/>
message: ${message}<br/>
<p><a href="j_spring_security_logout">Logout</a></p>
</body>
</html>

次に、SpringのBean定義ファイルを作成します。
Beanの定義と、テンプレートの場所を指定しています。

File名:war/WEB-INF/spring-mvc-dispatcher-servlet.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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
                        ">

    <mvc:annotation-driven />

    <bean class="sample.controller.VelocityController">
        <property name="message">
            <value>Velocity Sample</value>
        </property>
    </bean>

    <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/view/"/>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
    </bean>

</beans>

次に、Spring Securityの定義ファイルを作成します。
とりあえず、admin, bobというユーザを準備しておきました。パスワードは、ひとまず平文で書いておきます。

File名:war/WEB-INF/spring-security-dispatcher.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="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-3.2.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-3.1.xsd
                        ">

    <!-- Spring Security -->

    <http auto-config='true'>
        <intercept-url pattern="/**" access="ROLE_USER" />
    </http>

    <authentication-manager>
        <authentication-provider>
            <user-service>
              <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
              <user name="bob" password="bob" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>

次に、web.xmlを修正します。

すべてのコンテキストパスに対して、DelegatingFilterProxyを適用する(フィルタする?)ように設定しています。(赤字にしています)

contextConfigLocationのところで、Spring Securityの定義ファイルを指定しています。
ここでは、Bwan定義ファイル(spring-mvc-dispatcher-servlet.xml)については指定していません。これは、servlet-nameで指定している名称spring-mvc-dispatcherに紐づいた名前のファイルを、自動的に読み込みに行ってくれるから。(というか、名前を紐づけないやり方がわからない。。)

File名:war/WEB-INF/web.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <!-- Spring MVC -->
    <servlet>
        <servlet-name>spring-mvc-dispatcher</servlet-name>
        <servlet-class>
                    org.springframework.web.servlet.DispatcherServlet
                </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring-security-dispatcher.xml
        </param-value>
    </context-param>

    <!-- Spring Security -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

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

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

appengine-web.xmlへ設定追加
GoogleAppEngineでは、デフォルトではセッションが使用できないようです。なので、appengine-web.xmlに以下の設定を追加します

<sessions-enabled>true</sessions-enabled>

作成したソースコードの配置場所をまとめると、こんな感じになっています。
spring_security01

自PC上で、ためしにアプリ起動
http://localhost:8888/velocity/test  にアクセスしてみます。ログイン画面が出てきました。
spring_security02

ユーザ「bob」でログインすると、画面が出ました!!
spring_security03

GAE上にDeployしても、きちんと動きました。よかった。


(6)もうちょっと、きちんと動かしてみる
:  :
:  :
記事が長くなってきたので、ここから先は次回のエントリーに書くことにします。
あと、ソースを張付けるのが大変になってきたので、GitHubを導入してみたいと思う!
こちら→ GAEでSpringMVCを動かしてみる4(続 Spring Security を試してみる) | Walk on apps.

GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む)

GoogleAppEngineの勉強中です。
前回のエントリーで、SpringMVCを動かしてみました。
その時は、ひとまずjspで画面表示していたけど、今回は、テンプレートエンジンのApache Velocity を使って、「SpringMVC + Velocity」を試してみます。
とりあえず動かすことを目標にしているので、テンプレートの分割(ヘッダ、フッタ、コンテンツ、サイドバー領域とか)まではやりません。

————————————————————–
使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

Spring Framework 3.2.2.RELEASE
Commons Collections 3.2.1
Commons Lang 2.6
Commons Logging 1.1.2
Apache Velocity 1.7


(1)はじめに

テンプレートエンジンって、そもそも、どれ使えばいいのか謎。
このページに一覧が載ってるけど、すげーたくさんある。
Template engine (web) – Wikipedia, the free encyclopedia

今回は、Springと連携させる、ってことで、以下のサイトを参考にしました。
(a)SpringMVCで使えるテンプレートエンジンを比較してみた #jsug – from world import goodies
(b)Template Engineの比較(Java) – No Bugs, No Life

上記サイトを見た感じだと、
・Velocity
・Freemarker
・Thymeleaf
・Mayaa

といったものが候補になりそう。(JSPはテンプレートの分割ができないと思うので最初から除外(タグを使えばできる?))

また、SpringFrameworkのドキュメントの「18. View technologies」にテンプレートのことが書かれてあるのを見つけました。
その中の、「18.4. Velocity & FreeMarker」で、Velocity 、FreeMarkerの説明があるので、この2つのうちどちらかがよさそう。
18. View technologies

で、先述したサイト(b)に記載があるんですが、FreeMarkerについては、現時点の最新版FreeMarker 2.3.19に、セキュリティの問題があるらしい。
FreeMarker Manual – 2.3.19

Attention! This release contains two important security workarounds that unavoidably make it obvious how some applications can be exploited. FreeMarker can’t solve these issues on all configurations, so please read the details instead of just updating FreeMarker!

というわけで、Velocityを使うのが無難そう。


(2)SpringFramework、Commons Loggingのダウンロード
こちらについては、前回のエントリーに書いてあるのでそちらを参照してください。
GAEでSpringMVCを動かしてみる | Walk on apps.


(3)velocityのダウンロード
こちらのサイトのDownloadsから入手できます。
Apache Velocity Site – The Apache Velocity Project

僕の場合、velocity-1.7.tar.gz をダウンロードしました。


(4)Commons Collectionsのダウンロード
こちらのサイトのDownloadsから入手できます。
Collections – Home

僕の場合、commons-collections-3.2.1-bin.tar.gz をダウンロードしました。


(5)Commons Langのダウンロード
こちらのサイトのDownloadsから入手できます。
Lang – Home

Commons Langについては、ちょっと注意です。
Commons Langがない状態でSpringMVCを起動すると、Class Not Foundエラーが出ます。原因は以下のClassでした。
org.apache.commons.lang.StringUtils

が、現時点での最新版、commons-lang3-3.1を使うとこのエラーが解消しません。JavaDocを見ると
version 3.*ではクラス名が微妙に違っているみたいでした。
extended by org.apache.commons.lang3.StringUtils

なので、version2.*系の最新版、commons-lang-2.6-bin.tar.gz をダウンロードしました。


(6)ライブラリ追加
ライブラリに追加するjarファイルは、以下の11個
commons-collections-3.2.1.jar
commons-lang-2.6.jar
commons-logging-1.1.2.jar
spring-beans-3.2.2.RELEASE.jar
spring-context-3.2.2.RELEASE.jar
spring-context-support-3.2.2.RELEASE.jar
spring-core-3.2.2.RELEASE.jar
spring-expression-3.2.2.RELEASE.jar
spring-web-3.2.2.RELEASE.jar
spring-webmvc-3.2.2.RELEASE.jar
velocity-1.7.jar

これらのjarファイルを、Eclipseの“war/WEB-INF/lib”フォルダに、配置します。
spring_velocity01

また、Eclipseの、「Java Build Path」に追加します。
spring_velocity02


(7)コーディング

まずは、Controllerを作成します。
Controllerの書き方は、こちらのサイトを参考にしました。
Spring-MVCとVelocityを使った Twitterライクに REST風な URLハンドリングをするサイトのテンプレート

書き方としては、Velocityを使わなかった時と変わらない書き方で大丈夫そう。(今回は、新規作成したので、前回のエントリーで作成したものと若干ちがっています。)
おそらく、SpringMVCでは、テンプレートエンジンに依存しない書き方ができるように設計されているんだと思った。

今回は、リクエスト「/velocity/***」に対して、テンプレートsample.vmを返す想定にしています。
model.addAttributeメソッドでテンプレートに引き渡す値をセットし、return “sample”;でテンプレートを指定しています。
テンプレートの置き場所や、ファイル名の拡張子については、SpringのBean定義ファイルの中で定義しています。

File名:src/sample/controller/VelocityController.java

package sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    @RequestMapping(value="/{name}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String name, ModelMap model) {

        model.addAttribute("name", name);
        model.addAttribute("message", this.message);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

次に、テンプレート(.vmファイル)を作成します。

リクエスト「/velocity/***」を受け取ったControllerが、***の部分を抜き出して、name変数に入れて、テンプレートに引き渡しています。

message変数は、SpringのBean定義ファイルの中でControllerに対して初期設定しています。Controllerがその値をテンプレートに引き渡しています。

File名:war/WEB-INF/view/sample.vm

<html>
<body>
name: ${name}<br/>
message: ${message}<br/>
</body>
</html>

次に、SpringのBean定義ファイルを作成します。
Bean定義ファイルについては、前回のエントリーで作成したものに追記し、不要な部分をコメントアウトしています。

<bean class=”sample.controller.MovieController”>の部分は、今回関係ないので読み飛ばしてください。

<bean class=”sample.controller.VelocityController“>のところで、コントローラの初期設定をしています。

「bean id=”velocityConfig“」と、「bean id=”viewResolver“」のところで、テンプレートファイルの置き場所・拡張子を指定しています。(複数のディレクトリを使い分けることは、できるんだろうか?)

File名:war/WEB-INF/mvc-dispatcher-servlet.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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
                        ">

<!--
    <context:component-scan base-package="sample.controller">
        <context:exclude-filter type="regex"
            expression="sample.controller.Movie.*" />
    </context:component-scan>
-->

    <mvc:annotation-driven />

    <!-- Bean to show you Di in GAE, via Spring, also init the MovieController -->
    <bean class="sample.controller.MovieController">
        <property name="message">
            <value>Hello World</value>
        </property>
    </bean>

<!--
    <bean
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>
-->

    <bean class="sample.controller.VelocityController">
        <property name="message">
            <value>Velocity Sample</value>
        </property>
    </bean>

    <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/view/"/>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
    </bean>

</beans>

次に、web.xmlを修正します。
web.xmlは、前回のエントリーで作成したときと同じ状態です。

リクエストをすべて、DispatcherServletに渡す、って感じ。と、Bean定義ファイルの場所を指定
ContextLoaderListenerは・・・なんだろ。。。。

File名:war/WEB-INF/web.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>
                    org.springframework.web.servlet.DispatcherServlet
                </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
                    org.springframework.web.context.ContextLoaderListener
                </listener-class>
    </listener>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

作成したソースコードの配置場所をまとめると、こんな感じになっています。

spring_velocity03


(8)アプリ起動
ブラウザから、アクセスするとこんな感じ。
spring_velocity04

リクエストで、/velocity/*** としたときの、***の部分が、
ブラウザ画面上で「name: ***」と表示されます。

上記の画面キャプチャは、自分のPC上(開発環境)のものですが、GAE上に実際にDeployしてもきちんと動きました。

GAEでSpringMVCを動かしてみる

GoogleAppEngineの勉強中です。
GAEでJavaを使って開発する場合、JSP,Servletが使えます。が、MVCモデルでアプリ作成しようとすると、何らかのFrameworkを使わないとダメっぽい。

というわけで、SpringMVCを試してみました。

————————————————————–
使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

Spring Framework 3.2.2.RELEASE
commons-logging-1.1.2


(1)はじめに
Javaのフレームワークというと、Struts、Springくらいしか聞いたことがなくって、たまたま目にしたPlayFrameworkというものが結構よさそうだと思ってた。ただPlayFrameworkは、まだ仕様がちょっと落ち着いてないみたい、う~ん。

で調べてみると、比較資料がありました。
Comparing JVM Web Frameworks – Devoxx France 2013

この資料は、すごい参考になったんだけど、スライド90枚に及ぶ長編スペクタクルなので、簡単にポイントを紹介します。

資料の40~44ページに、比較項目にポイント付けして、ランキングしたものがあります。
Grailsが1位になってるんですけど、どうなんでしょうか。Springは2位ですね。

57ページに、各種情報サイト?で紹介されているTop JVM Frameworkがのっています。選定基準は謎ですけど、有名どころ?と思われるFrameworkはこのあたりのものなんでしょうか。

69ページから、GoogleTrendでどんだけ話題になっているか、
73ページから、JobTrend(仕事で需要あるか)、
なんてことも紹介されています。

そして、最終的に筆者は、「俺に聞くな、自分で考えろ」と言っています(笑)。

Prioritize a list of goals that are important to your application.
・目的を一覧にして、優先順位をつける
Pick 3-4 frameworks and do a spike with each, developing the same application.
・3~4つのフレームワークをつかって、同じ機能のアプリ作成してみる。
Document and rank each framework against your list of goals.
・目的を一覧化し、それぞれの項目について各フレームワークに点数をつける
Calculate and choose!
・ランキングして、どれにするか選ぶ
Don’t be a Picker
・適当にチョイスするのは避ける


(2)SpringFrameworkのダウンロード
こちらのサイトの、一番下のところにProjectsという一覧があって、そこのSpring Frameworkのページに行きます。
SpringSource.org

spring_framework

そうすると、右のほうに、ダウンロード・ドキュメントへのリンクがあります。
僕の場合、spring-framework-3.2.2.RELEASE-dist.zip をダウンロードしました。


(3)Commons Loggingのダウンロード
Springを動かしてみるとわかるんですが、起動時にCommons LoggingがないとClass NotFoundエラーが出ます。
古いバージョンのSpringだと、SpringFrameworkのダウンロードすると、中にCommons Loggingのjarファイルも入ってたみたいですが、今は別になっているようです。

こちらのサイトの、Downloadから入手できます。
Commons Logging – Overview

僕の場合、commons-logging-1.1.2-bin.tar.gz をダウンロードしました。


(4)ライブラリ追加
とりあえず、超シンプルなアプリで試してみたいだけなので、必要最小限のjarファイルをライブラリに追加します。
追加するファイルはこちら7つ。

commons-logging-1.1.2.jar
spring-beans-3.2.2.RELEASE.jar
spring-context-3.2.2.RELEASE.jar
spring-core-3.2.2.RELEASE.jar
spring-expression-3.2.2.RELEASE.jar
spring-web-3.2.2.RELEASE.jar
spring-webmvc-3.2.2.RELEASE.jar

これらのjarファイルを、Eclipseの“war/WEB-INF/lib”フォルダに、配置します。
spring_library1

また、Eclipseの、「Java Build Path」に追加します。
spring_library2


(5)コーディング
こちらのサイトを参考にして、サンプル作成しました。
Google App Engine + Spring 3 MVC REST example
Getting Started With Spring MVC and Google App Engine | [Be el o ge]

まずは、Controllerを作成します。

Eclipseのsrcフォルダ配下に、パッケージsample.controllerを作成し、その中にMovieController.javaを作成します。
File名:src/sample/controller/MovieController.java

package sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/movie")
public class MovieController {

    //DI via Spring
    String message;

    @RequestMapping(value="/{name}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String name, ModelMap model) {

        model.addAttribute("movie", name);
        model.addAttribute("message", this.message);

        //return to jsp page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "list";

    }

    public void setMessage(String message) {
        this.message = message;
    }

}

次に、JSPファイルを作成します。
File名:war/pages/list.jsp

<html>
<body>
    <h1>GAE + Spring 3 MVC REST example</h1>

    <h3>Movie : ${movie} , DI message : ${message}</h3>    
</body>
</html>

次に、SpringのBean定義ファイルを作成します。
File名:war/WEB-INF/mvc-dispatcher-servlet.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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
                        ">

<!--
    <context:component-scan base-package="sample.controller">
        <context:exclude-filter type="regex"
            expression="sample.controller.Movie.*" />
    </context:component-scan>
-->

    <mvc:annotation-driven />

    <!-- Bean to show you Di in GAE, via Spring, also init the MovieController -->
    <bean class="sample.controller.MovieController">
        <property name="message">
            <value>Hello World</value>
        </property>
    </bean>

    <bean
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

</beans>

GoogleAppEngineでは、アプリ起動時にかかる時間を気にする必要があります。(今回はサンプルアプリなので気にしなくていいですが)

こちらの情報によると、
Optimizing Spring Framework for App Engine Applications – Google App Engine — Google

Springの「Component Scanning」は使わないほうがいいとのことです。
なので、context:component-scan タグを使わないようにしました。

ファイルの頭で、xmlの定義を読込ませているところ(以下の項目)
xmlns:context
xmlns:mvc
xsi:schemaLocation

は、漏れの無いように注意です。僕は「xmlns:mvc」を書き忘れていて、アプリ起動時にmvc-dispatcher-servlet.xmlの読込エラーが発生して悩まされました。

次に、web.xmlを修正します。
File名:war/WEB-INF/web.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>
                    org.springframework.web.servlet.DispatcherServlet
                </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
                    org.springframework.web.context.ContextLoaderListener
                </listener-class>
    </listener>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

作成したソースコードの配置場所をまとめると、こんな感じになります。

spring_files


(6)アプリ起動
ブラウザから、アクセスするとこんな感じ。
spring_sample_view

リクエストで、/movie/*** としたときの、***の部分が、
ブラウザ画面上で「Movie : ***」と表示されます。

上記の画面キャプチャは、自分のPC上(開発環境)のものですが、GAE上に実際にDeployしてもきちんと動きました。よかった。


4/21追記
テンプレートエンジンにApache Velocityを使ってみました。
GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む) | Walk on apps.