Producesアノテーションでのスコープ管理

【前提条件】

[環境]

【概要】

前回はProducesアノテーションを使ったインエジェクションについて書きました。
今回はProducesアノテーションとスコープアノテーションを組み合わせて使ってみます。

【前提の設定】

pom.xmlとApplicationの設定は前回のエントリと同じです。

【インジェクションするクラス】

import java.io.Serializable;
import java.time.LocalDateTime;

public class CreatedDate implements Serializable {

    private final LocalDateTime dateTime;
    private final String name;

    public CreatedDate() {

        dateTime = null;
        name = null;
    }

    public CreatedDate(final String name, final LocalDateTime dateTime) {

        this.name = name;
        this.dateTime = dateTime;
    }

    public LocalDateTime getDateTime() {
        return dateTime;
    }

    public String getName() {
        return name;
    }
}

名前と時刻を持つクラスです。

【Producesのクラス】

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Named;

@Dependent
public class ScopeProduces {

    public static final String NONE = "none";
    public static final String DEPENDENT = "dependent";
    public static final String REQEUST = "request";
    public static final String SESSION = "session";
    public static final String APPLICATION = "application";

    @Produces
    @Named(NONE)
    public CreatedDate getNoneScope() {

        output(NONE);
        return createDate(NONE);
    }

    @Produces
    @Named(DEPENDENT)
    @Dependent
    public CreatedDate getDependentScope() {

        output(DEPENDENT);
        return createDate(DEPENDENT);
    }

    @Produces
    @Named(REQEUST)
    @RequestScoped
    public CreatedDate getRequestScope() {

        output(REQEUST);
        return createDate(REQEUST);
    }

    @Produces
    @Named(SESSION)
    @SessionScoped
    public CreatedDate getSessionScope() {

        output(SESSION);
        return createDate(SESSION);
    }

    @Produces
    @Named(APPLICATION)
    @ApplicationScoped
    public CreatedDate getApplicationScope() {

        output(APPLICATION);
        return createDate(APPLICATION);
    }

    private void output(final String label) {


            System.out.println(label + ":"
                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")));
    }

    private CreatedDate createDate(final String label) {

        return new CreatedDate(label, LocalDateTime.now());
    }
}

同じクラスをインジェクションするのでNamedアノテーションで分けるようにしています。
Producesアノテーションと各スコープアノテーションをつけてメソッドを作成します。
各メソッドではラベルと作成した時刻を標準出力しています。

【リソースクラス】

今回もJAX-RSからCDI経由で結果を出力します。

import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@RequestScoped
@Path("scope")
public class GetScope {

    @Inject
    @Named(ScopeProduces.NONE)
    private CreatedDate none;

    @Inject
    @Named(ScopeProduces.DEPENDENT)
    private CreatedDate dependent;

    @Inject
    @Named(ScopeProduces.REQEUST)
    private CreatedDate request;

    @Inject
    @Named(ScopeProduces.SESSION)
    private CreatedDate session;

    @Inject
    @Named(ScopeProduces.APPLICATION)
    private CreatedDate application;

    @GET
    public Response view() {

        return Response.ok(createLabels()).build();
    }

