さらにBloggerに移転

はてなブログをやめてBloggerに移転しました。これでいったい何回目だろうorz

カテゴリー: 日記 | コメントする

はてなブログに移転

移転しました。http://oinume.hatenablog.com/

カテゴリー: 日記 | コメントする

SpringMVC + Bean Validation + FreeMarkerでFormのバリデーション

最近Javaの面倒臭さに耐性ができてきて何も感じなくなってきた oinume です。こんにちは。今日はSpringMVC + JSR-303 Bean Validation + FreeMarkerでいわゆるフォームのバリデーション+エラーメッセージ表示を試してみたので、そのまとめをば。サンプルコードはGitHubにあげてある。

使ったソフトウェアのバージョン

  • Spring MVC 3.2.3
  • FreeMarker 2.3.19
  • Hibernate Validator 4.3.1.Final

Hibernate Validatorは5.0.1.Finalというのが最新なんだけど、これを使うとWebアプリ起動時にNoClassDefFoundErrorで怒られてしまったので1世代古いやつを使ってる。

Caused by: java.lang.NoClassDefFoundError: org/hibernate/validator/method/MethodConstraintViolationException
	at org.springframework.validation.beanvalidation.MethodValidationPostProcessor.afterPropertiesSet(MethodValidationPostProcessor.java:102)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1541)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1479)
	... 58 more
Caused by: java.lang.ClassNotFoundException: org.hibernate.validator.method.MethodConstraintViolationException
	at org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy.loadClass(SelfFirstStrategy.java:50)
	at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:244)
	at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:230)
	at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:430)
	at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:383)
	... 61 more

ディレクトリ構成

src
├── main
│   ├── java
│   │   └── net
│   │       └── lampetty
│   │           └── samples
│   │               ├── spring
│   │               │   └── mvc
│   │               │       ├── controller
│   │               │       │   ├── FormController.java
│   │               │       └── form
│   │               │           └── UserForm.java
│   ├── resources
│   │   ├── database.xml
│   │   ├── freemarker.xml
│   │   ├── logback.xml
│   └── webapp
│       ├── WEB-INF
│       │   ├── spring-context.xml
│       │   ├── view
│       │   │   ├── common
│       │   │   │   └── head-css.ftl
│       │   │   ├── form
│       │   │   │   ├── complete.ftl
│       │   │   │   └── input.ftl
│       │   │   ├── index.ftl
│       │   │   └── spring.ftl
│       │   ├── web.xml
│       │   └── webapp-context.xml
│       └── static
│           └── bootstrap

画面遷移

画面遷移はこんな感じ。普通のWebアプリだとバリデーションがOKだったらデータベース更新したりするだしょう。処理が完了したら完了画面へHTTPリダイレクトする。なるべく説明をシンプルにするために、今回はCSRF対策は省いてる。

jsr-303_validation

application-context.xml的なヤツ

ここを参照。Validation関連で必要なのは下記の2つ。

    <beans:bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
    <beans:bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

FormController

今回用意したControllerは下記のようなシンプルなもの。/form/process でバリデーションを行なっている。

package net.lampetty.samples.spring.mvc.controller;
 
import static org.springframework.web.bind.annotation.RequestMethod.*;
 
import javax.validation.Valid;
 
import net.lampetty.samples.spring.mvc.form.UserForm;
 
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
 
/**
 * FormとValidationのサンプル
 */
@Controller
public class FormController {
 
    // 入力画面
    @RequestMapping(value = "/form/input", method = GET)
    public String index(Model model) {
        model.addAttribute("userForm", new UserForm());
        return "/form/input";
    }
 
    // バリデーションを行う
    @RequestMapping(value = "/form/process", method = POST)
    public String process(
            Model model,
            @Valid UserForm userForm, // フォームで入力された値がセットされている
            BindingResult result) {
 
        if (result.hasErrors()) {
            // エラーがある場合は入力画面を表示する
            model.addAttribute("userForm", userForm);
            return "/form/input";
        }
 
        // データベースの更新とか
 
        // 終わったら完了画面へリダイレクト
        return "redirect:/form/complete";
    }
 
    // 完了画面
    @RequestMapping(value = "/form/complete", method = GET)
    public String complete(Model model) {
        return "/form/complete";
    }
}

UserForm

こんな感じのBeanを作る。プロパティに対してアノテーションをつけて、どんなバリデーションをかけるかを定義する。使えるアノテーションに関してはここなどを参照。JSR-303で用意されているものだけだと使いづらい(@NotNullが空文字列チェックしてくれなかったり)ので、Hibernate Validatorのアノテーションも使わざるを得ないかなぁと思った。

package net.lampetty.samples.spring.mvc.form;
 
import javax.validation.constraints.Size;
 
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
 
public class UserForm {
 
    @NotEmpty
    @Size(max = 10)
    private String name;
 
