SpringでコレクションをDIする方法

技術情報

少しはまった箇所があるので備忘録がてら。
サンプルのアプリケーション。

@SpringBootApplication
@RestController
public class DemoApplication {

	private GreetingService service;

	@Autowired
	public DemoApplication(GreetingService service) {
		this.service = service;
	}

	@RequestMapping("/")
	String index() {
		String greetings = service.getGreetings().stream().collect(Collectors.joining(","));
		return greetings;
	}

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

DIした GreetingService を利用して挨拶を表示する。

GreetingService は以下。

@Component
public class GreetingService {

    private List<Greeting> greetings;

    @Autowired
    public GreetingService(List<Greeting> greetings) {
       this.greetings = greetings;
    }

    public List<String> getGreetings() {
        return greetings.stream().map(it -> it.hello()).collect(Collectors.toList());
    }
}

コンストラクタ・インジェクションでDIした Greeting のリストを回して、挨拶文を取得している。

Greeting は以下。

public interface Greeting {

    String hello();

    @Component
    static class EnglishGreeting implements Greeting {
        @Override
        public String hello() {
            return "Hello";
        }
    }

    @Component
    static class JapaneseGreeting implements Greeting {
        @Override
        public String hello() {
            return "こんにちは";
        }
    }
}

EnglishGreetingJapaneseGreeting の2つの実装クラスを作成し、 @Component によりコンポーネントを登録している。この2つのコンポーネントインスタンスが先ほどの GreetingService にDIされるため、画面に表示されるメッセージは「Hello,こんにちは」のようになる。

このように、List などのコレクション型を使った複数コンポーネントのDIは簡単に行うことができる。

ここで、 Greeting を以下のように一部コメントアウトして、コンポーネントが1件も見つからない状態を作る。

public interface Greeting {

    String hello();

    //@Component
    static class EnglishGreeting implements Greeting {
        @Override
        public String hello() {
            return "Hello";
        }
    }

    //@Component
    static class JapaneseGreeting implements Greeting {
        @Override
        public String hello() {
            return "こんにちは";
        }
    }
}

するとどうなるか?
Spring 5系では(確認したのはver 5.2.9)特に問題は起こらない。 GreetingService のコンストラクタには空のリストが渡される。
Spring 4系だと、Boot起動時に UnsatisfiedDependencyException が発生してしまう。

以下は、Spring 4系での対処方法を記す。

ググると、以下のように @Autowired アノテーションの属性に require = false を指定すればOKというのが見つかる。

    @Autowired(required = false)
    public GreetingService(List<Greeting> greetings) {
       this.greetings = greetings;
    }

たしかに GreetingService コンポーネントに関しては上記で例外発生を回避できるのだが、それを参照する DemoApplication 側で UnsatisfiedDependencyException が発生してしまう結果となった。

以下のように、 ListOptional で包んで受け取ることで例外が発生せず動作するようにできた。

    @Autowired
    public GreetingService(Optional<List<Greeting>> greetings) {
       this.greetings = greetings.orElseGet(ArrayList::new);
    }

おわり。

タイトルとURLをコピーしました