Contents
@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を理解する上で欠かかせないのが、@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が発生します。