    @NotEmpty
    @Size(max = 255)
    @Email
    private String email;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
}

入力画面

入力画面。
input.ftl

 
<#import "/spring.ftl" as spring />
<!DOCTYPE html>
<#escape __x as __x?html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>フォーム</title>
<#include "/common/head-css.ftl" />
</head>
<body>
<div class="container">
<form class="form-horizontal" method="POST" action="/form/process">
<legend>JSR-303 Bean Validation</legend>
<!-- name -->
<div class="control-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<@spring.formInput 'userForm.name' 'id="name" placeholder="Name"' />
<#if spring.status.error>
<p><@spring.showErrors "<br>", "color:red" /></p>
</#if>
</div>
</div>
<!-- email -->
<div class="control-group">
<label class="control-label" for="email">Email</label>
<div class="controls">
<@spring.formInput 'userForm.email' 'id="email" placeholder="Email"' />
<#if spring.status.error>
<p><@spring.showErrors "<br>", "color:red" /></p>
</#if>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" name="send" class="btn">送信</button>
</div>
</div>
</form>
 
</div><!-- /div.container -->
</body>
</html>
</#escape>

@spring.formInput のマクロを使うことでフォームのタグを生成できる。詳細はSpringMVCのドキュメントを参照。

spring.status.error はバリデーションエラーの時にtrueになる変数で、<@spring.showErrors “<br>”, “color:red” /> というマクロで、エラーがあった場合にエラーメッセージを表示させる。バリデーションエラーが複数ある場合もあるので、複数のエラーメッセージがここに表示される。1つ目の引数はエラーメッセージを区切る文字列。<br>タグなので改行で区切っている。2つ目の引数はエラーメッセージにつける span タグのCSSスタイル。”:”があると<span style=”…”>となり、”:”がない場合は<span class=”…”> というクラス指定になるみたい。

↓は実際にバリデーションエラーが起きた時の入力画面のキャプチャ。
jsr-303-validation-error

フォーム完了画面

これはただの完了画面なので説明不要。
complete.ftl

エラーメッセージを変えたい

デフォルトだとHibernate Validator付属の英語のメッセージが表示されるので、ValidationMessages_ja.propertiesという名前のプロパティファイルを用意してクラスパスの通ったところに置いておけばいいらしい。参考

軽く使ってみた感想や疑問など

いくつか使いづらいところや疑問があった。

  • @Sizeのエラーメッセージが “size must be between 0 and 10″ となっていて不自然なので、@MaxSizeや@MinSizeとかあった方がいいんじゃないか
  • FTLの中で「バリデーションエラーが起こっているか」を判定する方法がよくわからなかった。(@springのマクロにはそれっぽいものはなかった)
  • FTLの中で「フォームの全ての入力項目のエラーをまとめて表示させたい」んだけど、どうやるのがいいのかわからない。ControllerでModelに全エラーメッセージをつっこんだListをセットすればいいのかなぁ

でも、全体的に見るといい仕組みとしてはだと思う。ちょっと使いづらいところは自分で拡張もできるっぽいので。というわけで仕事でもこれを使っていこうと思う。

[tmkm-amazon]4798123366[/tmkm-amazon]

カテゴリー: FreeMarker, Java, Spring | タグ: , , | コメントする

Pythonでローカル変数の値をprintfデバッグしたい

a = 1
b = 2
print("locals = " + str(locals()))

こんな風に書くとローカル変数の値が簡単にデバッグできるかなーと思った。

locals = {'a': 1, 'b': 2, '__builtins__': <module '__builtin__' (built-in)>, '__file__': '/Users/oinuma_kazuhiro/Dropbox/code/python/locals.py', '__package__': None, '__name__': '__main__', '__doc__': None}

ただ、余計なものまで表示されてしまうので、特定の変数だけprintしたい場合は下記のようにする。

a = 1
b = 2
print("a = {a}, b = {b}".format(**locals()))
a = 1, b = 2

なんかもっといいやり方ありそう。

[tmkm-amazon]477415539X[/tmkm-amazon]

カテゴリー: Python | タグ: | コメントする

Jettyでシンボリックリンクを有効にする

最近Jettyと戯れている。Tomcatに比べてちょっと扱いづらいところが可愛らしい。

Jettyはセキュリティ的な理由でAlias(Symbolic Link)をデフォルトでは無効にしている。でもsymlink使いたいんですよあたしゃ。

というわけで、調べた結果web.xmlに下記を追加するのが一番楽そうなのでこれを設定したらいけた。

<servlet>
    <servlet-name>jetty</servlet-name>
    <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
    <init-param>
        <param-name>aliases</param-name>
        <param-value>true</param-value>
   </init-param>
   <load-on-startup>0</load-on-startup>
</servlet>

[tmkm-amazon]4798125415[/tmkm-amazon]

カテゴリー: Java, Jetty | タグ: | コメントする