はじめに
Spring Bootでは、クラスを Bean としてDIコンテナに登録することで、インスタンスの生成や依存関係の管理をフレームワークに任せることができます。
Bean とはSpringが管理するオブジェクトのことで、DIコンテナがそのライフサイクルを管理します。@Autowired や コンストラクターインジェクションでBeanを受け取れるのは、DIコンテナがインスタンスを保持しているからです。
Beanとして登録するための主なアノテーションが @Component・@Service・@Repository・@Controller の4つです。
この記事ではそれぞれの用途について整理します。
全員 @Component の派生アノテーション
まず大前提として、4つはすべて @Component を元にした派生アノテーション(メタアノテーション) です。
// @Service の定義(イメージ)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component // ← @Component が付いている
public @interface Service { ... }
// @Repository の定義(イメージ)
@Component
public @interface Repository { ... }
// @Controller の定義(イメージ)
@Component
public @interface Controller { ... }
つまり機能的には @Component だけでDIコンテナへの登録はできます。では4つに分かれている理由は何でしょうか。
役割と使う層が違う
Spring Bootはレイヤードアーキテクチャを前提としており、アノテーションがその層を表します。
┌─────────────────────────────────┐
│ プレゼンテーション層 │ → @Controller / @RestController
│ (HTTPリクエスト・レスポンス) │
├─────────────────────────────────┤
│ ビジネスロジック層 │ → @Service
│ (業務処理・判断) │
├─────────────────────────────────┤
│ データアクセス層 │ → @Repository
│ (DBとのやりとり) │
├─────────────────────────────────┤
│ その他の汎用コンポーネント │ → @Component
└─────────────────────────────────┘
各アノテーションの詳細
@Component
どの層にも属さない、汎用的なBeanに使います。ユーティリティクラスや共通処理など、明確に層が決まらないクラスに使うイメージです。
@Component
public class DateUtils {
public String formatToJapanese(LocalDate date) {
return date.getYear() + "年" + date.getMonthValue() + "月" + date.getDayOfMonth() + "日";
}
}
@Service
ビジネスロジックを担うクラスに使います。「何をするか」という業務処理の判断が含まれる層です。
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("ユーザーが見つかりません: " + id));
}
public User save(User user) {
// バリデーション・重複チェック等のビジネスロジック
return userRepository.save(user);
}
}
@Repository
DBとのやりとりを担うクラスに使います。Spring Data JPAを使っている場合は JpaRepository を継承したインターフェースが自動的にこの役割を担うため、自分で @Repository を書く機会は少ないです。
// Spring Data JPAの場合、@Repository は不要(自動認識される)
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
JDBCを直接使うなど、自分でRepositoryクラスを実装する場合は明示的に付けます。
@Repository
public class UserJdbcRepository {
private final JdbcTemplate jdbcTemplate;
public List<User> findAll() {
return jdbcTemplate.query("SELECT * FROM users", userRowMapper());
}
}
@Repository には @Component にはない例外変換機能があります。DB固有の例外(SQLException 等)をSpringの DataAccessException に変換してくれるため、DB種別を意識せずに例外処理を書けます。
@Controller / @RestController
HTTPリクエストを受け取り、レスポンスを返す層に使います。
// @Controller:ビューを返す場合(Thymeleaf等)
@Controller
public class UserController {
@GetMapping("/users")
public String list(Model model) {
model.addAttribute("users", userService.findAll());
return "users/list"; // テンプレート名を返す
}
}
// @RestController:JSONを返す場合(REST API)
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserRestController {
private final UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
}
@RestController は @Controller + @ResponseBody の組み合わせです。REST APIを作る場合は @RestController を使います。
4つの比較まとめ
| アノテーション | 使う層 | 主な用途 | 追加機能 |
|---|---|---|---|
@Component |
汎用 | ユーティリティ・共通処理 | なし |
@Service |
ビジネスロジック層 | 業務処理・判断 | なし |
@Repository |
データアクセス層 | DBとのやりとり | 例外変換 |
@Controller |
プレゼンテーション層 | ビュー返却 | なし |
@RestController |
プレゼンテーション層 | JSON返却(REST API) |
@ResponseBody 付与 |
なぜ役割に合ったアノテーションを使うべきか
技術的には @Component だけでも動きます。それでも役割に合ったアノテーションを使う理由は2つあります。
① コードの意図が伝わる
@Service が付いているクラスを見れば、「ここにビジネスロジックがある」とすぐに読み取れます。
② Springの追加機能を受けられる
@Repository の例外変換のように、アノテーションに応じたSpringの機能が有効になります。
まとめ
- 4つはすべて
@Componentの派生アノテーションで、DIコンテナへの登録という点では同じ - 違いは「どの層で使うか」と「追加機能の有無」
- REST APIを作るなら
@RestController、ビジネスロジックは@Service、DBアクセスは@Repositoryを使う - どの層にも属さない汎用クラスには
@Componentを使う