ThymeleafでJava SE 8のDate and Time APIを使う方法
やりたいこと
Thymeleafのビューで、java.time.LocalDate
などJava SE 8で入った日時クラス(Date and Time API)を使いたい。
Date and Time APIについてはこちらをどうぞ。
Java日付時刻APIメモ(Hishidama's Java8 Date and Time API Memo)
(蓮沼さんの資料は何故か見つからなかった・・・)
Thymeleafの拡張機能を追加
Thymeleafには、Date and Time APIを利用するための拡張機能があります。
https://github.com/thymeleaf/thymeleaf-extras-java8time
本体に入っていないのは恐らく、Thymeleaf本体はJava SE 6でコンパイルされているからだと思われます。
thymeleaf/CONTRIBUTING.markdown at 3.0-master · thymeleaf/thymeleaf · GitHub
pom.xmlに依存性を追加します。
<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> <version>3.0.0.RELEASE</version> <scope>compile</scope> </dependency>
Dialect
の追加
TemplateEngine
をnewしている場所で、Java8TimeDialect
を追加します。
templateEngine = new TemplateEngine(); templateEngine.addDialect(new Java8TimeDialect());
ビューからの利用
こんなクラスがあって、
public class Employee { ... private java.time.LocalDate joinedDate; // getter/setter }
ThymeleafでjoinedDate
をフォーマットして表示するには、下記のようにします。
<tr th:object="${employee}"> ... <td th:text="*{#temporals.format(joinedDate, 'yyyy-MM-dd')}">2020-01-01</td>
簡単ですね!
その他の機能
こちらのREADMEに、一通りの機能が説明されています。
Thymeleafのビューから@NamedなCDI管理ビーンにアクセスする
やりたいこと
こんなCDI管理ビーンがあって、
@Named @SessionScoped public class SessionDto implements Serializable { private String id; public String getId() { return id; } }
Thymeleafのビューからこんな感じで参照したいです。
<p th:text="${sessionDto.id}">...</p>
本当は上記みたいにやりたかったのですが、現時点では、下記のような感じで参照することができました。
<p th:text="${#cdi.bean('sessionDto').id}">...</p>
以下、やり方を解説します。
名前からCDI管理ビーンを取得するクラスの作成
package com.example.rest.thymeleaf; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.CDI; import java.util.Set; public class Cdi { public Object bean(String name) { CDI<Object> cdi = CDI.current(); BeanManager beanManager = cdi.getBeanManager(); Set<Bean<?>> beans = beanManager.getBeans(name); Bean<?> bean = beanManager.resolve(beans); Class<?> beanClass = bean.getBeanClass(); return cdi.select(beanClass).get(); } }
引数のname
をもとに、ビーンを取得するメソッドを作ります。
まずBeanManager
を取得して、名前からSet<Bean<?>>
を取得します。
名前だけではビーンを1つに特定できない(セッションスコープとかだとユーザー数だけ同じ名前のビーンがあるため)ので、resolve()
でビーンを1つに特定します。
そこからビーンのClass
オブジェクトを取得し、cdi.select(beanClass).get()
でようやっと目的のCDI管理ビーンそのもののインスタンスを取得できます。
何か効率的ではないような感じがするので、今後変更するかもしれません。
参考資料
羽生田さんのスライド 3.Java EE7 徹底入門 CDI&EJB
かずひらさんのブログ CDIのBeanManagerを使う - CLOVER
CDI管理ビーンを参照するDialectを自作する
ThymeleafのDialectを自作します。
まず、IExpressionObjectFactory
実装クラスを作成します。
この中で、先ほど作成したCdi
クラスをnewしています。
ビューからは、getAllExpressionObjectNames()
で返している名前でアクセスできます。
package com.example.rest.thymeleaf; import org.thymeleaf.context.IExpressionContext; import org.thymeleaf.expression.IExpressionObjectFactory; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class CdiExpressionFactory implements IExpressionObjectFactory { private static final String EXPRESSION_OBJECT_NAME = "cdi"; private static final Set<String> ALL_EXPRESSION_OBJECT_NAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(EXPRESSION_OBJECT_NAME))); @Override public Set<String> getAllExpressionObjectNames() { return ALL_EXPRESSION_OBJECT_NAMES; } @Override public Object buildObject(IExpressionContext context, String expressionObjectName) { if (EXPRESSION_OBJECT_NAME.equals(expressionObjectName)) { return new Cdi(); } return null; } @Override public boolean isCacheable(String expressionObjectName) { return false; } }
次に、Dialect
クラスを作成します。
IDialect
というインタフェースがあり、そのサブインタフェースとして下記の5つがあります。
IProcessorDialect
IPreProcessorDialect
IPostProcessorDialect
IExpressionObjectDialect
IExecutionAttributeDialect
今回は、IExpressionObjectDialect
を使いました。他の4つはまだ試せていません...(^^;
package com.example.rest.thymeleaf; import org.thymeleaf.dialect.AbstractDialect; import org.thymeleaf.dialect.IExpressionObjectDialect; import org.thymeleaf.expression.IExpressionObjectFactory; public class CdiDialect extends AbstractDialect implements IExpressionObjectDialect { private static final IExpressionObjectFactory EXPRESSION_OBJECT_FACTORY = new CdiExpressionFactory(); public CdiDialect() { super("cdi"); } @Override public IExpressionObjectFactory getExpressionObjectFactory() { return EXPRESSION_OBJECT_FACTORY; } }
参考資料
Thymeleafのドキュメント Tutorial: Extending Thymeleaf
Thymeleafのソースコード
自作Dialectの追加
TemplateEngine
をnewしている場所で、自作Dialectを追加します。
templateEngine = new TemplateEngine(); templateEngine.addDialect(new CdiDialect());
ビューの作成
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> ... <p th:text="${#cdi.bean('sessionDto').id}">...</p>
#cdi.bean()
で自作したCdi
クラスのbean()
メソッドを呼び出しています。
引数には@Named
で指定したビーン名を指定します。
今回は@Named
のvalue
属性をしていていないので、「クラス名の頭文字を小文字にした名前」がビーン名になります。
まとめ
今回の方法は「Expression Object」(#で始まるやつ)を使ってみました。
この方法は非常に簡単でいいですね。他の方法も試してみたいです。
コードはこちら。
https://github.com/MasatoshiTada/jjug-action-based-mvc/tree/master/jjug-my-mvc