読者です 読者をやめる 読者になる 読者になる

#chiroito ’s blog

Java を中心とした趣味の技術について

IntelliJ IDEA で CDI をデバッグする時の落とし穴

IntelliJ IDEA (以下 IntelliJ) を使用して CDI 実装の1つである Weld を使用したアプリケーションを開発している人は多いかと思います。 テストフェーズでは、テストが上手くいかず、原因を特定するためにデバッグ実行をして動きをステップ毎に確認することも多いと思います。しかしながら Weld はブレークポイントの入れ方次第で動作が大きく変わる事に注意が必要です。

今回は CDI の実装として Weld の 1.1 系と 2.3 系で確認してます。

問題が分かる例として次のような RequestScoped な Bean である HelloBean とサーブレットである HelloServlet を処理するとします。HelloBean はクラスフィールドとして作成したオブジェクトの数を保持し、オブジェクトが生成されると 1 つ加算し、オブジェクトが破棄されると 1 つ減算します。
※今回は AP サーバのスレッドを 1 つにして処理するため同期処理は気にしていません。

@RequestScoped
public class HelloBean {
    private static int OBJECT_NUM = 0;

    @PostConstruct
    public void init() {
        OBJECT_NUM++
    }

    @PreDestroy
    public void destroy() {
        OBJECT_NUM ++;
    }

    public String hello() {
        return "Hello";
    }
}

HelloServlet は HelloBean をインジェクトします。doGet メソッドは HelloBean オブジェクトが null かどうか確認し、HelloBean オブジェクトの hello メソッドを実行し、その前後で作成された HelloBean の数を出力します。

@WebServlet(name = "Hello", urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {

    @Inject
    private HelloBean bean;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

        res.getWriter().println("<html><body>");
        res.getWriter().println("HelloBean is " + ((this.bean == null) ? "" : "not") + " null<br/>");
        res.getWriter().println("HelloBean count is " + HelloBean.OBJECT_NUM  + "<br/>");
        res.getWriter().println(this.bean.hello() + "<br/>");
        res.getWriter().println("HelloBean count is " + HelloBean.OBJECT_NUM  + "<br/>");
        res.getWriter().println("</body></html>");
    }
}

この時、出力される結果はどうなるでしょうか?
HelloBean クラスのオブジェクトがインジェクトされているため、bean フィールドは null にはならず、OBJECT_NUM の数は 0 から 1 に増加していると考えるかと思います。
実際に出力されるコードは以下の通り、1 回目の OBJECT_NUM は 0 になります。

HelloBean is not null
HelloBean count is 0
Hello
HelloBean count is 1

この理由は、@Inject で渡されるオブジェクトが HelloBean クラスのプロキシであり、HelloBean オブジェクトではないためです。プロキシ先となる HelloBean のオブジェクトは HelloBean が持っている特定のメソッドが初めて呼ばれたタイミングでが作られます。特定のメソッドは HelloBean クラスが持つメソッドと toString() メソッドです。
IntelliJ では、デバッグをしながら実行すると、以下のようにコードの右側にオブジェクトの toString() メソッドの結果が出力されます。

f:id:chiroito:20170131010326p:plain

そのため、IntelliJ で Weld をデバッグしながら実行すると toString() メソッドが呼ばれたタイミングでオブジェクトが生成されるため、デバッグをしていない時と動きが変わってしまいます。

HelloBean is not null
HelloBean count is 1
Hello
HelloBean count is 1

今回のサンプルではブレークポイントが 1 つ下の行だった場合には動きに差はありません。ですが、実際のアプリケーションではどこに問題があるかを特定するのはすごく難しいでしょう。