スポンサーリンク

@Autowired (SpringBootのアノテーション)

@Autowiredの公式API-Javadocより

Spring の依存性注入機能によってオートワイヤーされるように、コンストラクター、フィールド、setter メソッド、または構成メソッドをマークします。

アノテーション型 Autowired

@Autowiredの読み方は?

上記のJavadocに書いてある通り「autowire」は「オートワイヤー」と読みます。
なので、「@Autowired」は「アットオートワイヤード」が正しい読み方です。

SpringBootの@Autowiredをわかりやすく解説!

@Autowiredの使い方・サンプル

まずは@Autowiredのサンプルです。

@Controller
public class ChildrenController {

    @Autowired
    private ChildrenService childrenService;

    @RequestMapping("/children")
    public String index(Model model) {

        // 子どもリストを取得
        List<Child> children = childrenService.getAll();

        // 画面表示
        model.addAttribute("children", children);
        return "children/index";
    }
}

上記の例では、childrenServiceフィールドに@Autowiredが付与されています。
そのため、ChildrenServiceのインスタンスをnewしなくても、11行目でchildrenServiceフィールドのメソッドを使用できています。

childrenService = new ChildrenService();
List<Child> children = childrenService.getAll();

なぜ、上記のように呼び出したいクラスをnewしなくても、メソッドを呼び出すことができるのでしょうか?
NullPointerExceptionになってもおかしくない気がするかもしれません。
その答えは、@Autowiredを付与しているからなんです。

@Autowiredの仕組み

Springフレームワークのインスタンス管理の仕組みは次の図ようになっています。

@Autowiredの仕組み

@Autowiredを理解する上で欠かかせないのが、@Component/@Controller/@Service/@Repositoryといったアノテーションです。

それらのアノテーションはクラスブロックに対して付与します。
それらのアノテーションが付与されたクラスは、SpringBoot起動時に、フレームワークによってnewされて、コンテナに格納(図の①)されます。

一方、@Autowired は、コントローラークラスやサービスクラスのフィールドやコンストラクタに対して付与するアノテーションです。

@Autowiredが付与されたフィールドには、フレームワークがコンテナからインスタンスを取り出して、フィールドにインスタンスを注入(図の②)してくれます。
(このことをDI=Dependency Injection=依存性注入と言います)

@Autowiredを言葉の意味から考えると...

@Autowiredを英単語的に考えると、
・Auto
・wired
に分解できます。

Autoは「自動で」という意味です。
SpringBootにおいて自動とは「フレームワークが勝手にやってくれる」という意味になります。

wiredは「紐付けられた」という意味です。
変数の中身=インスタンスを紐づけてくれる ぐらいの意味になります。

SpringBootの@Autowiredを使うメリットは?

@Autowiredを使うと、Springフレームワークが自動でインスタンスを生成して、変数に格納してくれます。

もし、@Autowiredを使わないと、次の左側のコードのようにnew演算子を使用してインスタンスを生成しなければなりません。

しかし、右側のコードのように@Autowiredによる自動注入を使ってインスタンスを注入することで、保守性の高いコードになります。

保守性の低いコード

@Controller
public class ChildrenController {

    @RequestMapping("/children")
    public String index(Model model) {

        // 子どもリストを取得
        ChildrenService childrenService = new ChildrenService();
        List<Child> children = childrenService.getAll();

        // 画面表示
        model.addAttribute("children", children);
        return "children/index";
    }

保守性の高いコード

@Controller
public class ChildrenController {
    private final ChildrenService childrenService;

    @Autowired
    public ChildrenController(ChildrenService childrenService) {
        this.childrenService = childrenService;
    }

    @RequestMapping("/children")
    public String index(Model model) {

        // 子どもリストを取得
        List<Child> children = childrenService.getAll();

        // 画面表示
        model.addAttribute("children", children);
        return "children/index";
    }

右側のようにコンストラクターを通じてインスタンスを注入することを、コンストラクタインジェクションと言います。

new演算子使えばいいじゃない?と思うかもしれません。
しかし、コンストラクタインジェクションのように、外部からインスタンスを注入できるようにしておくことで、テスタビリティが確保できます

簡単にいうと、テスト用の擬似クラス(スタブ)に容易に切り替えられるようになります。

@Autowiredで注入できるクラスは?

@Autowiredにより自動で注入されるのは、第一には@Componentを付与したクラスです。
@Componentを付与したクラスが起動時にインスタンス化され、
@Autowiredを付与したフィールドにインスタンスが注入されます。

しかし、実際現場では@Controller@Service@Repositoryを目にする方が多いでしょう。
@Serviceや@RepositoryのJavadocを読むと「このアノテーションは @Component の特殊化としても機能」と書かれています。

つまり、
・Web 3層構造のコントローラ層に特化したComponentを、@Controllerで表す。
・Web 3層構造のサービス層に特化したComponentを、@Serviceで表す。
・Web 3層構造のデータ層に特化したComponentを、@Repositoryで表す。
ということになります。

@Componentと同じ意味を含むアノテーションは下記です。

・@Controller
・@Service
・@Repository
・@RestController
・@ControllAdvice
・@ManagedBean
・@Named

@Autowiredの使用例

コントローラークラスでサービスクラスをDIする

@Controller
public class ChildrenController {

