アノテーションベース Spring DIの復習

はじめに

普段はSpring Bootを利用して開発している DI周りは困ったら調べるくらいでしかないので、ちょっと復習しようと思う

学習内容

この辺に触れていく

@Component
@Autowire
@Configuration
@Bean
AnnotationConfigApplicationContext

環境

java周り

Apache Maven 3.9.3 (21122926829f1ead511c958d89bd2f672198ae9f)
Maven home: C:\Program Files\apache-maven-3.9.3
Java version: 17.0.2, vendor: Oracle Corporation, runtime: C:\Program Files\jdk-17.0.2
Default locale: ja_JP, platform encoding: MS932
OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"

pom

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.0</version>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

RestContollerを使って動作確認をしていく

やっていく

DIとは?

依存性の注入という単語で色々な方が解説されているので、割愛します。 公式の文章が気になったので引用します*1

この章では、制御の反転(IoC)原則の Spring Framework 実装について説明します。IoC は、依存性注入(DI)とも呼ばれます。これは、コンストラクター引数、ファクトリメソッドへの引数、ファクトリメソッドが構築または返された後にオブジェクトインスタンスに設定されるプロパティを通じてのみ、オブジェクトが依存関係(つまり、操作する他のオブジェクト)を定義するプロセスです。コンテナーは、Bean を作成するときにそれらの依存関係を注入します。このプロセスは、基本的に、クラスの直接構築または Service Locator パターンなどのメカニズムを使用して、Bean 自身がその依存関係のインスタンス化や場所を制御することの逆(そのため、Inversion of Control という名前)です。 めちゃめちゃ分かりづらいですが、IoC≒DIということ また、違いは次のブログのまとめがわかりやすかったです *2 ※IoCという概念を公式のドキュメントを読んで初めて知りました

アノテーションベースで実装してみる

model

@Data
public class Employee {
    @NonNull
    private final String name;
    @NonNull
    private final String id;
}

Controller

@RestController
public class EmployeeController {

    // @Autowired
    private final EmployeeServiceInterface employeeService;

    // コンストラクタインジェクション
    EmployeeController(EmployeeServiceInterface employeeService){
        this.employeeService = employeeService;
    }

    @RequestMapping("/getEmployee")
    public Employee getEmployee(){
        return employeeService.get();
    }
}

Service

public interface EmployeeServiceInterface {
    public Employee get();
}

@Service // Beanを登録する
public class EmployeeServiceImpl implements EmployeeServiceInterface {
    @Override
    public Employee get() {
        return new Employee("Admin","1");
    }
}

curlでアクセス、こんな感じで取れる

$ curl http://localhost:8080/getEmployee

Content           : {"name":"Admin","id":"1"}

@Seriveをつけることで、該当のクラスがDIコンテナに登録される (細かい話をすると@SpringBootApplicationが付与されたクラスと同じ階層か、配下のパッケージにDIしたいクラスが存在する必要がある)
本来インスタンス化したいクラス変数に@Autowiredを付与することでも、DIしたクラスの代入が可能となるが
公式によるとコンストラクタインジェクションが推奨されているので、このように実装した*3

ApplicationContextからBeanを取得する

新しくConfigクラスを追加する

@Configuration
public class MyConfig {

    @Bean
    Employee getEmployee() {
        return new Employee("MrMyConfig", "2");
    }
}

あんまり良い例ではないけど、Controllerを修正してApplicationContextからBeanを取得する

 public class EmployeeController {

     // @Autowired
-    private final EmployeeServiceInterface employeeService;
+//    private final EmployeeServiceInterface employeeService;

     // コンストラクタインジェクション
-    EmployeeController(EmployeeServiceInterface employeeService){
-        this.employeeService = employeeService;
-    }
+//    EmployeeController(EmployeeServiceInterface employeeService){
+//        this.employeeService = employeeService;
+//    }

     @RequestMapping("/getEmployee")
     public Employee getEmployee(){
-        return employeeService.get();
+//        return employeeService.get();
+        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
+        return context.getBean(Employee.class);
     }
 }

生成した社員インスタンスをDIコンテナに格納、Applicationコンテキストからgetしてくる
本来のConfigクラスの利用方法として、自作コードではなく外部ライブラリの機能をDIしたい場合に利用する
例えば、thymeleafを利用したSpring MVCのアプリを作成している場合、以下のような利用方法になる

@Configuration
public class JavaConfig {

    @Bean
    public ModelMapper modelMapper(){
        return new ModelMapper();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Configクラスでインスタンス化した社員オブジェクトが取得できることがわかる

$ curl http://localhost:8080/getEmployee

Content           : {"name":"MrMyConfig","id":"2"}

まとめ

なんか知っていることを改めて整理して書くことの難しさを感じた 出てくる単語や概念をすべて記載すると文章量が増えて分かりづらくなる 結果、誰向けに書いているのか分からなくなった(振り返りなので、これでいいかもしれないけど)

また、公式のドキュメントを見ていると色々な発見があったので、また備忘としてブログにまとめたい

引用

*1 Spring IoC コンテナーと Bean の導入 ::Spring Framework - リファレンス

*2 IoCコンテナ&AOPについて - HackMD

*3 依存性注入 :: Spring Framework - リファレンス

コンストラクター vs setter、どちらの DI を選ぶ?