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