    private String createLabels() {

        StringBuilder builder = new StringBuilder();

        List<CreatedDate> dates = new ArrayList<>();
        dates.add(none);
        dates.add(dependent);
        dates.add(request);
        dates.add(session);
        dates.add(application);

        builder.append("<html>");
        builder.append("<head><title>TEST</title></head>");
        builder.append("<body>");

        builder.append("<table>");
        dates.forEach(v -> {
            builder.append("<tr>");

            builder.append("<td>");
            builder.append(v.getClass().getName());
            builder.append("</td>");

            builder.append("<td>");
            builder.append(v.getName());
            builder.append("</td>");

            builder.append("<td>");
            builder.append(v.getDateTime().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.nnnnnnnnn")));
            builder.append("</td>");

            builder.append("</tr>");
        });
        builder.append("</table>");

        builder.append("</body>");
        builder.append("</html>");

        return builder.toString();
    }
}

ScopeProducesクラスで定義したものをすべてインジェクションさせて、
インジェクションされたクラス名をHTMLとして出力しています。

【実行結果】

[画面]

作成したリソースにアクセスします。

jp.glory.cdi.produces.scope.CreatedDate	                          none        2015/10/31 10:03:52.444000000
jp.glory.cdi.produces.scope.CreatedDate	                          dependent	  2015/10/31 10:03:52.445000000
jp.glory.cdi.produces.scope.CreatedDate$Proxy$_$$_WeldClientProxy	request	    2015/10/31 10:03:52.458000000
jp.glory.cdi.produces.scope.CreatedDate$Proxy$_$$_WeldClientProxy	session	    2015/10/31 10:03:52.472000000
jp.glory.cdi.produces.scope.CreatedDate$Proxy$_$$_WeldClientProxy	application	2015/10/31 10:03:52.473000000

上記のような結果が返ってくると思います。
スコープアノテーションなし、Dependenスコープの場合は作成したクラス名が出ています。
他のスコープについてはProxyのクラス名が出力されています。

もう一度同じURLにアクセスします。

jp.glory.cdi.produces.scope.CreatedDate	                          none        2015/10/31 10:03:55.923000000
jp.glory.cdi.produces.scope.CreatedDate                           dependent   2015/10/31 10:03:55.923000000
jp.glory.cdi.produces.scope.CreatedDate$Proxy$_$$_WeldClientProxy	request     2015/10/31 10:03:55.924000000
jp.glory.cdi.produces.scope.CreatedDate                           session     2015/10/31 10:03:52.472000000
jp.glory.cdi.produces.scope.CreatedDate                           application 2015/10/31 10:03:52.473000000

今度はSessionスコープ、Applicationスコープは作成したクラス名が出ています。
以降、何度か更新してもRequestスコープだけがProxyが出力さています。

スコープアノテーションがある場合は作成したタイミングだけProxyが帰ってきて、
以降はインジェクションしたクラスが帰ってくるようです。

ためしに別のブラウザでアクセスしてみます。

jp.glory.cdi.produces.scope.CreatedDate                           none        2015/10/31 10:28:48.104000000
jp.glory.cdi.produces.scope.CreatedDate                           dependent   2015/10/31 10:28:48.105000000
jp.glory.cdi.produces.scope.CreatedDate$Proxy$_$$_WeldClientProxy request     2015/10/31 10:28:48.105000000
jp.glory.cdi.produces.scope.CreatedDate$Proxy$_$$_WeldClientProxy session     2015/10/31 10:28:48.106000000
jp.glory.cdi.produces.scope.CreatedDate                           application 2015/10/31 10:03:52.473000000

Requestスコープ、SessionスコープがProxy経由になっています。
それぞれRequesetスコープ、Sessionスコープ、Applicationスコープがスコープ通り、
スコープなし、Dependentスコープは毎回オブジェクトが生成されているのがわかります。

[ログ]

つづいて、System.out.printlnで出力した内容を確認してみます。
Producesアノテーションをつけたメソッド内で出力しているので、
メソッドが呼び出されたタイミングで出力されます。

まずは初回アクセスのログです。

none:2015/10/31 10:03:52
dependent:2015/10/31 10:03:52
request:2015/10/31 10:03:52
session:2015/10/31 10:03:52
application:2015/10/31 10:03:52

すべてのメソッドでログが出力されているのがわかります。

つづいて2回目以降アクセスのログです。

none:2015/10/31 10:03:55
dependent:2015/10/31 10:03:55
request:2015/10/31 10:03:55

Sessionスコープ、Applicationスコープが出力されていません。

続いて別ブラウザでアクセした時のログです。

none:2015/10/31 10:28:48
dependent:2015/10/31 10:28:48
request:2015/10/31 10:28:48
session:2015/10/31 10:28:48

Sessionスコープのものが出力されているのがわかります。

【まとめ】

Producesアノテーションを経由した場合でもスコープ通りにCDIで管理されているようです。

ただし、スコープで管理する場合、作成されたタイミングだけProxy経由になるようです。
Proxy経由で問題になることはあまりないと思うので、気にしなくても大丈夫そうです。
もしかしたら、コアな部分では影響があるかもしれないので、覚えておくと良さそうです。

Producesアノテーションをつけたメソッドは生成のタイミングで呼ばれるようです。
なので、オブジェクトの生成に時間がかかるものはメソッド内に書かないほうが良さそうです。
できれば、Applicationスコープとして定義するか
キャッシュの仕組みを作りキャッシュから取得すると良さそうです。