必要最小限のサンプルでThymeleafを完全マスター

この記事は?

Java EE Advent Calendar 2016 - Qiitaの12日目です。

昨日の記事は@yyYankさんの「どうすんのJava EE - Javaプログラマのはしくれダイアリー」でした。明日は@n_agetsuさんです。

Thymeleafは、Javaで作られたテンプレートエンジンです。JSPの代替技術として近年注目されていて、JJUG CCCなどで話を聞いていても、利用事例が増えているように感じます。

ブログ情報も多く、検索すると「チートシート」のようなブログがいっぱい出てきます。

ただ、Thymeleafは多機能なのでチートシートもボリュームがあり、Thymeleafを初めて学習する人にはちょっと重たいなあ・・・と感じていました。

そこで今回は、JSPから移行したい方が、まず最初に理解すべき必要最低限の項目をまとめました。

アジェンダ

  • 環境準備
  • 4つの記法
    • リンク式(@{...}
    • メッセージ式(#{...}
    • 変数式(${...}
    • 選択変数式(*{...}
  • テンプレートの記述
    • 条件分岐(th:ifth:unless
    • 繰り返し(th:each
  • その他
    • ユーテリティオブジェクト(#listsなど)
    • コメント(<!--/* */-->

サンプルコードはGitHubに公開しています。

GitHub - MasatoshiTada/beginning-thymeleaf

環境準備

プロジェクトの作成

MavenまたはGradleを使います。(下記はMavenの例です)

    <dependencies>
        ...
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.2.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        ...
    </dependencies>

TemplateResolverTemplateEngineの生成

今回は、サーブレットが1つ、Thymeleafのテンプレートが1つだけのシンプルなプログラムです。

Thymeleafの中心的なクラスが、TemplateResolverTemplateEngineです。

TemplateResolverクラスは、指定された論理的なビュー名(例:"hello")から、物理的なビュー名(例:"/WEB-INF/views/hello.html")を解決する役割を担います。

TemplateEngineクラスは、作成されたテンプレートを元に、実際にレスポンスするHTMLを出力する役割を担います。

まずは、上記2つのインスタンスを生成しておく必要があります。これらのインスタンスは1つずつ生成して、以降の処理では使い回せばOKです。

今回は、サーブレットクラスの初期化処理の中で生成します。

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    // Thymeleafの出力を書き出すクラス
    private TemplateEngine templateEngine;

    /**
     * サーブレットの初期化処理。
     * TemplateResolverおよびTemplateEngineを生成する。
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        // TemplateResolverの生成
        ServletContext servletContext = config.getServletContext();
        ServletContextTemplateResolver templateResolver =
                new ServletContextTemplateResolver(servletContext);
        templateResolver.setPrefix("/WEB-INF/views/"); // ビューの保存フォルダ
        templateResolver.setSuffix(".html"); // ビューの拡張子
        templateResolver.setTemplateMode(TemplateMode.HTML);

        // TemplateEngineの生成
        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
    }
    
    // その他のメソッド省略
}

今回はサーブレットが1つなので、サーブレットの初期化処理で書きましたが、本来のアプリケーション開発であれば、何らかの方法でシングルトンにしておくと良いでしょう。

SpringだったらBean定義すればいいですし*1Java EEならば@ApplicationScopedなプロデューサーメソッドとして作ってもいいでしょう。(詳細は最後の参考資料をご覧ください)

4つの記法

さて、ここから具体的なThymeleafによるテンプレート記述に入ります。

Thymeleafには、大きく分けて4つの記法があります。

リンク式(@{...}

HTMLとかJSPを書いていてけっこう面倒なのが、リンク/CSS/JS/画像などの「パスの記述」ではないでしょうか。

絶対パスで指定するためにコンテキストパスを毎回書いたり、相対パスで書くとプロジェクトのフォルダ構造と、サーバー上で実行される時のパスが違っていて、うまくパスを指定するのが難しかったりします。

こういった問題の対策として、Thymeleafにはリンク式という記法があります。

  • テンプレート
    <link rel="stylesheet" href="../../css/style.css" th:href="@{/css/style.css}">

th:href属性の@{...}の部分がリンク式です。/で始めると、レスポンスされるHTMLには、なんとコンテキストパスが補完されます!

href属性は、サーバーを通さずに直接ブラウザでHTMLを開いたときに利用されます。そして、サーバー上で実行したときには、href属性の値はth:href属性の値で上書きされます。

Thymeleafには、th:xxxという形式の属性がいっぱいあるのですが、基本的にはxxxという属性の値を上書きするものです。

  • レスポンスされるHTML
    <link rel="stylesheet" href="/beginning-thymeleaf/css/style.css">

なぜ、上書きされて消えてしまうhref属性を書くのかというと、直接ブラウザでHTMLを開いてもCSSが適用されるようにするためです。

こうすると、テンプレートをそのままブラウザで開いて、お客様に見せて打ち合わせなどができます。

本番用のソースコードがそのまま画面モックになるのが、Thymeleaf最大の特徴です。

メッセージ式(#{...}

Thymeleafでは、画面上のメッセージをプロパティファイルなどに記述し、国際化ができます。

やり方は簡単で、テンプレートの.htmlファイルと同じフォルダに、「テンプレート名.properties」というファイルを作るだけです。

例えば、hello.htmlに対するプロパティファイルは「hello.properties」「hello_ja.properties」などです。

言語は、クライアントからのAccept-LanguageHTTPリクエストヘッダで指定します。

指定された言語に対応したプロパティファイルがなかった場合は、ロケールなしの「hello.properties」が使われます。

  • hello.properties
backToTop=Back to top
nothingToShow=No users to show.
  • hello_ja.properties
backToTop=トップページに戻る
nothingToShow=表示するユーザーがありません。
  • hello.html(テンプレート)
<p th:text="#{nothingToShow}">表示するユーザーがありません。</p>

<a href="../../index.html" th:href="@{/}" th:text="#{backToTop}">トップページに戻る</a>

#{...}の部分がメッセージ式です。「nothingToShow」や「backToTop」は、プロパティファイルのキーを指定しています。

th:text属性は、画面にメッセージを表示するために使われる属性です。

サーバーで実行した時は、th:text属性を指定したタグに挟まれた部分が置き換えられます。

テンプレートに記述している「表示するユーザーがありません。」などのメッセージは、ブラウザで直接開いたときのための仮のメッセージで、サーバーで実行時はプロパティファイルのメッセージで上書きされます。

変数式(${...}

サーブレット側で、テンプレートに値を渡す処理は、こんな感じで書きます。

List<User> userList = ...;

// ビューに渡す値を保存するマップ
HashMap<String, Object> map = new HashMap<>();
map.put("userList", userList);

// レスポンスするHTMLを書き出す
WebContext webContext = new WebContext(request, response,
        getServletContext(), request.getLocale());
webContext.setVariables(map); // 値をビューに渡す
Writer writer = new OutputStreamWriter(
        response.getOutputStream(), StandardCharsets.UTF_8);
templateEngine.process("hello", webContext, writer); // /WEB-INF/views/hello.htmlを書き出す

少し面倒なコードに見えますが、このあたりはフレームワーク化すれば、毎回書く必要は無くなります。

この値をテンプレートで表示するには、変数式を使います。

  • テンプレート
<tr th:each="user : ${userList}">
    <td th:text="${user.id}">111</td>
    <td th:text="${user.name}">Yumi Wakatsuki</td>
</tr>

th:eachは繰り返しを記述するもので、後述します)

${...}の部分が変数式です。ほぼJSPのELと同じですね。

ここもth:textを使っているので、タグで挟まれた部分(「111」など)は、サーバー上で実行されたときには置き換えられます。

  • レスポンスされるHTML
<tr>
    <td>1</td>
    <td>User1</td>
</tr>
<tr>
    <td>2</td>
    <td>User2</td>
</tr>
<tr>
    <td>3</td>
    <td>User3</td>
</tr>

選択変数式(*{...}

先ほどのユーザーの表示の部分ですが、userというのをIDと名前で2回書いています。

これを簡略化できるのが、選択変数式です。

  • テンプレート
<tr th:each="user : ${userList}" th:object="${user}">
    <td th:text="*{id}">111</td>
    <td th:text="*{name}">Yumi Wakatsuki</td>
</tr>

上記の例だと、<tr>開始タグにth:object=${user}と書いています。

こうすると、<tr>で挟んでいる部分では、${user.id}ではなく*{id}と書くことができます。これが選択変数式です。

レスポンスされるHTMLは、変数式の場合とまったく同じになります。

  • レスポンスされるHTML
<tr>
    <td>1</td>
    <td>User1</td>
</tr>
<tr>
    <td>2</td>
    <td>User2</td>
</tr>
<tr>
    <td>3</td>
    <td>User3</td>
</tr>

テンプレートの記述

テンプレートでは欠かせない、条件分岐・繰り返しについて説明します。

条件分岐(th:ifth:unless

JSPで言うところの<c:if>に相当します。

<c:choose>に相当するものはありません。つまり、ifに対するelseみたいな記述はできません。

  • テンプレート
<table border="1" th:unless="${#lists.isEmpty(userList)}">
    ...
</table>

<p th:if="${#lists.isEmpty(userList)}">表示するユーザーがありません。</p>

#listsの部分はユーティリティオブジェクトというもの(後述)なのですが、ざっくり言うと、userListが空でなかったら<table>要素の部分のみが、空だったら<p>要素の部分のみが出力されます。

th:unlessは変数式で指定した条件がfalseの時に出力され、th:ifは変数式で指定した条件がtrueの時に出力されます。

  • レスポンスされるHTML(userListが空でない場合)
<table border="1">
    ...
</table>
  • レスポンスされるHTML(userListが空の場合)
<p>表示するユーザーがありません。</p>

条件を指定するには、普通のJavaのように==!=などを使うことができます。

不等号については、<>ではなく、lt<相当)、gt>相当)、le<=相当)、ge>=相当)、を使います。

andorで、複数の条件を指定することも可能です。

実は、true/falseだけでなく、下記のものはfalseと判断されます。逆に言うと、falseと下記以外は全てtrueと判断されます。

  • null
  • 0
  • "0"
  • “false”
  • “off”
  • “no”

これは、検索結果などを表示する時に非常に便利です。null比較をしなくて済みますね!

  • テンプレートの例(このコードはGitHubにはありません)
<!--/* th:if="${user != null}" と同じ */-->
<table border="1" th:if="${user}">
    <tr>
        <td th:text="*{id}">111</td>
        <td th:text="*{name}">Yumi Wakatsuki</td>
    </tr>
</table>

繰り返し(th:each

前にも少し出てきましたが、繰り返しはth:eachで表します。

下記の場合だと、th:each属性を記述している<tr>要素自体が繰り返し出力されます。

構文はth:each="コレクションから取り出した要素の変数名 : ${コレクションの変数名}">です。

  • テンプレート
<tr th:each="user : ${userList}" th:object="${user}">
    <td th:text="*{id}">111</td>
    <td th:text="*{name}">Yumi Wakatsuki</td>
</tr>
  • レスポンスされるHTML
<tr>
    <td>1</td>
    <td>User1</td>
</tr>
<tr>
    <td>2</td>
    <td>User2</td>
</tr>
<tr>
    <td>3</td>
    <td>User3</td>
</tr>

僕もたまにやってしまうのですが、<c:forEach>の感覚で使っていると、こんな書き方をしちゃいます。

  • テンプレート
<table th:each="user : ${userList}">
    <tr th:object="${user}">
        <td th:text="*{id}">111</td>
        <td th:text="*{name}">Yumi Wakatsuki</td>
    </tr>
</table>

こう書いてしまうと、<table>要素自体が繰り返されるので注意してください。th:eachは、あくまで繰り返したい要素自身に付加します。

その他の記法

ユーテリティオブジェクト(#listsなど)

変数式などの中で、ユーティリティオブジェクトを呼ばれるものを使うことができます。

ユーテリティオブジェクトは、変数式などの中で#オブジェクト名.メソッド名(引数)で利用します。

Thymeleafには、あらかじめいくつかのユーティティオブジェクトが定義されています。

#listsはその1つで、リストに関するメソッドをいくつか持っています。

#lists.isEmpty()メソッドは、引数で渡されたリストが、nullまたは要素数ゼロの場合にtrueを返します。

下記の場合は、th:unlessと組み合わせて、userListが「nullまたは要素数ゼロ」でない場合に<table>要素を出力しています。

  • テンプレート
<table border="1" th:unless="${#lists.isEmpty(userList)}">
    ...
</table>
  • レスポンスされるHTML
<table border="1">
    ...
</table>

他にも色々なメソッドやユーティティオブジェクトがあるので、詳細は公式ドキュメントの下記のページを確認してみてください。

http://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf_ja.html#appendix-b-expression-utility-objects

ちなみに、ユーティティオブジェクトは自作も可能です。

多分、SpringでThymeleafを使っていると、#fieldsというユーティティオブジェクトが見つかると思います。

これは素のThymeleafのものではなく、thymeleaf-spring4*2というライブラリ側が提供しているユーティティオブジェクトです。

コメント(<!--/* */-->

普通のHTMLコメント(<!-- -->)とすると、レスポンスされるHTMLにこのコメントが含まれてしまいます。

レスポンスされるHTMLに含めたくないコメントは、<!--/* */-->で書きます。

Thymeleafは、<!--/*から*/-->までをコメントとして認識し、テンプレートを解釈する時にこれらで挟まれた部分を無視します。

これを応用すると、こんなことが可能です。

<tr th:each="user : ${userList}" th:object="${user}">
    <td th:text="*{id}">111</td>
    <td th:text="*{name}">Yumi Wakatsuki</td>
</tr>
<!--/*-->
<tr>
    <td>222</td>
    <td>Reika Sakurai</td>
</tr>
<tr>
    <td>333</td>
    <td>Erika Ikuta</td>
</tr>
<!--*/-->

th:eachで繰り返しを書く時、ブラウザで直接開いた時も、複数件のデータが見えるようにします。

上記で言うと、222番や333番の人は、ブラウザで直接開いたときは見えるのですが、サーバー上で実行したときは、<!--/**/-->で挟まれているので、この部分は無視されます。

さらに勉強するための参考資料

以上、最低限必要な機能を紹介していきました。

Thymeleafに興味を持って、さらに勉強されたい方は、下記の資料を読んでみましょう。

まずは、@bufferingsさんの資料を読んで、一通りの機能を把握しましょう。

Welcome Thymeleaf 3.0! #jjug_ccc #ccc_f2 // Speaker Deck

合わせて、公式ドキュメントも確認しておきましょう。

Tutorial: Using Thymeleaf

@bufferingsさんが翻訳された日本語版もあります。Thymeleaf 2.xの頃のドキュメントですが、あまり大きくは変わっていません。

Tutorial: Using Thymeleaf (ja)

SpringでThymeleafを使うという人は、「Spring徹底入門」を読みましょう。

Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発(株式会社NTTデータ) | 翔泳社の本

Java EEで Thymeleafを使うという人は、僕のスライドを読んでみてください。

Java EEアクションベースMVC入門 #jjug_ccc #ccc_cd4 // Speaker Deck

それでは、Enjoy Thymeleaf!!

*1:正確には、Thymeleaf-Spring4というライブラリが提供しているクラスをBean定義します。詳しくはhttps://github.com/MasatoshiTada/spring4-thymeleaf/blob/master/src/main/java/com/example/web/WebMvcConfig.java

*2:ちなみに、thymeleaf-spring4はThymeleaf作者が自ら開発されています

Bean Validationでクライアント側のロケールに合わせたメッセージを出力する

やりたいこと

通常のBean Validationでは、クライアント側ではなく、サーバー側のロケールのエラーメッセージになってしまいます。

なので、クライアント側のロケールに合わせて

$ curl -v -H "Accept: text/html" -H "Accept-Language: ja" http://localhost:8080/jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a

としたらValidationMessages_ja.propertiesのエラーメッセージになり、

$ curl -v -H "Accept: text/html" -H "Accept-Language: en" http://localhost:8080/jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a

としたらValidationMessages_en.propertiesのエラーメッセージになり、

$ curl -v -H "Accept: text/html" -H "Accept-Language: cs" http://localhost:8080/jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a

としたらValidationMessages.propertiesのエラーメッセージになるようにします。

独自ResourceBundleLocatorの作成

Bean Validation参照実装のHibernate Validator独自インタフェースです。

ResourceBundleLocatorは、Localが引数で戻り値がResourceBundleなメソッドを持っているので、この中でゴニョゴニョします。

package com.example.rest.validation;

import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;

import java.util.Locale;
import java.util.ResourceBundle;

class NoFallbackControlResourceBundleLocator implements ResourceBundleLocator {
    @Override
    public ResourceBundle getResourceBundle(Locale locale) {
        ResourceBundle.Control control =
                ResourceBundle.Control.getNoFallbackControl(
                        ResourceBundle.Control.FORMAT_DEFAULT);
        ResourceBundle bundle = ResourceBundle.getBundle("ValidationMessages", locale, control);
        return bundle;
    }
}

ResourceBundleは、指定されたロケールに合致したプロパティファイルが無い場合、デフォルトロケール(僕の環境だとja_JP)のプロパティファイルを選びます。

これは今回のやりたいことに合致しないので、「フォールバック制御」を行っています。

getNoFallbackControl()メソッドによって、ロケールに合致したプロパティファイルが無い場合、ロケール無しのプロパティファイルが選ばれるようになります。

参考資料

櫻庭さんの記事です。上記の内容は、この記事を読んで初めて知りました・・・。

Java技術最前線 - 「Java SE 6完全攻略」第54回 ResourceBundleの新機能 その2:ITpro

独自MessageInterpolatorの作成

MessageInterpolator自体は、Bean Validationで定義されているインタフェースです。

今回は、Hibernate Validatorが持っているMessageInterpolator実装クラスを継承して、メソッドをオーバーライドします。

package com.example.rest.validation;

import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;

import java.util.Locale;

class LocalizedMessageInterpolator extends ResourceBundleMessageInterpolator {

    private final Locale locale;

    LocalizedMessageInterpolator(Locale locale) {
        super(new NoFallbackControlResourceBundleLocator());
        this.locale = locale;
    }

    @Override
    public String interpolate(String messageTemplate, Context context) {
        return super.interpolate(messageTemplate, context, this.locale);
    }

    @Override
    public String interpolate(String messageTemplate, Context context, Locale locale) {
        return super.interpolate(messageTemplate, context, locale);
    }
}

コンストラクタでロケールを受け取り、1つ目のinterpolate()メソッド内で利用します。

2つ目のinterpolate()メソッドはどんな時に呼ばれるのかは、まだ不明です... (^^;

参考資料

Hibernate Validatorのドキュメント

Chapter 4. Interpolating constraint error messages

ローカライズしたValidatorの作成

package com.example.rest.validation;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.CDI;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@ApplicationScoped
public class LocalizedValidator {

    private ConcurrentMap<Locale, Validator> validatorCache = new ConcurrentHashMap<>();

    public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
        HttpServletRequest httpServletRequest = CDI.current().select(HttpServletRequest.class).get();
        Locale locale = httpServletRequest.getLocale();
        Validator validator = validatorCache.computeIfAbsent(locale, (keyLocale) ->
            Validation.byDefaultProvider()
                    .configure()
                    .messageInterpolator(new LocalizedMessageInterpolator(keyLocale))
                    .buildValidatorFactory()
                    .getValidator()
        );
        return validator.validate(object, groups);
    }
}

HttpServletRequestからクライアント側のロケールを取得し、LocalizedMessageInterpolatorに渡します。

Validatorの生成を毎回行なうのは効率的でないので、キャッシュしました。

ValidatorFactoryおよびValidatorはスレッドセーフとJavadocに書いてありますので、キャッシュして使い回しても大丈夫なはず・・・です。

僕はあまりスレッドセーフ関連は詳しくないので、ご自分でも十分に調査の上、利用してください。

参考資料

槙さんのスライド

そんなリザルトキャッシュで大丈夫か? #jjug

コントローラークラスの作成

@Path("employee")
@RequestScoped
@Produces(MediaType.TEXT_HTML)
public class EmployeeController {

    @Inject
    private LocalizedValidator validator;

    @GET
    @Path("result")
    public ThymeleafViewable result(@BeanParam EmployeeIdForm form) throws Exception {
        // バリデーション実行
        Set<ConstraintViolation<EmployeeIdForm>> violations = validator.validate(form);
        // エラーがあれば入力画面に戻る
        if (!violations.isEmpty()) {
            HashMap<String, Object> models = new HashMap<>();
            models.put("violations", violations);
            return new ThymeleafViewable("employee/index.html", models);
        }

先ほど作成したLocalizedValidatorをDIし、コントローラーメソッド内でバリデーションを実行します。

プロパティファイルの作成

日本語用 src/main/resources/ValidationMessages_ja.properties

employee.id.notblank=社員IDは必須入力です。
employee.id.pattern=社員IDは整数で入力してください。

英語用 src/main/resources/ValidationMessages_en.properties

employee.id.notblank=Employee ID must not be blank.
employee.id.pattern=Employee ID must be integer.

それ以外用(英語のメッセージの先頭に「(Def)」をつけています) src/main/resources/ValidationMessages.properties

employee.id.notblank=(Def)Employee ID must not be blank.
employee.id.pattern=(Def)Employee ID must be integer.

実行

$ curl -v -H "Accept: text/html" -H "Accept-Language: ja" http://localhost:8080/jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: text/html
> Accept-Language: ja
>
< HTTP/1.1 200 OK
< Server: Payara Micro #badassfish
< Set-Cookie: JSESSIONID=43dfaf692f43c4bcdd999837d7c9; Path=/jjug-my-mvc-1.0-SNAPSHOT; HttpOnly
< Content-Type: text/html
< Date: Thu, 09 Jun 2016 08:18:45 GMT
< Content-Length: 644
<
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
(中略)
<ul class="error">
    <li>社員IDは整数で入力してください。</li>

</ul>
(中略)
* Connection #0 to host localhost left intact
$ curl -v -H "Accept: text/html" -H "Accept-Language: en" http://localhost:8080/jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: text/html
> Accept-Language: en
>
< HTTP/1.1 200 OK
< Server: Payara Micro #badassfish
< Set-Cookie: JSESSIONID=43e9b73c50fe3be35f60bd734842; Path=/jjug-my-mvc-1.0-SNAPSHOT; HttpOnly
< Content-Type: text/html
< Date: Thu, 09 Jun 2016 08:19:26 GMT
< Content-Length: 616
<
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
(中略)
<ul class="error">
    <li>Employee ID must be integer.</li>

</ul>
(中略)
* Connection #0 to host localhost left intact
$ curl -v -H "Accept: text/html" -H "Accept-Language: cs" http://localhost:8080/jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /jjug-my-mvc-1.0-SNAPSHOT/api/employee/result?id=a HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: text/html
> Accept-Language: cs
>
< HTTP/1.1 200 OK
< Server: Payara Micro #badassfish
< Set-Cookie: JSESSIONID=43efe0e6f24b7d015771568feb84; Path=/jjug-my-mvc-1.0-SNAPSHOT; HttpOnly
< Content-Type: text/html
< Date: Thu, 09 Jun 2016 08:19:51 GMT
< Content-Length: 640
<
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
(中略)
<ul class="error">
    <li>(Def)Employee ID must be integer.</li>

</ul>
(中略)
* Connection #0 to host localhost left intact

今回のコード

https://github.com/MasatoshiTada/jjug-action-based-mvc/tree/master/jjug-validation

https://github.com/MasatoshiTada/jjug-action-based-mvc/tree/master/jjug-my-mvc

【書評】Spring Microservices(洋書)

www.packtpub.com

Spring Cloudの勉強をするために買いました。

内容としては、

  • Spring Bootの基礎
  • Microservicesとは何ぞや?その背景は?
  • Circuit BreakerやService Discoveryって何ぞや?なぜ必要なのか?
  • Spring Cloudの基礎
  • Docker、Mesos

など、かなり幅広いです(ページ数もそこそこ多い)。

背景やなぜ必要なのか、というところはすごく参考になりました。 また、Spring Cloudでアプリケーションを作るところは、そこそこ多いマイクロサービス(最終的には10個くらいになる)を手順を追いながら作っていくので、手を動かして学びたい人にはうってつけな本です。

総じて良い本だと思いますが、注意点があります。

1つ1つの手順があまり詳しく書いておらず、「え?このソースコード、どのマイクロサービスの!?」と思うことがとても多いです。また、誤植も散見され、そのままだと動かんやろ、という部分もいくつかありました。

正直言って、Spring BootやSpring Cloudが全く初めてという方には向きません。ある程度は学習したことがある方が、より理解を深めるための本だと思います。

「新人研修や本では教えてくれないJava!」というタイトルで発表してきました #jjug_ccc

昨日のJJUG CCC 2016 Fallで発表してきました。ありがたいことに、CCC登壇は6回連続6回目になります。

speakerdeck.com

朝イチ10時から、かつ基調講演の裏番組にもかかわらず、120名くらいの部屋がほぼ満席となりました。

ご参加いただいた皆様、そして運営されていたJJUG幹事やボランティアスタッフの皆様、本当にありがとうございました。

また、感想ブログや全セッションのスライドは、こちらにまとまっています。

GitHub - jjug-ccc/slides-articles-2016fall: JJUG CCC 2016 Fallの発表資料およびブログ記事まとめ

発表内容

この発表をしようと思った理由

僕は研修トレーナーが仕事なので、毎年春には新人研修を担当しています。それ以外の期間は、新人以外の一般のお客様向けに、自社開催の研修だったり、お客様先で研修したりしています。

今回の発表のような内容は、通常、新人研修では扱いません。また、1つにまとまった書籍や資料が無いためか、新人でなくても意外とご存知ない方が多いと感じていました。

だったら資料を作って公開しちゃえ!と思ったのがきっかけです。

発表の最初に、「どちらかというと『初心者』だという方?」「どちらかというと『指導役』だという方?」と全体に対して伺ったところ、ちょうど半々くらいでした。

資料を公開したツイートは、かなり多くのリツイートやいいねをいただきました。

また、反応もいくつかいただけました。お役に立てたようで、とても嬉しいです!

ちなみに、Speaker Deckにアップしているスライドは、本来話したかった内容が全て含まれた「完全版」です。

ただ、想像以上にボリュームが膨らんでしまったため、発表はその抜粋版で行いました。発表を聞いていただいた方も、ぜひこの「完全版」をご確認ください。

この資料はPDFでアップしていますので、ダウンロードや社内でのご利用等は、ご自由にどうぞ!

著作権は所属会社であるカサレアルに属しますので、有償のサービスで使うことだけはご遠慮ください)

聴講したセッション

Java EE - What's Next? #ccc_a2

Oracleの方によるJava EE 8、Java EE 9の今後のプランに関するセッションでした。以前、JavaOne報告会で寺田さんがお話されていたので、それを再確認する感じでした。

冒頭、「Microservices」や「Cloud Native」という言葉が出てきたので、Oracleもこういうところに本腰を入れていくんだなあ、と改めて実感しました。

僕の理解では、本丸は2018年末に出るJava EE 9で、EE 8はその前フリ、という感じです。

MVC 1.0が(おそらく)標準では入らなくなることは残念ですが、Java EEが前進したので、個人的には全体的に満足しています。

余談ですが、Anilさんの英語は聞き取りやすく、ちょっとばかし自分の英語ぢからアップを感じ取れました。

でも、50分間は集中力が持たず、後半は通訳の方に頼りっぱなしでした。まだまだやなあ・・・。

Event Driven Microservices with Spring Cloud Stream #ccc_ab3

最近はSpring業務も多いので、槙さんのセッションは外せませんでした。

メッセージングとかキューは、最近ちょっと使っててとても面白いなあと思ったので、自分でもやってみたいです。

しかし本当に、Springは進んでいるなあ。Java EEも頑張ってほしいなあ・・・。

Spring CloudでDDD的なマイクロサービスを作ってみる #ccc_ab4

2016 SpringでThymeleafのセッションをされた椎葉さんのセッション。

僕はDDD本も読んだことないのでぼんやりとしか知らなかったのですが、ちょっとだけ見えたような気がします。

ソースコードも公開されていたので、後でじっくり確認してみます!

JAX-RS REST Client で Cognitive Service や Excel を操作しよう #ccc_cd5

Microsoft寺田さんのセッション。最近話題のCognitive Serviceを知りたくて参加しました。

音声・画像・動画・テキストから、顔や物体や感情を判定するサービスです。

非常に面白いなあと思いました。色々と活用できる場面がありそうですね。

JPA と DDD の関係で僕が思っていること #ccc_ab6

Qiitaで有名な@opengl_8080さんのセッションです。

公私とも、ずーっとこの方のQiitaにはお世話になっています。去年のCCC Fallで初めてお話ししたのでした。

JPAについて徹底的に調べられていて、それなりにJPA歴がある僕でも「え!?知らんかった・・・」ということが沢山ありました。

やっぱり、しっかり仕様を確認することと、実験することが大事ですね。

Payara Micro の設計と実装 #ccc_ab7

我らがGlassFish User Group Japan会長、蓮沼さんのセッションです。

GlassFishソースコードをフォークして、英国C2B2社がオープンソースで開発しているのが「Payara」です。そして、Payara Microはその組み込みサーバー版です。

このセッションでは、Payara Microのソースコードから、起動・クラスタリング・デプロイの仕組みについて解説されていました。

さすが蓮沼さん、知識が膨大だ・・・。でも、とても興味深かったです。

参加を終えてどう感じたか

マイクロサービスやクラウドネイティブというのが当たり前になりつつある、というのが率直な感想でした。

そして、来年のCCCのがある頃には、完全に「当たり前」のものになっているだろうと思います。

研修、そしてトレーナー自身も、進化を続けていかなければならないと、改めて思いました。

そして、聴くだけで終わらせるのではなく、どれか知った内容を今日から実践してみよう、とも思います。

槙さんが発表されていたSpring Cloud Streamをやってみたいなー!

運営に関して

改めて、JJUG幹事とボランティアスタッフのみなさま、ありがとうございました。そして、お疲れ様でした。

おかげさまで、非常に楽しい1日となりました。

運営が非常にスムーズで、特に問題点のようなものは見つけられませんでした。

敢えて言えば、参加者数が増えたことで、お手洗いが混んでいたなあ・・・というくらいでしょうか。それで次のセッションにちょっと遅刻しちゃったことが何回かありました。

セッション間の10分休憩を15分くらいにすると丁度いいかもしれません。午後に2セッションごとにある40分間の休憩も、体力回復のためとてもありがたいのですが、30分間くらいでも大丈夫と思います。そうすると、トータルでの終了時間(懇親会開始が19時半)は変わらないはずです。

(このことはアンケートにも記入済みです)

あと、聴きたいセッションの時間帯が重なってる!ということが多かったのですが、それはすなわち、どのセッションも魅力的であったということなので、これは致し方ないかと思います。

まとめ

久々に会えた方や、ツイッターでは知っていたものの対面でお話しすることは初めてだった方など、いろんな方にお会いできました。これもCCCの楽しいところですね。

来年も楽しみにしております!

【回避方法あり】Payara 4.1.1.164には認証に関するバグがありました

環境

Payara Web ML 4.1.1.164 確認していませんが、Full Profileでも同じバグがある可能性があります。 また、4.1.1.163以前のバージョンには、このバグはありません。

現象

JDBCリソースやレルムの設定を正しく行っても、アプリケーションでフォーム認証ができません。 具体的には、ログイン画面から正しいID・パスワードを入力してログインしても、403エラーになります。

原因

「デフォルトロールマッピングプリンシパル」の有効化が認識されないようです。 これは、管理コンソール・asadminコマンド・domain.xml直接書き換え、いずれを行なってもダメでした。

いつ修正されるのか

来年第1四半期にリリース予定のPayara 4.1.1.171で修正される予定です。

回避策

アプリケーションのWEB-INFフォルダにglassfish-web.xmlというファイルを作成し、下記のように記述してください。 こうすると、うまく「デフォルトロールマッピングプリンシパル」の有効化が認識されるようになります。

<glassfish-web-app>
    <property name="default-role-mapping" value="true">
        <description>Enable default group to role mapping</description>
    </property>
</glassfish-web-app>

もっと詳しいことは

GitHubにIssueを書きましたので、ご覧ください。

403 error occurs when Form-Authentication succeeded PAYARA-1244 · Issue #1213 · payara/Payara · GitHub

サンプルコードはこちら。

GitHub - MasatoshiTada/form-authentication-sample

Spring Boot 1.4でThymeleaf 3を使う

プロジェクト作成は、Spring Initializrとかで作成済みの前提です。

Spring Boot 1.4では、デフォルトでThymeleaf 2が使われます。

Thymeleaf 2ではXHTMLで書く必要がありますが、Thymeleaf 3だと完全にピュアなHTMLで書くことが可能です。

pom.xmlのpropertiesに下記の記述を追加します。

    <properties>
        <!-- その他のプロパティは省略 -->
        <thymeleaf.version>3.0.1.RELEASE</thymeleaf.version>
        <thymeleaf-layout-dialect.version>2.0.1</thymeleaf-layout-dialect.version>
    </properties>

Thymeleaf 3のバージョンと共に、Thymeleaf Layout Dialectのバージョンも指定する必要があります。

これだけで完了で、あとはビルドしなおせばThymeleaf 3が使われます。

Bean定義なども記述する必要はなく、本当にこれだけでおしまいです。

Gradleの場合はこちらを参照してください。

Support Thymeleaf 3 · Issue #4393 · spring-projects/spring-boot · GitHub

Java EE 7を勉強する方法(2016年ver.)

今年になって、Java EEの学習環境がガラッと変わりました。

2016年も半分以上過ぎてしましましたが(笑)、今からJava EE 7を勉強するにはどうすべきか、改めて書きたいと思います。

1. まずは作りながら学ぶ

以下3冊のいずれか1つで、Webアプリを作りながらJava EEの概要を理解しましょう。本屋さんで軽く立ち読みして、ご自分に合いそうな本を1つ選んでください。

www.shuwasystem.co.jp

www.shuwasystem.co.jp

www.shoeisha.co.jp

これらの本は、いずれもWebアプリを作りながらJava EEの基本を学ぶ、チュートリアルのような形式です。

「わかりやすいJava EE」と「パーフェクトマスター」は、サーブレットJSPの学習が終わったJava初心者の方向けです。ただし、RESTful Webサービスを作る技術「JAX-RS」の説明は、2冊とも無いので注意してください。

ある程度のJavaの経験(Webフレームワーク、DIコンテナ、ORマッパーを使った開発経験)があるならば、「Java EE 7徹底入門」が良いでしょう。この本には、JAX-RSの章があります。

2. ブログ情報を調べてみる

上記の入門書籍だけでは、実開発には足りない部分があると思います。

多くの方がブログやスライドを書かれていますので、ぜひ参考にしてみてください。

JSF

JSFでは、まずは菊田さん(@kikutaro_)のブログを読んでいただくのが良いでしょう。

JSF カテゴリーの記事一覧 - Challenge Java EE !

その他の読んでおくべき情報は、下記の記事にリンクをまとめておきました。

JSFを使うなら読んでおきたいリンクまとめ - Java EE 事始め!

JAX-RS

JAX-RSでは、まずはうらがみさん(@backpaper0)のスライドを読みましょう。

JAX-RS入門および実践

ブログはこちら。

この投稿のタグ JAX-RS — 裏紙

アクションベースMVC

Java EEの情報を調べていると、「JSFコンポーネントベースで、Strutsはアクションベースで・・・」という情報が見つかることも多いと思います。

アクションベースMVCは、現在はまだJava EE 7標準ではありませんが、使うこと自体は可能です。僕のスライドを読んでいただくのが一番良いと思います。

Java EEアクションベースMVC入門 #jjug_ccc #ccc_cd4 // Speaker Deck

CDI

上妻さん(@n_agetsu)のブログが良いでしょう。リンクは下記にまとめています。

CDI/JTAを使うなら読んでおきたいリンクまとめ - Java EE 事始め!

JPA

まずは、拙著スライドからどうぞ。

はまる!JPA(初学者向けライト版)
http://www.slideshare.net/masatoshitada7/jpa-46874399

より高度な内容については、下記のスライドを読んでみてください。

はまる!JPA
http://www.slideshare.net/makingx/jpa-29150059

金魚本に載ってないJPQLの話
http://d.hatena.ne.jp/megascus/20120925/1348575449

アプリケーションサーバ

サーバーそれぞれのエキスパートの方々がブログを書かれています。

GlassFish Japan

nekop's blog

yamadamnのはてな日記

Java EE全般

  • 上妻さんの資料

上妻さんがGlassFish勉強会で発表された、Java EEのパフォーマンスに関する資料です。

Java EE パフォーマンスTips #glassfish_jp

  • Java EE 7 Tutorial日本語訳まとめ

Java EE 7 Tutorialは、Oracle公式のチュートリアルです。全部ではありませんが、@kagamihogeさんが日本語訳をまとめていらっしゃいます。

The Java EE 7 Tutorialのテキトー翻訳まとめ - Qiita

  • ○○使い方メモ

@opengl_8080さんのブログです。Java EEに限らず、いろんな情報が書かれていてとても素晴らしいです。

特に、Bean ValidationとJTAの記事は必読です。

JavaEE使い方メモ(Bean Validation) - Qiita

JavaEE使い方メモ(JTA) - Qiita

各種Advent Calender

Java EE Advent Calendar 2015 - Qiita

Java EE Advent Calendar 2014 - Qiita

GlassFish Advent Calendar 2014 - Adventar

GlassFish Advent Calendar 2013 - Adventar

JBoss / WildFly (全部俺) Advent Calendar 2013 - Adventar

3. 仕上げは「パーフェクトJava EE」!

Java EE 7日本語書籍の決定版とも言うべき本です。内容の質・量とも素晴らしいです。開発でJava EEを使う方は必読でしょう。

gihyo.jp

4. 公式情報も確認しよう

かなり日本語情報が充実してきましたが、できれば英語の公式情報にも目を通しておきましょう。日本語情報が無い場合には、英語の1次情報をあたるのが最も良い方法です。OracleJava EE 7公式情報は、こちらにまとめられています。
Home: Java Platform, Enterprise Edition (Java EE) 7 Release 7

また、仕様書であるJSRも重要です。JSRはこちらから検索できます。「JPA JSR」のように「技術名 JSR」でググってもOKです。
The Java Community Process(SM) Program

まとめ

  • まずは書籍を読んで、手を動かしながらJava EEの基本を身につける!
  • 重要なブログ情報をチェック!
  • パーフェクトJava EEで仕上げ!
  • 公式情報もしっかりチェック!

もはや「Java EEは日本語情報が少ない」とは言えません。日本語情報は、現存の技術の中ではかなり充実している方だと思います。

ただし、日本語情報はGlassFish前提で書かれているものが多く、その他のサーバーでは細かい挙動が異なることも多々あります。

トラブル時に大事になってくるのは、英語の公式情報です。英語情報もしっかりとチェックしましょう。

それでは、Enjoy Java EE