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で指定したビーン名を指定します。

今回は@Namedvalue属性をしていていないので、「クラス名の頭文字を小文字にした名前」がビーン名になります。

まとめ

今回の方法は「Expression Object」(#で始まるやつ)を使ってみました。

この方法は非常に簡単でいいですね。他の方法も試してみたいです。

コードはこちら。

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