    private final ChildrenService childrenService;

    @Autowired
    public ChildrenController(ChildrenService childrenService) {
        this.childrenService = childrenService;
    }

    @RequestMapping("/children")
    public String index(Model model) {

        // 子どもリストを取得
        List<Child> children = childrenService.getAll();

        // 画面表示
        model.addAttribute("children", children);
        return "children/index";
    }
}

冒頭のサンプルが、コントローラークラスでサービスクラスをDIする例です。

サービスクラスでリポジトリークラスをDIする

@Service
public class ChildrenService {

    private final ChildrenRepository childrenRepository;

    @Autowired
    public ChildrenService(ChildrenRepository childrenRepository) {
        this.childrenRepository = childrenRepository;
    }

    public List<Child> getAll() {
        // DBから取得
        List<ChildrenEntity> allChildren = childrenRepository.findAll();
        var children = allChildren
                .stream()
                .map(child -> new Child(child.getChildId(), child.getName(), child.getBirthday()))
                .collect(Collectors.toList());
        return children;
    }
}

サービスクラスでリポジトリークラスをDIする場合はこのようになります。
リポジトリークラスのクラス宣言では、@Repositoryを付与することを忘れないようにしましょう。
忘れると、コンテナにリポジトリークラスのインスタンスが登録されず、適切に@Autowiredしてくれません。

SpringBootで@Autowiredは非推奨になった?

Javadocによると、非推奨とは書かれていません。

Spring Framework 5.0 以降、@Autowired は技術的には個々のメソッドまたはコンストラクターパラメーターで宣言できますが、フレームワークのほとんどの部分ではそのような宣言は無視されます。

@Autowired オートワイヤーされたパラメーター

しかし、コンストラクタインジェクションの場合は、@Autowiredは無視されるため意味は無くなりました。
@Autowiredを省略できるということです。
(後述「@Autowiredを使わないDIの書き方の検証」を参照)

まずは、自分のSpring Frameworkのバージョンを調べてみましょう。
Spring Framework 5.0以降であれば、@Autowiredは省略できます。

Spring Framworkのバージョンの調べ方

自身のSpringBootプロジェクトの外部ライブラリを開きます。

spring-coreというライブラリがありますので、そのバージョンを見てみてください。
上の赤枠「5.3.15」がSpring Frameworkのバージョンです。

@Autowiredを使わないDIの書き方の検証

コンストラクタインジェクションは@Autowiredを省略できる

@Controller
public class ChildrenController {

    private final ChildrenService childrenService;

    // @Autowired 省略可
    public ChildrenController(ChildrenService childrenService) {
        this.childrenService = childrenService;
    }

    @RequestMapping("/children")
    public String index(Model model) {

        // 子どもリストを取得
        List<Child> children = childrenService.getAll();
...
}

フィールドに対して、コンストラクタでDIすることをコンストラクタインジェクションと言います。
これが現在、SpringBoot界隈で推奨されている方法です。

6行目で@Autowiredをコメントアウトしていますが、コンストラクタのchildrenServiceにはインスタンスが渡ってくるため、フィールドchildrenServiceにインスタンスが注入されます。

その結果、15行目の呼び出しも問題なく行うことができます。

フィールドインジェクションは@Autowiredを省略できない

@Controller
public class ChildrenController {

    // @Autowired 省略不可
    private ChildrenService childrenService;

    @RequestMapping("/children")
    public String index(Model model) {

        // 子どもリストを取得
        List<Child> children = childrenService.getAll();
...
}

4行目の @Autowiredをコメントアウトして動かした場合、
childrenService=nullとなり、10行目でNullPointerExceptionが発生します。

セッターインジェクションは@Autowiredを省略できない

@Controller
public class ChildrenController {

    private ChildrenService childrenService;

    // @Autowired 省略不可
    public void setChildrenService(ChildrenService childrenService) {
        this.childrenService = childrenService;
    }

    @RequestMapping("/children")
    public String index(Model model) {

        // 子どもリストを取得
        List<Child> children = childrenService.getAll();
...
}

あまり現場では見かけませんが、フィールドに対するsetterからDIする手法もあります。
しかし、setterに対する@Autowiredをコメントアウトして動かした場合、
childrenService=nullとなり、15行目でNullPointerExceptionが発生します。

スポンサーリンク

投稿日:2021年2月8日 更新日:

Copyright© 【Spring Hack】 , 2022 All Rights Reserved Powered by AFFINGER5.