必要最小限のサンプルでThymeleafを完全マスター
この記事は?
Java EE Advent Calendar 2016 - Qiitaの12日目です。
昨日の記事は@yyYankさんの「どうすんのJava EE - Javaプログラマのはしくれダイアリー」でした。明日は@n_agetsuさんです。
Thymeleafは、Javaで作られたテンプレートエンジンです。JSPの代替技術として近年注目されていて、JJUG CCCなどで話を聞いていても、利用事例が増えているように感じます。
ブログ情報も多く、検索すると「チートシート」のようなブログがいっぱい出てきます。
ただ、Thymeleafは多機能なのでチートシートもボリュームがあり、Thymeleafを初めて学習する人にはちょっと重たいなあ・・・と感じていました。
そこで今回は、JSPから移行したい方が、まず最初に理解すべき必要最低限の項目をまとめました。
アジェンダ
- 環境準備
- 4つの記法
- リンク式(
@{...}
) - メッセージ式(
#{...}
) - 変数式(
${...}
) - 選択変数式(
*{...}
)
- リンク式(
- テンプレートの記述
- 条件分岐(
th:if
、th: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>
TemplateResolver
とTemplateEngine
の生成
今回は、サーブレットが1つ、Thymeleafのテンプレートが1つだけのシンプルなプログラムです。
Thymeleafの中心的なクラスが、TemplateResolver
とTemplateEngine
です。
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定義すればいいですし*1、 Java 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-Language
HTTPリクエストヘッダで指定します。
指定された言語に対応したプロパティファイルがなかった場合は、ロケールなしの「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:if
、th: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
(>=
相当)、を使います。
and
やor
で、複数の条件を指定することも可能です。
実は、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>
他にも色々なメソッドやユーティティオブジェクトがあるので、詳細は公式ドキュメントの下記のページを確認してみてください。
ちなみに、ユーティティオブジェクトは自作も可能です。
多分、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
合わせて、公式ドキュメントも確認しておきましょう。
@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に書いてありますので、キャッシュして使い回しても大丈夫なはず・・・です。
僕はあまりスレッドセーフ関連は詳しくないので、ご自分でも十分に調査の上、利用してください。
参考資料
槙さんのスライド
コントローラークラスの作成
@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(洋書)
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回目になります。
朝イチ10時から、かつ基調講演の裏番組にもかかわらず、120名くらいの部屋がほぼ満席となりました。
ご参加いただいた皆様、そして運営されていたJJUG幹事やボランティアスタッフの皆様、本当にありがとうございました。
また、感想ブログや全セッションのスライドは、こちらにまとまっています。
GitHub - jjug-ccc/slides-articles-2016fall: JJUG CCC 2016 Fallの発表資料およびブログ記事まとめ
発表内容
この発表をしようと思った理由
僕は研修トレーナーが仕事なので、毎年春には新人研修を担当しています。それ以外の期間は、新人以外の一般のお客様向けに、自社開催の研修だったり、お客様先で研修したりしています。
今回の発表のような内容は、通常、新人研修では扱いません。また、1つにまとまった書籍や資料が無いためか、新人でなくても意外とご存知ない方が多いと感じていました。
だったら資料を作って公開しちゃえ!と思ったのがきっかけです。
発表の最初に、「どちらかというと『初心者』だという方?」「どちらかというと『指導役』だという方?」と全体に対して伺ったところ、ちょうど半々くらいでした。
資料を公開したツイートは、かなり多くのリツイートやいいねをいただきました。
完全版の資料です。 #jjug_ccc #ccc_f1
— 多田真敏(MasatoshiTada) (@suke_masa) 2016年12月3日
[完全版] 新人研修や本では教えてくれないJava! // Speaker Deck https://t.co/6Gl8dcE8xw
また、反応もいくつかいただけました。お役に立てたようで、とても嬉しいです!
良い資料だー! / [完全版] 新人研修や本では教えてくれないJava! // Speaker Deck https://t.co/53Uq12VqMj
— かずひら (@kazuhira_r) 2016年12月3日
これはまとまってて嬉しい、うちの新入社員にも見てもらおう https://t.co/ncWiFGNROd
— Norito Agetsuma (@n_agetsu) 2016年12月3日
ちなみに、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を書きましたので、ご覧ください。
サンプルコードはこちら。
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つ選んでください。
これらの本は、いずれも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)のスライドを読みましょう。
ブログはこちら。
アクションベース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
蓮沼さん(@khasunuma)
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
各種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を使う方は必読でしょう。
4. 公式情報も確認しよう
かなり日本語情報が充実してきましたが、できれば英語の公式情報にも目を通しておきましょう。日本語情報が無い場合には、英語の1次情報をあたるのが最も良い方法です。OracleのJava 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は日本語情報が少ない」とは言えません。日本語情報は、現存の技術の中ではかなり充実している方だと思います。
ただし、日本語情報はGlassFish前提で書かれているものが多く、その他のサーバーでは細かい挙動が異なることも多々あります。
トラブル時に大事になってくるのは、英語の公式情報です。英語情報もしっかりとチェックしましょう。
それでは、Enjoy Java EE!