ヘッダー画像

【Java】JakartaEE(JavaEE)でコンストラクタインジェクションがしたい

DI
投稿 2026年2月7日 最終更新 2026年2月7日 専門用語多め

JakartaEE(JavaEE)でコンストラクタインジェクションがしたい

Spring BootのDIはよくお世話になってますが、JakartaEE(JavaEE)※以降JakartaEE を使う機会があったので、Springと同じようにDIができるか試してみます。

JakartaEEではCDI(Contexts and Dependency Injection)と呼ばれるみたいです。

インジェクトの種類

インジェクトするには下記の三種類があります。

フィールドに直接Inject

@RequestScoped
public class TestDomain {
  @Inject
  private TestRepository testRepository;
}

メソッドでInject

@RequestScoped
public class TestDomain {
  private TestRepository testRepository;

  @Inject
  void setTestRepository(TestRepository testRepository) {
    this.testRepository = testRepository;
  }
}

コンストラクタでInject

@RequestScoped
public class TestDomain {
  private final TestRepository testRepository;

  @Inject
  TestDomain(TestRepository testRepository) {
    this.testRepository = testRepository;
  }
}

今回はテストをしやすいように外から依存関係を注入できる後者2つ、
さらに依存関係を注入するフィールドを定数(final)にすべく、コンストラクタインジェクションでやりたいと思います。

CDIの準備

依存関係

weld-servlet-shadedを使います。

dependencies {
  // Source: https://mvnrepository.com/artifact/org.jboss.weld.servlet/weld-servlet-shaded
  implementation("org.jboss.weld.servlet:weld-servlet-shaded:7.0.0.Alpha1")
}

設定ファイル

本来はCDI1.1以上であれば不要なはずなんですが、beans.xmlのデフォルト設定である
bean-discovery-mode="annotated"
のままだと、どうもうまく動かなくて
bean-discovery-mode="all"
にするべくbeans.xmlを配置しました。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
       bean-discovery-mode="all">
</beans>

bean-discovery-mode="annotated"だと、アノテーションがついているクラスしか認識させない設定なので、
パフォーマンスが向上して推奨されていますが、原因をいまだ調査中です…。

beans.xmlの配置場所

インジェクトされる.classファイルが、
WEB-INF/lib配下にある場合
 jarファイル内のMETA-INF/beans.xml
WEB-INF/classes配下にある場合、
 WEB-INF/beans.xml
に配置します。

インジェクションしてみる

フォルダ構成は下記のとおりです。

TestProject
┗WebContent
 ┗WEB-INF
  ┗lib
   ┗test.jar
    ┣META-INF
    ┃┣MANIFEST.MF
    ┃┗beans.xml
    ┗com
     ┗test
      ┣servlet
      ┃┗TestServlet.class
      ┣domain
      ┃┣TestRepository.class
      ┃┗TestService.class
      ┗infrastructure
       ┗TestDataSource.class

TestServlet.java

エンドポイントとなるサーブレットクラスです。

サーブレットはコンストラクタが作れないので、メソッドでインジェクトします。

package com.test.servlet;

import com.test.domain.TestService;
import jakarta.inject.Inject;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;

@WebServlet("/test")
public class TestServlet extends HttpServlet {
  private TestService testService;

  @Inject
  protected void setTestService(final TestService testService) {
    this.testService = testService;
  }

  @Override
  protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
    response.setStatus(HttpServletResponse.SC_OK);
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json");
    try (PrintWriter out = response.getWriter()) {
      out.write("{\"message\":\"" + Objects.requireNonNull(testService).findAll() + "\"}");
    }
  }
}

TestService.java

コンストラクタでインジェクトします。

引数に指定している型はinterfaceです。

package com.test.domain;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;

import java.util.Objects;

@RequestScoped
public class TestService {
  private final TestRepository testRepository;

  protected TestService() {
    this.testRepository = null;
  }

  @Inject
  protected TestService(final TestRepository testRepository) {
    this.testRepository = testRepository;
  }

  public String findAll() {
    return Objects.requireNonNull(testRepository).findAll();
  }
}

TestRepository.java

interfaceです。

アノテーションは不要です。

package com.test.domain;

public interface TestRepository {
  String findAll();
}

TestDataSource.java

interfaceの実装クラスです。

先ほどのTestServiceに注入されるクラスです。

package com.test.infrastructure;

import com.test.domain.TestRepository;
import jakarta.enterprise.context.RequestScoped;

@RequestScoped
public class TestDataSource implements TestRepository {
  @Override
  public String findAll() {
    return "OK";
  }
}

個人的感想

  • フィールドインジェクション
    • テストしにくい
  • メソッドインジェクション
    • 変数をfinalにできない
  • コンストラクタインジェクション
    • デフォルトコンストラクタが必要
    • コンパイラでnull警告が出てしまう

なんでしょう…この何か足りない感じ…。

Lombokでコンストラクタ作成

Lombokでコンストラクタを作って、煩わしさを減らそうともしましたが…

@RequestScoped
@NoArgsConstructor(force = true, access = AccessLevel.PROTECTED)
@RequiredArgsConstructor(onConstructor_ = {@Inject}, access = AccessLevel.PROTECTED)
// こういう書き方もあるらしい?
// @RequiredArgsConstructor(onConstructor = @__({@Inject}), access = AccessLevel.PROTECTED)
public class TestDomain {
}
  • 注入しているのがわかりにくい
    • デフォルトコンストラクタだけLombok、引数付きコンストラクタだけ手動で作成すると、コンパイラがうまく認識してくれない
  • Lombokの書き方が独特すぎる
    • アノテーションを付与する方法(onConstructor)が、結局うまく動かなかった
    • onConstructorじゃなければ、Lombokの設定が必要

ということで結局あきらめて、全部コンストラクタを書くようにしました。

まとめ

SpringのDIに慣れすぎて、CDIにはちょっと苦戦しました…。

なんというか、微妙にかゆいところに届かない感じですねぇ。

逆に改めてSpringがすごいということがわかりました。

以上、ここまで見ていただきありがとうございます。

皆さまの快適な開発ライフに、ほんの少しでもお役に立てれば幸いです。

コメント

この記事のコメントはありません。

TOP