Javaのアノテーションの書き方
Javaのアノテーションの書き方
Section titled “Javaのアノテーションの書き方”Spring Boot開発で頻繁に使用されるアノテーションを、頻出度とユースケース別に詳しく解説します。
アノテーションとは
Section titled “アノテーションとは”アノテーションは、Javaコードにメタデータを追加するための機能です。Spring Bootでは、アノテーションを使用して設定を簡潔に記述し、フレームワークの機能を活用できます。
頻出度の凡例
Section titled “頻出度の凡例”- ★★★★★: ほぼすべてのプロジェクトで使用(必須レベル)
- ★★★★☆: 多くのプロジェクトで使用(推奨レベル)
- ★★★☆☆: 特定の機能で使用(中頻度)
- ★★☆☆☆: 特殊なケースで使用(低頻度)
- ★☆☆☆☆: まれに使用(参考レベル)
Spring Framework基本アノテーション
Section titled “Spring Framework基本アノテーション”@Component ★★★★★
Section titled “@Component ★★★★★”用途: SpringコンテナにBeanとして登録する汎用的なアノテーション
ユースケース:
- カスタムユーティリティクラス
- 設定クラス
- 汎用的なサービスコンポーネント
実践例:
@Componentpublic class EmailValidator {
public boolean isValid(String email) { return email != null && email.contains("@"); }}
// 使用例@Servicepublic class UserService {
@Autowired private EmailValidator emailValidator; // 自動注入される
public void registerUser(String email) { if (emailValidator.isValid(email)) { // ユーザー登録処理 } }}注意点:
@Service、@Repository、@Controllerは@Componentの特殊版- より具体的なアノテーションがある場合は、そちらを使用する
@Service ★★★★★
Section titled “@Service ★★★★★”用途: ビジネスロジックを実装するサービス層のクラスに使用
ユースケース:
- ビジネスロジックの実装
- 複数のRepositoryを組み合わせた処理
- トランザクション管理が必要な処理
実践例:
@Service@Transactionalpublic class UserService {
@Autowired private UserRepository userRepository;
@Autowired private EmailService emailService;
public User createUser(UserCreateRequest request) { // ビジネスロジック: ユーザー作成 User user = new User(); user.setName(request.getName()); user.setEmail(request.getEmail());
User savedUser = userRepository.save(user);
// メール送信 emailService.sendWelcomeEmail(savedUser.getEmail());
return savedUser; }
public User findById(Long id) { return userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException("User not found: " + id)); }}よくある使い方:
@Transactionalと組み合わせてトランザクション管理- 複数のRepositoryを呼び出して複雑なビジネスロジックを実装
@Repository ★★★★★
Section titled “@Repository ★★★★★”用途: データアクセス層のクラスに使用。JPAの例外をSpringの例外に変換
ユースケース:
- JPAリポジトリのインターフェース
- カスタムデータアクセス実装
実践例:
// インターフェース(Spring Data JPA)@Repositorypublic interface UserRepository extends JpaRepository<User, Long> {
// メソッド名からクエリを自動生成 List<User> findByName(String name);
// カスタムクエリ @Query("SELECT u FROM User u WHERE u.email = :email") Optional<User> findByEmail(@Param("email") String email);}
// カスタム実装クラス@Repositorypublic class CustomUserRepositoryImpl implements CustomUserRepository {
@PersistenceContext private EntityManager entityManager;
@Override public List<User> findUsersWithCustomLogic() { // カスタム実装 return entityManager.createQuery( "SELECT u FROM User u", User.class ).getResultList(); }}注意点:
- Spring Data JPAを使用する場合、インターフェースに
@Repositoryは省略可能(自動的に認識される) - カスタム実装クラスには明示的に
@Repositoryを付与
@Controller ★★★★★
Section titled “@Controller ★★★★★”用途: MVCパターンのコントローラー層。主にHTMLビューを返す場合に使用
ユースケース:
- サーバーサイドレンダリング(Thymeleaf、JSPなど)
- フォーム処理
- リダイレクト処理
実践例:
@Controller@RequestMapping("/users")public class UserController {
@Autowired private UserService userService;
// GET /users - ユーザー一覧ページ @GetMapping public String listUsers(Model model) { List<User> users = userService.findAll(); model.addAttribute("users", users); return "users/list"; // templates/users/list.html }
// GET /users/{id} - ユーザー詳細ページ @GetMapping("/{id}") public String showUser(@PathVariable Long id, Model model) { User user = userService.findById(id); model.addAttribute("user", user); return "users/detail"; }
// GET /users/new - ユーザー作成フォーム @GetMapping("/new") public String newUserForm(Model model) { model.addAttribute("user", new User()); return "users/form"; }
// POST /users - ユーザー作成処理 @PostMapping public String createUser(@ModelAttribute User user) { userService.createUser(user); return "redirect:/users"; // リダイレクト }}@RestControllerとの違い:
@Controller: HTMLビューを返す@RestController: JSON/XMLなどのデータを返す(REST API)
@RestController ★★★★★
Section titled “@RestController ★★★★★”用途: REST APIのエンドポイントを提供するコントローラー
ユースケース:
- RESTful APIの実装
- JSON/XMLレスポンスの返却
- フロントエンド(React、Vue.jsなど)との連携
実践例:
@RestController@RequestMapping("/api/users")public class UserRestController {
@Autowired private UserService userService;
// GET /api/users - ユーザー一覧取得 @GetMapping public ResponseEntity<List<UserDTO>> getUsers() { List<UserDTO> users = userService.findAll() .stream() .map(UserDTO::fromEntity) .collect(Collectors.toList()); return ResponseEntity.ok(users); }
// GET /api/users/{id} - ユーザー詳細取得 @GetMapping("/{id}") public ResponseEntity<UserDTO> getUser(@PathVariable Long id) { User user = userService.findById(id); return ResponseEntity.ok(UserDTO.fromEntity(user)); }
// POST /api/users - ユーザー作成 @PostMapping public ResponseEntity<UserDTO> createUser( @Valid @RequestBody UserCreateRequest request) { User user = userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED) .body(UserDTO.fromEntity(user)); }
// PUT /api/users/{id} - ユーザー更新 @PutMapping("/{id}") public ResponseEntity<UserDTO> updateUser( @PathVariable Long id, @Valid @RequestBody UserUpdateRequest request) { User user = userService.updateUser(id, request); return ResponseEntity.ok(UserDTO.fromEntity(user)); }
// DELETE /api/users/{id} - ユーザー削除 @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); }}よくある使い方:
@RequestMappingでベースパスを指定@GetMapping、@PostMappingなどでHTTPメソッドを指定@RequestBodyでJSONリクエストを受け取るResponseEntityでHTTPステータスコードを制御
@Autowired ★★★★★
Section titled “@Autowired ★★★★★”用途: 依存性注入(DI)。SpringコンテナからBeanを自動的に注入
ユースケース:
- サービス層への依存注入
- リポジトリへの依存注入
- 設定クラスへの依存注入
実践例:
@Servicepublic class UserService {
// フィールドインジェクション(非推奨) @Autowired private UserRepository userRepository;
// コンストラクタインジェクション(推奨) private final EmailService emailService;
@Autowired public UserService(EmailService emailService) { this.emailService = emailService; }
// セッターインジェクション private NotificationService notificationService;
@Autowired public void setNotificationService(NotificationService notificationService) { this.notificationService = notificationService; }}
// コンストラクタインジェクション(@Autowired省略可能 - Spring 4.3以降)@Servicepublic class OrderService {
private final OrderRepository orderRepository; private final PaymentService paymentService;
// @Autowiredは省略可能 public OrderService(OrderRepository orderRepository, PaymentService paymentService) { this.orderRepository = orderRepository; this.paymentService = paymentService; }}推奨される使い方:
- コンストラクタインジェクションを推奨(不変性、テスト容易性、必須依存関係の明確化)
- フィールドインジェクションは避ける(テストが困難、不変性が保証されない)
注意点:
- 複数のBean候補がある場合は
@Qualifierを使用 - オプショナルな依存関係には
@Autowired(required = false)を使用
@Value ★★★★☆
Section titled “@Value ★★★★☆”用途: プロパティファイルや環境変数から値を注入
ユースケース:
- アプリケーション設定値の注入
- 環境変数の読み込み
- デフォルト値の設定
実践例:
@Servicepublic class EmailService {
// application.propertiesから値を読み込み @Value("${email.smtp.host}") private String smtpHost;
@Value("${email.smtp.port:587}") // デフォルト値587 private int smtpPort;
@Value("${email.from:noreply@example.com}") // デフォルト値 private String fromAddress;
// SpEL(Spring Expression Language)を使用 @Value("#{systemProperties['user.name']}") private String systemUser;
@Value("#{T(java.lang.Math).random() * 100}") private double randomValue;
// リストやマップの注入 @Value("${app.allowed.ips:127.0.0.1,localhost}") private List<String> allowedIps;}
// application.properties// email.smtp.host=smtp.gmail.com// email.smtp.port=587// email.from=admin@example.com// app.allowed.ips=192.168.1.1,192.168.1.2よくある使い方:
- 設定値の外部化
- 環境別の設定(dev、staging、prod)
- デフォルト値の指定
Spring Webアノテーション
Section titled “Spring Webアノテーション”@RequestMapping ★★★★★
Section titled “@RequestMapping ★★★★★”用途: コントローラークラスやメソッドにURLマッピングを定義
ユースケース:
- ベースパスの定義
- 複数のHTTPメソッドに対応
- 共通のパス設定
実践例:
@RestController@RequestMapping("/api/v1/users") // ベースパスpublic class UserController {
// GET /api/v1/users @RequestMapping(method = RequestMethod.GET) public List<User> getUsers() { return userService.findAll(); }
// GET /api/v1/users/{id} @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUser(@PathVariable Long id) { return userService.findById(id); }
// POST /api/v1/users @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public User createUser(@RequestBody User user) { return userService.createUser(user); }}よくある使い方:
- クラスレベルでベースパスを定義
- メソッドレベルで具体的なパスとHTTPメソッドを指定
@GetMapping、@PostMappingなどのショートカットアノテーションを使用
@GetMapping ★★★★★
Section titled “@GetMapping ★★★★★”用途: GETリクエストのマッピング(@RequestMapping(method = RequestMethod.GET)のショートカット)
ユースケース:
- リソースの取得
- 一覧取得
- 詳細取得
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
// GET /api/users @GetMapping public List<UserDTO> getUsers( @RequestParam(required = false) String name, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { return userService.findUsers(name, page, size); }
// GET /api/users/{id} @GetMapping("/{id}") public UserDTO getUser(@PathVariable Long id) { return userService.findById(id); }
// GET /api/users/{id}/orders @GetMapping("/{id}/orders") public List<OrderDTO> getUserOrders(@PathVariable Long id) { return orderService.findByUserId(id); }
// 複数のパスパターン @GetMapping({"/list", "/all"}) public List<UserDTO> getAllUsers() { return userService.findAll(); }}@PostMapping ★★★★★
Section titled “@PostMapping ★★★★★”用途: POSTリクエストのマッピング
ユースケース:
- リソースの作成
- フォーム送信
- ファイルアップロード
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
// POST /api/users @PostMapping public ResponseEntity<UserDTO> createUser( @Valid @RequestBody UserCreateRequest request) { UserDTO user = userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED) .header("Location", "/api/users/" + user.getId()) .body(user); }
// POST /api/users/{id}/activate @PostMapping("/{id}/activate") public ResponseEntity<Void> activateUser(@PathVariable Long id) { userService.activateUser(id); return ResponseEntity.ok().build(); }
// ファイルアップロード @PostMapping(value = "/{id}/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<Void> uploadAvatar( @PathVariable Long id, @RequestParam("file") MultipartFile file) { userService.uploadAvatar(id, file); return ResponseEntity.ok().build(); }}@PutMapping ★★★★☆
Section titled “@PutMapping ★★★★☆”用途: PUTリクエストのマッピング(リソースの完全置換)
ユースケース:
- リソースの更新
- リソースの置換
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
// PUT /api/users/{id} @PutMapping("/{id}") public ResponseEntity<UserDTO> updateUser( @PathVariable Long id, @Valid @RequestBody UserUpdateRequest request) { UserDTO user = userService.updateUser(id, request); return ResponseEntity.ok(user); }}PUTとPATCHの違い:
- PUT: リソースの完全置換
- PATCH: リソースの部分更新(
@PatchMappingを使用)
@DeleteMapping ★★★★☆
Section titled “@DeleteMapping ★★★★☆”用途: DELETEリクエストのマッピング
ユースケース:
- リソースの削除
- 論理削除のトリガー
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
// DELETE /api/users/{id} @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); }
// DELETE /api/users/{id}/soft @DeleteMapping("/{id}/soft") public ResponseEntity<Void> softDeleteUser(@PathVariable Long id) { userService.softDeleteUser(id); return ResponseEntity.ok().build(); }}@PathVariable ★★★★★
Section titled “@PathVariable ★★★★★”用途: URLパスから変数を取得
ユースケース:
- RESTful APIのリソースID取得
- 階層的なリソースの取得
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
// GET /api/users/123 @GetMapping("/{id}") public UserDTO getUser(@PathVariable Long id) { return userService.findById(id); }
// パス変数名を明示的に指定 @GetMapping("/{userId}/orders/{orderId}") public OrderDTO getOrder( @PathVariable("userId") Long userId, @PathVariable("orderId") Long orderId) { return orderService.findByUserIdAndOrderId(userId, orderId); }
// 正規表現でバリデーション @GetMapping("/{id:[0-9]+}") public UserDTO getUserWithValidation(@PathVariable Long id) { return userService.findById(id); }
// オプショナルなパス変数 @GetMapping({"/{id}", ""}) public ResponseEntity<?> getUserOrList(@PathVariable(required = false) Long id) { if (id != null) { return ResponseEntity.ok(userService.findById(id)); } else { return ResponseEntity.ok(userService.findAll()); } }}@RequestParam ★★★★★
Section titled “@RequestParam ★★★★★”用途: クエリパラメータやフォームデータから値を取得
ユースケース:
- 検索条件の取得
- ページネーション
- フィルタリング
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
// GET /api/users?name=John&age=30 @GetMapping public List<UserDTO> searchUsers( @RequestParam(required = false) String name, @RequestParam(required = false) Integer age, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { return userService.searchUsers(name, age, page, size); }
// パラメータ名を明示的に指定 @GetMapping("/search") public List<UserDTO> search( @RequestParam("q") String query, @RequestParam(value = "sort", defaultValue = "name") String sortBy) { return userService.search(query, sortBy); }
// リストや配列の取得 @GetMapping("/filter") public List<UserDTO> filterUsers( @RequestParam("ids") List<Long> ids) { return userService.findByIds(ids); }
// 必須パラメータ @GetMapping("/required") public UserDTO getUserRequired( @RequestParam(required = true) Long id) { return userService.findById(id); }}@RequestBody ★★★★★
Section titled “@RequestBody ★★★★★”用途: HTTPリクエストボディ(JSON、XMLなど)をJavaオブジェクトに変換
ユースケース:
- REST APIでのJSONリクエストの受け取り
- 複雑なオブジェクトの受け取り
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
// POST /api/users // Request Body: {"name": "John", "email": "john@example.com"} @PostMapping public ResponseEntity<UserDTO> createUser( @Valid @RequestBody UserCreateRequest request) { UserDTO user = userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED).body(user); }
// PUT /api/users/{id} @PutMapping("/{id}") public ResponseEntity<UserDTO> updateUser( @PathVariable Long id, @Valid @RequestBody UserUpdateRequest request) { UserDTO user = userService.updateUser(id, request); return ResponseEntity.ok(user); }}
// DTOクラスpublic class UserCreateRequest { @NotBlank(message = "名前は必須です") @Size(max = 100, message = "名前は100文字以内で入力してください") private String name;
@NotBlank(message = "メールアドレスは必須です") @Email(message = "有効なメールアドレスを入力してください") private String email;
// getters and setters}よくある使い方:
@Validと組み合わせてバリデーション- DTOクラスでリクエストデータを受け取る
@JsonIgnoreで不要なフィールドを無視
Spring Data JPAアノテーション
Section titled “Spring Data JPAアノテーション”@Entity ★★★★★
Section titled “@Entity ★★★★★”用途: JPAエンティティクラスであることを示す
ユースケース:
- データベーステーブルに対応するエンティティクラス
- ORMマッピング
実践例:
@Entity@Table(name = "users")public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(name = "user_name", nullable = false, length = 100) private String name;
@Column(unique = true, nullable = false) private String email;
@Column(name = "created_at") @CreationTimestamp private LocalDateTime createdAt;
@Column(name = "updated_at") @UpdateTimestamp private LocalDateTime updatedAt;
// getters and setters}@Table ★★★★☆
Section titled “@Table ★★★★☆”用途: エンティティがマッピングされるテーブル名を指定
ユースケース:
- テーブル名がクラス名と異なる場合
- スキーマ名の指定
- インデックスの定義
実践例:
@Entity@Table( name = "user_accounts", schema = "public", uniqueConstraints = { @UniqueConstraint(columnNames = {"email"}), @UniqueConstraint(columnNames = {"username"}) }, indexes = { @Index(name = "idx_email", columnList = "email"), @Index(name = "idx_created_at", columnList = "created_at") })public class User { // ...}@Id ★★★★★
Section titled “@Id ★★★★★”用途: プライマリキーを指定
実践例:
@Entitypublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
// ...}@GeneratedValue ★★★★★
Section titled “@GeneratedValue ★★★★★”用途: プライマリキーの生成戦略を指定
実践例:
@Entitypublic class User {
// AUTO: データベースに依存(推奨) @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id;
// IDENTITY: データベースの自動増分(MySQL、PostgreSQL) @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
// SEQUENCE: シーケンスを使用(Oracle、PostgreSQL) @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq") @SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1) private Long id;
// TABLE: テーブルを使用(非推奨) @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "user_gen") @TableGenerator(name = "user_gen", table = "id_generator", pkColumnName = "gen_name", valueColumnName = "gen_value") private Long id;}@Column ★★★★☆
Section titled “@Column ★★★★☆”用途: カラムの詳細設定
実践例:
@Entitypublic class User {
@Column( name = "user_name", // カラム名 nullable = false, // NOT NULL制約 unique = true, // ユニーク制約 length = 100, // 文字列の長さ precision = 10, // 数値の精度(小数点を含む) scale = 2, // 小数点以下の桁数 columnDefinition = "VARCHAR(100) DEFAULT 'Unknown'" // カラム定義 ) private String name;
@Column(updatable = false) // 更新不可 private LocalDateTime createdAt;
@Column(insertable = false) // 挿入不可 private LocalDateTime updatedAt;}Spring Validationアノテーション
Section titled “Spring Validationアノテーション”@Valid ★★★★★
Section titled “@Valid ★★★★★”用途: オブジェクトのバリデーションを有効化
ユースケース:
- リクエストデータの検証
- ネストされたオブジェクトの検証
実践例:
@RestController@RequestMapping("/api/users")public class UserController {
@PostMapping public ResponseEntity<UserDTO> createUser( @Valid @RequestBody UserCreateRequest request) { // バリデーションエラーがある場合は自動的に400 Bad Requestが返される UserDTO user = userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED).body(user); }}
// DTOクラスpublic class UserCreateRequest {
@NotBlank(message = "名前は必須です") @Size(min = 1, max = 100, message = "名前は1文字以上100文字以内で入力してください") private String name;
@NotBlank(message = "メールアドレスは必須です") @Email(message = "有効なメールアドレスを入力してください") private String email;
@Min(value = 0, message = "年齢は0以上である必要があります") @Max(value = 150, message = "年齢は150以下である必要があります") private Integer age;
@Valid // ネストされたオブジェクトのバリデーション @NotNull private AddressDTO address;}@NotNull ★★★★★
Section titled “@NotNull ★★★★★”用途: nullでないことを検証
実践例:
public class UserCreateRequest {
@NotNull(message = "IDは必須です") private Long id;
@NotNull(message = "年齢は必須です") @Min(0) @Max(150) private Integer age;}@NotBlank ★★★★★
Section titled “@NotBlank ★★★★★”用途: 文字列がnull、空文字、空白のみでないことを検証
実践例:
public class UserCreateRequest {
@NotBlank(message = "名前は必須です") private String name;
@NotBlank(message = "メールアドレスは必須です") @Email private String email;}@Size ★★★★☆
Section titled “@Size ★★★★☆”用途: 文字列やコレクションのサイズを検証
実践例:
public class UserCreateRequest {
@Size(min = 1, max = 100, message = "名前は1文字以上100文字以内で入力してください") private String name;
@Size(min = 1, max = 10, message = "タグは1個以上10個以内で入力してください") private List<String> tags;}@Email ★★★★☆
Section titled “@Email ★★★★☆”用途: メールアドレスの形式を検証
実践例:
public class UserCreateRequest {
@NotBlank(message = "メールアドレスは必須です") @Email(message = "有効なメールアドレスを入力してください") private String email;}Spring Transactionアノテーション
Section titled “Spring Transactionアノテーション”@Transactional ★★★★★
Section titled “@Transactional ★★★★★”用途: メソッドやクラスにトランザクション管理を適用
ユースケース:
- データベース操作のトランザクション管理
- 複数のデータベース操作の原子性保証
- ロールバック制御
実践例:
@Service@Transactional // クラス全体に適用public class UserService {
@Autowired private UserRepository userRepository;
@Autowired private OrderRepository orderRepository;
// デフォルト設定(読み取り専用でない、REQUIRED伝播) public User createUser(UserCreateRequest request) { User user = new User(); user.setName(request.getName()); return userRepository.save(user); }
// 読み取り専用トランザクション @Transactional(readOnly = true) public User findById(Long id) { return userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException("User not found: " + id)); }
// 新しいトランザクションを開始 @Transactional(propagation = Propagation.REQUIRES_NEW) public void logUserActivity(Long userId, String activity) { // ログ記録処理(独立したトランザクション) }
// 特定の例外でロールバックしない @Transactional(noRollbackFor = {IllegalArgumentException.class}) public void updateUserWithValidation(Long id, UserUpdateRequest request) { // バリデーションエラーでもロールバックしない }
// トランザクションのタイムアウト設定(秒) @Transactional(timeout = 30) public void processLargeData() { // 30秒でタイムアウト }}よくある使い方:
- サービス層のメソッドに適用
readOnly = trueで読み取り専用トランザクション- 例外発生時のロールバック制御
Lombokとは
Section titled “Lombokとは”Lombokは、Javaコードのボイラープレート(定型的なコード)を削減するためのライブラリです。アノテーションを使用して、コンパイル時にgetter、setter、toString、equals、hashCodeなどのメソッドを自動生成します。
Lombokの特徴
Section titled “Lombokの特徴”- ボイラープレートコードの削減: 繰り返し書く必要があるコード(getter/setterなど)を自動生成
- コンパイル時処理: アノテーション処理により、コンパイル時にコードを生成
- IDEサポート: IntelliJ IDEA、Eclipseなどの主要IDEでサポート
- コードの可読性向上: 冗長なコードを減らし、ビジネスロジックに集中できる
Lombokの動作原理
Section titled “Lombokの動作原理”Lombokは、Javaの**アノテーション処理(Annotation Processing)**機能を使用して、コンパイル時にコードを生成します。
1. ソースコードにLombokアノテーションを記述2. コンパイル時にアノテーションプロセッサが実行3. 必要なメソッド(getter/setterなど)を自動生成4. 生成されたコードがコンパイルされるLombokの使用例(Before/After)
Section titled “Lombokの使用例(Before/After)”Lombokなし(従来のコード):
@Entitypublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name; private String email;
// 以下、手動で記述する必要がある(ボイラープレートコード) public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + '}'; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(email, user.email); }
@Override public int hashCode() { return Objects.hash(id, name, email); }}Lombokあり(簡潔なコード):
@Data@Entitypublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name; private String email;
// @Dataアノテーションにより、以下が自動生成される: // - getter/setter // - toString() // - equals()/hashCode() // - コンストラクタ(finalフィールドのみ)}Lombokの主なアノテーション
Section titled “Lombokの主なアノテーション”@Data: getter、setter、toString、equals、hashCodeをまとめて生成@Getter/@Setter: getter/setterメソッドを生成@ToString: toStringメソッドを生成@EqualsAndHashCode: equals/hashCodeメソッドを生成@NoArgsConstructor: 引数なしコンストラクタを生成@AllArgsConstructor: 全フィールドを引数に持つコンストラクタを生成@RequiredArgsConstructor: finalフィールドのみを引数に持つコンストラクタを生成@Builder: Builderパターンを実装@Slf4j: SLF4Jロガーを生成(log変数が使用可能)
Lombokアノテーション
Section titled “Lombokアノテーション”@Data ★★★★★
Section titled “@Data ★★★★★”用途: @Getter、@Setter、@ToString、@EqualsAndHashCode、@RequiredArgsConstructorをまとめて適用
実践例:
@Data@Entitypublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name; private String email;
// 以下のコードが自動生成される: // - getter/setter // - toString() // - equals()/hashCode() // - 全フィールドを引数に持つコンストラクタ(finalフィールドのみ)}⚠️ 重要な注意点: 双方向の関連がある場合の@Exclude
もしあなたが@Data(または@ToStringや@EqualsAndHashCode)を使い、かつ「双方向(親子両方から参照できる)」の関連を作るなら、手動で@Excludeを入れるのは必須の儀式だと思ってください。
問題のある例(無限ループが発生):
// Userエンティティ@Entity@Table(name = "users")@Data // ⚠️ 問題: 双方向の関連がある場合、無限ループが発生する可能性@NoArgsConstructorpublic class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Order> orders = new ArrayList<>(); // ⚠️ OrderからもUserを参照}
// Orderエンティティ@Entity@Table(name = "orders")@Data // ⚠️ 問題: 双方向の関連がある場合、無限ループが発生する可能性@NoArgsConstructorpublic class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@ManyToOne @JoinColumn(name = "user_id") private User user; // ⚠️ UserからもOrderを参照(双方向)
private BigDecimal amount;}
// 問題: toString()やequals()/hashCode()が無限ループを引き起こす// User.toString() → Order.toString() → User.toString() → ...解決方法: @Excludeを使用
// Userエンティティ@Entity@Table(name = "users")@Data@NoArgsConstructor@ToString(exclude = "orders") // ✅ ordersフィールドをtoStringから除外@EqualsAndHashCode(exclude = "orders") // ✅ ordersフィールドをequals/hashCodeから除外public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Order> orders = new ArrayList<>();}
// Orderエンティティ@Entity@Table(name = "orders")@Data@NoArgsConstructor@ToString(exclude = "user") // ✅ userフィールドをtoStringから除外@EqualsAndHashCode(exclude = "user") // ✅ userフィールドをequals/hashCodeから除外public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@ManyToOne @JoinColumn(name = "user_id") private User user;
private BigDecimal amount;}なぜ@Excludeが必須なのか:
- 無限ループの防止:
@ToStringが双方向の関連フィールドを含むと、循環参照によりスタックオーバーフローが発生 - パフォーマンスの問題:
@EqualsAndHashCodeが双方向の関連フィールドを含むと、計算コストが膨大になる - 予期しない動作: 双方向の関連を含む
equals()/hashCode()は、エンティティの管理状態によって予期しない動作を引き起こす可能性がある
ベストプラクティス:
- 双方向の関連フィールドは常に
@Excludeで除外する - IDフィールドのみを使用して
equals()/hashCode()を計算する(推奨)
// より安全な実装例@Entity@Table(name = "users")@Data@NoArgsConstructor@ToString(exclude = "orders")@EqualsAndHashCode(of = "id") // ✅ IDフィールドのみを使用public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Order> orders = new ArrayList<>();}@Getter / @Setter ★★★★★
Section titled “@Getter / @Setter ★★★★★”用途: getter/setterメソッドを自動生成
実践例:
@Entitypublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Getter // getterのみ生成 private Long id;
@Getter @Setter private String name;
@Getter @Setter private String email;}@Builder ★★★★☆
Section titled “@Builder ★★★★☆”用途: ビルダーパターンを自動生成
実践例:
@Builder@Entitypublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name; private String email;
// 使用例 // User user = User.builder() // .name("John") // .email("john@example.com") // .build();}@NoArgsConstructor / @AllArgsConstructor ★★★★☆
Section titled “@NoArgsConstructor / @AllArgsConstructor ★★★★☆”用途: コンストラクタを自動生成
実践例:
@Entity@NoArgsConstructor // 引数なしコンストラクタ@AllArgsConstructor // 全フィールドを引数に持つコンストラクタpublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name; private String email;}@Slf4j ★★★★★
Section titled “@Slf4j ★★★★★”用途: ロガー(log)を自動生成
実践例:
@Service@Slf4jpublic class UserService {
public void createUser(UserCreateRequest request) { log.info("Creating user: {}", request.getName());
try { // ユーザー作成処理 log.debug("User created successfully"); } catch (Exception e) { log.error("Failed to create user", e); throw e; } }}その他の重要なアノテーション
Section titled “その他の重要なアノテーション”@Override ★★★★★
Section titled “@Override ★★★★★”用途: メソッドがスーパークラスまたはインターフェースのメソッドをオーバーライドしていることを明示
なぜ@Overrideを使うのか?
Section titled “なぜ@Overrideを使うのか?”@Overrideアノテーションを使用することで、以下のメリットがあります:
- ✅ コンパイル時の検証: オーバーライドの誤りをコンパイル時に検出できる
- ✅ 可読性の向上: コードを読む人が、このメソッドがオーバーライドであることをすぐに理解できる
- ✅ リファクタリングの安全性: 親クラスのメソッド名を変更した際に、エラーとして検出できる
- ✅ タイポの防止: メソッド名のタイポをコンパイル時に検出できる
// インターフェースの定義public interface UserServiceInterface { User findById(Long id); void save(User user);}
// 実装クラスpublic class UserService implements UserServiceInterface {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) { this.userRepository = userRepository; }
// @Overrideを付けることで、インターフェースのメソッドを実装していることを明示 @Override public User findById(Long id) { return userRepository.findById(id).orElse(null); }
@Override public void save(User user) { userRepository.save(user); }}
// 継承の場合public class Animal { public void makeSound() { System.out.println("Some sound"); }}
public class Dog extends Animal { // @Overrideを付けることで、親クラスのメソッドをオーバーライドしていることを明示 @Override public void makeSound() { System.out.println("Woof! Woof!"); }}ベストプラクティス
Section titled “ベストプラクティス”1. オーバーライドするメソッドには必ず@Overrideを付ける
// ✅ 良い例: @Overrideを付けて明示的にオーバーライドを表明public class UserService implements UserServiceInterface { @Override public User findById(Long id) { return userRepository.findById(id).orElse(null); }}
// ❌ 悪い例: @Overrideがないと、タイポに気づけないpublic class UserService implements UserServiceInterface { // findByIdのタイポだが、コンパイルエラーにならない public User findByIdd(Long id) { // 新しいメソッドとして扱われる return userRepository.findById(id).orElse(null); }}2. オーバーライドの条件を満たしているか確認する
public class Animal { public void makeSound() { } protected String getName() { return ""; }}
public class Dog extends Animal { // ✅ 正しい: メソッド名、引数、戻り値が一致 @Override public void makeSound() { System.out.println("Woof"); }
// ✅ 正しい: アクセス修飾子をpublicに変更可能 @Override public String getName() { return "Dog"; }
// ❌ エラー: 引数が異なる(これはオーバーロード) // @Override // public void makeSound(String volume) { }}3. 親クラスのメソッドを呼び出す場合はsuperを使用
public class Animal { public void display() { System.out.println("Animal"); }}
public class Dog extends Animal { @Override public void display() { super.display(); // 親クラスのメソッドを先に呼び出す System.out.println("Dog"); }}アンチパターン
Section titled “アンチパターン”1. @Overrideを付けずにオーバーライドする
// ❌ アンチパターン: @Overrideがないと、タイポに気づけないpublic class UserService implements UserServiceInterface { // findByIdのタイポだが、コンパイルエラーにならない public User findByIdd(Long id) { // 新しいメソッドとして扱われる return userRepository.findById(id).orElse(null); }}
// ✅ 正しい: @Overrideを付けると、タイポがコンパイルエラーになるpublic class UserService implements UserServiceInterface { @Override public User findById(Long id) { // インターフェースのメソッドを正しく実装 return userRepository.findById(id).orElse(null); }}2. オーバーロードとオーバーライドを混同する
// ❌ アンチパターン: オーバーロードに@Overrideを付けようとするpublic class Animal { public void makeSound() { }}
public class Dog extends Animal { @Override public void makeSound() { } // ✅ これは正しい
// ❌ エラー: 引数が異なるため、これはオーバーロード(オーバーライドではない) // @Override // public void makeSound(String volume) { }}
// ✅ 正しい: オーバーロードには@Overrideを付けないpublic class Dog extends Animal { @Override public void makeSound() { } // オーバーライド
public void makeSound(String volume) { } // オーバーロード(@Override不要)}3. オーバーライドの条件を満たしていないメソッドに@Overrideを付ける
// ❌ アンチパターン: 戻り値の型が異なるpublic class Animal { public String getName() { return ""; }}
public class Dog extends Animal { // ❌ エラー: 戻り値の型が異なるため、オーバーライドではない // @Override // public int getName() { return 0; }}
// ✅ 正しい: 戻り値の型が一致しているpublic class Dog extends Animal { @Override public String getName() { return "Dog"; }}@Deprecated ★★★☆☆
Section titled “@Deprecated ★★★☆☆”用途: 非推奨のメソッドやクラスにマーク
なぜ@Deprecatedを使うのか?
Section titled “なぜ@Deprecatedを使うのか?”@Deprecatedアノテーションを使用することで、以下のメリットがあります:
- ✅ 段階的な移行: 既存のコードを壊さずに、新しいAPIへの移行を促せる
- ✅ 警告の表示: IDEやコンパイラが非推奨の使用に対して警告を表示する
- ✅ ドキュメント化: どのメソッドが非推奨で、代替手段が何かを明確にできる
- ✅ 後方互換性の維持: 既存のコードを壊さずに、APIを改善できる
@Servicepublic class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) { this.userRepository = userRepository; }
// @Deprecated: このメソッドは非推奨。findById()を使用してください @Deprecated public User findUserById(Long id) { // 既存のコードとの互換性のために残しているが、新しいコードでは使用しない return findById(id); }
// 新しい推奨メソッド public User findById(Long id) { return userRepository.findById(id).orElse(null); }
// @Deprecated with since and forRemoval // since: 非推奨になったバージョン // forRemoval: 将来削除予定かどうか @Deprecated(since = "2.0", forRemoval = true) public void oldMethod() { // このメソッドは将来削除される予定 }}
// クラス全体を非推奨にする場合@Deprecatedpublic class OldUserService { // このクラス全体が非推奨}ベストプラクティス
Section titled “ベストプラクティス”1. @Deprecatedと一緒にJavaDocコメントを追加する
@Servicepublic class UserService {
/** * ユーザーをIDで検索します。 * * @deprecated このメソッドは非推奨です。{@link #findById(Long)}を使用してください。 * @param id ユーザーID * @return ユーザー */ @Deprecated public User findUserById(Long id) { return findById(id); }
/** * ユーザーをIDで検索します(推奨)。 * * @param id ユーザーID * @return ユーザー */ public User findById(Long id) { return userRepository.findById(id).orElse(null); }}2. 代替手段を明確に示す
@Servicepublic class UserService {
// ❌ 悪い例: 代替手段が不明確 @Deprecated public User findUserById(Long id) { return findById(id); }
// ✅ 良い例: JavaDocで代替手段を明確に示す /** * ユーザーをIDで検索します。 * * @deprecated このメソッドは非推奨です。代わりに {@link #findById(Long)} を使用してください。 * 移行例: findUserById(id) → findById(id) * * @param id ユーザーID * @return ユーザー */ @Deprecated public User findUserById(Long id) { return findById(id); }
public User findById(Long id) { return userRepository.findById(id).orElse(null); }}3. sinceとforRemovalを適切に使用する
@Servicepublic class UserService {
// 段階的な非推奨化 // since: 非推奨になったバージョン // forRemoval: 将来削除予定かどうか @Deprecated(since = "2.0", forRemoval = true) public void oldMethod() { // このメソッドはバージョン2.0で非推奨になり、将来削除される予定 }
@Deprecated(since = "2.1") public void deprecatedButNotRemoved() { // このメソッドは非推奨だが、削除予定はない }}4. 非推奨メソッドの実装を新しいメソッドに委譲する
@Servicepublic class UserService {
// ✅ 良い例: 非推奨メソッドは新しいメソッドに委譲 @Deprecated public User findUserById(Long id) { return findById(id); // 新しいメソッドに委譲 }
public User findById(Long id) { return userRepository.findById(id).orElse(null); }}アンチパターン
Section titled “アンチパターン”1. @Deprecatedを付けただけで、代替手段を示さない
// ❌ アンチパターン: 代替手段が不明確@Servicepublic class UserService { @Deprecated public User findUserById(Long id) { // 何を使えばいいのかわからない return userRepository.findById(id).orElse(null); }}
// ✅ 正しい: JavaDocで代替手段を明確に示す@Servicepublic class UserService { /** * @deprecated このメソッドは非推奨です。{@link #findById(Long)}を使用してください。 */ @Deprecated public User findUserById(Long id) { return findById(id); }
public User findById(Long id) { return userRepository.findById(id).orElse(null); }}2. 非推奨メソッドを削除せずに放置する
// ❌ アンチパターン: 非推奨メソッドを長期間放置@Servicepublic class UserService { @Deprecated(since = "1.0", forRemoval = true) // 削除予定だが、何年も放置 public User findUserById(Long id) { return findById(id); }}
// ✅ 正しい: 適切なタイミングで削除する// 1. 非推奨をマーク// 2. 十分な移行期間を設ける(例: 1-2バージョン)// 3. 削除予定を通知// 4. 削除3. 非推奨メソッドの実装を変更する
// ❌ アンチパターン: 非推奨メソッドの実装を変更@Servicepublic class UserService { @Deprecated public User findUserById(Long id) { // 非推奨メソッドの実装を変更してしまうと、既存のコードが壊れる可能性がある return userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException()); // 動作が変わってしまう }}
// ✅ 正しい: 非推奨メソッドは新しいメソッドに委譲し、実装を変更しない@Servicepublic class UserService { @Deprecated public User findUserById(Long id) { return findById(id); // 常に新しいメソッドに委譲 }
public User findById(Long id) { return userRepository.findById(id).orElse(null); }}4. 新しいコードで非推奨メソッドを使用する
// ❌ アンチパターン: 新しいコードで非推奨メソッドを使用@Servicepublic class OrderService { private final UserService userService;
public OrderService(UserService userService) { this.userService = userService; }
public Order createOrder(Long userId) { // 警告: 非推奨メソッドを使用している User user = userService.findUserById(userId); // ❌ 非推奨メソッド // ... }}
// ✅ 正しい: 新しいコードでは推奨メソッドを使用@Servicepublic class OrderService { private final UserService userService;
public OrderService(UserService userService) { this.userService = userService; }
public Order createOrder(Long userId) { User user = userService.findById(userId); // ✅ 推奨メソッド // ... }}@Configuration ★★★★☆
Section titled “@Configuration ★★★★☆”用途: 設定クラスであることを示す
なぜ@Configurationを使うのか?
Section titled “なぜ@Configurationを使うのか?”@Configurationアノテーションを使用することで、以下のメリットがあります:
- ✅ Bean定義の集約: 複数のBean定義を1つのクラスに集約できる
- ✅ 設定の一元管理: アプリケーションの設定を1箇所で管理できる
- ✅ 条件付きBean定義:
@ConditionalOnPropertyなどと組み合わせて、条件付きでBeanを定義できる - ✅ 依存関係の明確化: Bean間の依存関係を明確に定義できる
- ✅ テストの容易性: テスト用の設定クラスを簡単に作成できる
@Configurationpublic class AppConfig {
// RestTemplateのBean定義 @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); return restTemplate; }
// ObjectMapperのBean定義 @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); return mapper; }
// 条件付きBean定義 @Bean @ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true") public FeatureService featureService() { return new FeatureService(); }
// 他のBeanに依存するBean定義 @Bean public ApiClient apiClient(RestTemplate restTemplate, ObjectMapper objectMapper) { return new ApiClient(restTemplate, objectMapper); }}ベストプラクティス
Section titled “ベストプラクティス”1. 設定クラスは機能ごとに分離する
// ✅ 良い例: 機能ごとに設定クラスを分離@Configurationpublic class WebConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); }}
@Configurationpublic class DatabaseConfig { @Bean public DataSource dataSource() { // データソースの設定 return new HikariDataSource(); }}
@Configurationpublic class CacheConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); }}
// ❌ 悪い例: すべての設定を1つのクラスに詰め込む@Configurationpublic class AllConfig { @Bean public RestTemplate restTemplate() { } @Bean public DataSource dataSource() { } @Bean public CacheManager cacheManager() { } // ... 100個以上のBean定義}2. @ConfigurationPropertiesと組み合わせて使用する
// 設定プロパティクラス@ConfigurationProperties(prefix = "app")@Datapublic class AppProperties { private String apiUrl; private int timeout; private boolean enabled;}
// 設定クラス@Configuration@EnableConfigurationProperties(AppProperties.class)public class AppConfig {
@Bean public ApiClient apiClient(AppProperties appProperties) { return new ApiClient(appProperties.getApiUrl(), appProperties.getTimeout()); }}3. プロファイル別の設定クラスを作成する
// 開発環境用の設定@Configuration@Profile("dev")public class DevConfig { @Bean public DataSource dataSource() { // 開発環境用のデータソース return new HikariDataSource(); }}
// 本番環境用の設定@Configuration@Profile("prod")public class ProdConfig { @Bean public DataSource dataSource() { // 本番環境用のデータソース return new HikariDataSource(); }}4. @Conditionalアノテーションで条件付きBean定義を行う
@Configurationpublic class ConditionalConfig {
// プロパティが有効な場合のみBeanを作成 @Bean @ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true") public FeatureService featureService() { return new FeatureService(); }
// クラスが存在する場合のみBeanを作成 @Bean @ConditionalOnClass(name = "com.example.ExternalLibrary") public ExternalService externalService() { return new ExternalService(); }}アンチパターン
Section titled “アンチパターン”1. @Configurationを付けずに@Beanメソッドを定義する
// ❌ アンチパターン: @Configurationがないと、Bean間の依存関係が正しく解決されないpublic class AppConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
@Bean public ApiClient apiClient() { // 問題: restTemplate()が毎回新しいインスタンスを作成してしまう return new ApiClient(restTemplate()); // シングルトンにならない }}
// ✅ 正しい: @Configurationを付けると、Bean間の依存関係が正しく解決される@Configurationpublic class AppConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
@Bean public ApiClient apiClient(RestTemplate restTemplate) { // 正しく: restTemplate()はシングルトンとして注入される return new ApiClient(restTemplate); }}2. 設定クラスにビジネスロジックを含める
// ❌ アンチパターン: 設定クラスにビジネスロジックを含める@Configurationpublic class AppConfig { @Bean public UserService userService() { UserService service = new UserService(); // 問題: ビジネスロジックを設定クラスに書くべきではない service.processUsers(); // ビジネスロジック return service; }}
// ✅ 正しい: 設定クラスはBean定義のみを行う@Configurationpublic class AppConfig { @Bean public UserService userService(UserRepository userRepository) { // Bean定義のみ return new UserService(userRepository); }}
// ビジネスロジックはサービスクラスに@Servicepublic class UserService { private final UserRepository userRepository;
public UserService(UserRepository userRepository) { this.userRepository = userRepository; }
public void processUsers() { // ビジネスロジック }}3. すべての設定を1つのクラスに詰め込む
// ❌ アンチパターン: すべての設定を1つのクラスに詰め込む@Configurationpublic class AllConfig { @Bean public RestTemplate restTemplate() { } @Bean public DataSource dataSource() { } @Bean public CacheManager cacheManager() { } @Bean public EmailService emailService() { } @Bean public PaymentService paymentService() { } // ... 100個以上のBean定義 // 問題: 保守性が低く、テストが困難}
// ✅ 正しい: 機能ごとに設定クラスを分離@Configurationpublic class WebConfig { @Bean public RestTemplate restTemplate() { }}
@Configurationpublic class DatabaseConfig { @Bean public DataSource dataSource() { }}4. @Beanメソッド内でnewを直接使用する(依存性注入を使わない)
// ❌ アンチパターン: 依存関係を直接newで解決@Configurationpublic class AppConfig { @Bean public UserService userService() { // 問題: 依存関係を直接newで解決している UserRepository repository = new UserRepositoryImpl(); return new UserService(repository); }}
// ✅ 正しい: 依存性注入を使用@Configurationpublic class AppConfig { @Bean public UserService userService(UserRepository userRepository) { // 依存関係をSpringに委譲 return new UserService(userRepository); }}@Bean ★★★★☆
Section titled “@Bean ★★★★☆”用途: メソッドの戻り値をSpring Beanとして登録
なぜ@Beanを使うのか?
Section titled “なぜ@Beanを使うのか?”@Beanアノテーションを使用することで、以下のメリットがあります:
- ✅ サードパーティライブラリの統合: Springが管理していないクラスをBeanとして登録できる
- ✅ カスタム設定: Beanの作成時にカスタム設定を適用できる
- ✅ 条件付きBean定義: 条件に応じてBeanを作成できる
- ✅ 複数の実装: 同じ型の複数のBeanを定義できる(名前で区別)
- ✅ ライフサイクル管理: Springのライフサイクル(初期化、破棄)を利用できる
@Configurationpublic class AppConfig {
// 基本的なBean定義 @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); return restTemplate; }
// 名前を指定したBean定義 @Bean(name = "customRestTemplate") public RestTemplate customRestTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setConnectTimeout(Duration.ofSeconds(10)); restTemplate.setReadTimeout(Duration.ofSeconds(30)); return restTemplate; }
// @Primaryで優先Beanを指定 @Bean @Primary public RestTemplate primaryRestTemplate() { return new RestTemplate(); }
// 他のBeanに依存するBean定義 @Bean public ApiClient apiClient(RestTemplate restTemplate) { return new ApiClient(restTemplate); }
// 初期化・破棄メソッドを指定 @Bean(initMethod = "init", destroyMethod = "cleanup") public ResourceManager resourceManager() { return new ResourceManager(); }
// 条件付きBean定義 @Bean @ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true") public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); }}ベストプラクティス
Section titled “ベストプラクティス”1. Bean名は明確で一貫性のあるものにする
@Configurationpublic class AppConfig {
// ✅ 良い例: 明確なBean名 @Bean(name = "httpRestTemplate") public RestTemplate httpRestTemplate() { return new RestTemplate(); }
@Bean(name = "httpsRestTemplate") public RestTemplate httpsRestTemplate() { return new RestTemplate(); }
// ❌ 悪い例: 曖昧なBean名 @Bean(name = "restTemplate1") public RestTemplate restTemplate1() { return new RestTemplate(); }}2. 依存関係はメソッド引数で注入する
@Configurationpublic class AppConfig {
// ✅ 良い例: 依存関係をメソッド引数で注入 @Bean public ApiClient apiClient(RestTemplate restTemplate, ObjectMapper objectMapper) { return new ApiClient(restTemplate, objectMapper); }
// ❌ 悪い例: 依存関係を直接newで解決 @Bean public ApiClient apiClient() { RestTemplate restTemplate = new RestTemplate(); // 問題: シングルトンにならない return new ApiClient(restTemplate); }}3. @PrimaryでデフォルトのBeanを指定する
@Configurationpublic class AppConfig {
// デフォルトのRestTemplate(@Primary) @Bean @Primary public RestTemplate restTemplate() { return new RestTemplate(); }
// カスタムのRestTemplate(名前で指定して使用) @Bean(name = "customRestTemplate") public RestTemplate customRestTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setConnectTimeout(Duration.ofSeconds(30)); return restTemplate; }}
// 使用例@Servicepublic class UserService { // @Primaryが付いたBeanが自動注入される private final RestTemplate restTemplate;
public UserService(RestTemplate restTemplate) { this.restTemplate = restTemplate; // デフォルトのRestTemplateが注入される }}
@Servicepublic class ApiService { // 名前を指定してカスタムのRestTemplateを注入 @Autowired @Qualifier("customRestTemplate") private RestTemplate restTemplate;}4. 条件付きBean定義を使用する
@Configurationpublic class AppConfig {
// プロパティが有効な場合のみBeanを作成 @Bean @ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true") public FeatureService featureService() { return new FeatureService(); }
// クラスが存在する場合のみBeanを作成 @Bean @ConditionalOnClass(name = "com.example.ExternalLibrary") public ExternalService externalService() { return new ExternalService(); }
// プロファイルが有効な場合のみBeanを作成 @Bean @Profile("dev") public DevService devService() { return new DevService(); }}5. 初期化・破棄メソッドを適切に使用する
public class ResourceManager { public void init() { // 初期化処理 System.out.println("ResourceManager initialized"); }
public void cleanup() { // クリーンアップ処理 System.out.println("ResourceManager cleaned up"); }}
@Configurationpublic class AppConfig { @Bean(initMethod = "init", destroyMethod = "cleanup") public ResourceManager resourceManager() { return new ResourceManager(); }}アンチパターン
Section titled “アンチパターン”1. @Configurationクラス内で@Beanメソッドを直接呼び出す
@Configurationpublic class AppConfig {
@Bean public RestTemplate restTemplate() { return new RestTemplate(); }
// ❌ アンチパターン: @Beanメソッドを直接呼び出す @Bean public ApiClient apiClient() { // 問題: restTemplate()が毎回新しいインスタンスを作成してしまう return new ApiClient(restTemplate()); // シングルトンにならない }}
// ✅ 正しい: 依存関係をメソッド引数で注入@Configurationpublic class AppConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
@Bean public ApiClient apiClient(RestTemplate restTemplate) { // 正しく: restTemplateはシングルトンとして注入される return new ApiClient(restTemplate); }}2. 同じ型のBeanを複数定義する際に名前を付けない
@Configurationpublic class AppConfig {
// ❌ アンチパターン: 同じ型のBeanを複数定義する際に名前を付けない @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
@Bean public RestTemplate customRestTemplate() { // 問題: どちらが注入されるか不明確 return new RestTemplate(); }}
// ✅ 正しい: 名前を明確に指定する@Configurationpublic class AppConfig { @Bean(name = "defaultRestTemplate") @Primary public RestTemplate defaultRestTemplate() { return new RestTemplate(); }
@Bean(name = "customRestTemplate") public RestTemplate customRestTemplate() { return new RestTemplate(); }}3. Bean定義メソッド内でビジネスロジックを実行する
@Configurationpublic class AppConfig {
// ❌ アンチパターン: Bean定義メソッド内でビジネスロジックを実行 @Bean public UserService userService() { UserService service = new UserService(); // 問題: Bean定義時にビジネスロジックを実行すべきではない service.processAllUsers(); // ビジネスロジック return service; }}
// ✅ 正しい: Bean定義はシンプルに保つ@Configurationpublic class AppConfig { @Bean public UserService userService(UserRepository userRepository) { // Bean定義のみ return new UserService(userRepository); }}
// ビジネスロジックはサービスクラスに@Servicepublic class UserService { public void processAllUsers() { // ビジネスロジック }}4. 状態を持つBeanを@Beanメソッドで定義する
@Configurationpublic class AppConfig {
// ❌ アンチパターン: 状態を持つBeanを@Beanメソッドで定義 private int counter = 0; // 問題: 状態を持つ
@Bean public CounterService counterService() { counter++; // 問題: 状態を変更している return new CounterService(counter); }}
// ✅ 正しい: 状態を持たないBean定義@Configurationpublic class AppConfig { @Bean public CounterService counterService() { // 状態を持たない return new CounterService(); }}5. @Beanメソッドをstaticにする(必要な場合を除く)
@Configurationpublic class AppConfig {
// ❌ アンチパターン: 通常はstaticにしない @Bean public static RestTemplate restTemplate() { // 問題: 他のBeanに依存できない return new RestTemplate(); }}
// ✅ 正しい: 通常はインスタンスメソッドとして定義@Configurationpublic class AppConfig { @Bean public RestTemplate restTemplate() { // 他のBeanに依存できる return new RestTemplate(); }
@Bean public ApiClient apiClient(RestTemplate restTemplate) { // restTemplateに依存できる return new ApiClient(restTemplate); }}アノテーションの組み合わせパターン
Section titled “アノテーションの組み合わせパターン”よくある組み合わせ
Section titled “よくある組み合わせ”// REST APIコントローラー@RestController@RequestMapping("/api/users")@Validated@Slf4jpublic class UserController {
private final UserService userService;
public UserController(UserService userService) { this.userService = userService; }
@GetMapping("/{id}") public ResponseEntity<UserDTO> getUser(@PathVariable Long id) { log.info("Getting user: {}", id); UserDTO user = userService.findById(id); return ResponseEntity.ok(user); }}
// サービス層@Service@Transactional@Slf4jpublic class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) { this.userRepository = userRepository; }
@Transactional(readOnly = true) public UserDTO findById(Long id) { log.debug("Finding user by id: {}", id); User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException("User not found: " + id)); return UserDTO.fromEntity(user); }}
// エンティティ@Entity@Table(name = "users")@Data@NoArgsConstructor@AllArgsConstructor@Builderpublic class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(nullable = false, length = 100) private String name;
@Column(unique = true, nullable = false) private String email;
@Column(name = "created_at", updatable = false) @CreationTimestamp private LocalDateTime createdAt;}AOP(Aspect Oriented Programming)の専門用語
Section titled “AOP(Aspect Oriented Programming)の専門用語”**AOP(Aspect Oriented Programming、アスペクト指向プログラミング)**とは、本編のコードとは関係ない「お決まりの処理」(例:ロギング、トランザクション管理、セキュリティチェックなど)を、外側に追い出して自動で合体させる技術です。
RailsでいうBeforeActionに相当する機能です。
AOPの動作原理
Section titled “AOPの動作原理”AOPは、動的プロキシを使用して、実行時にコードを生成します。具体的には、コンストラクタなどのボイラープレートテンプレートを作成し、実行時にメソッド呼び出しをインターセプトして、追加処理を実行します。
1. アスペクト(共通処理)を定義2. ポイントカット(適用箇所)を指定3. 実行時に動的プロキシが発動4. メソッド呼び出しをインターセプト5. アドバイス(追加処理)を実行6. 元のメソッドを実行AOPの専門用語
Section titled “AOPの専門用語”アスペクト (Aspect)
Section titled “アスペクト (Aspect)”アスペクトは、まとまった共通処理を定義する単位です。
例: 盛り付けルール
@Aspect@Componentpublic class LoggingAspect {
// このアスペクトは、ロギングという共通処理を定義 // 例: すべてのサービスメソッドの実行前後にログを出力する}アドバイス (Advice)
Section titled “アドバイス (Advice)”アドバイスは、アスペクトの中の具体的な中身、つまり「いつ」「何を」実行するかを定義します。
例: パセリを乗せる
@Aspect@Componentpublic class LoggingAspect {
// @Before: メソッド実行前にログを出力 @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { log.info("Before method: {}", joinPoint.getSignature()); // 例: パセリを乗せる(ログを出力) }
// @After: メソッド実行後にログを出力 @After("execution(* com.example.service.*.*(..))") public void logAfter(JoinPoint joinPoint) { log.info("After method: {}", joinPoint.getSignature()); }}ジョインポイント (Joinpoint)
Section titled “ジョインポイント (Joinpoint)”ジョインポイントは、アドバイスを差し込めるタイミングです。メソッドの実行前、実行後、例外発生時など、プログラムの実行における特定のポイントを指します。
例: 料理が完成した瞬間
// ジョインポイントの例:// - メソッド実行前 (Before)// - メソッド実行後 (After)// - メソッド実行後(正常終了時) (AfterReturning)// - 例外発生時 (AfterThrowing)// - メソッド実行の前後 (Around)
@Aspect@Componentpublic class LoggingAspect {
// メソッド実行前(ジョインポイント: Before) @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { // メソッド実行前に実行される }
// メソッド実行後(ジョインポイント: After) @After("execution(* com.example.service.*.*(..))") public void logAfter(JoinPoint joinPoint) { // メソッド実行後に実行される(正常終了・例外発生の両方) }}ポイントカット (Pointcut)
Section titled “ポイントカット (Pointcut)”ポイントカットは、どこにアドバイスを差し込むかのルールを定義します。どのクラス、どのメソッドにアドバイスを適用するかを指定します。
例: カレーの時だけ!
@Aspect@Componentpublic class TransactionAspect {
// ポイントカット: サービス層のメソッドのみに適用 @Pointcut("execution(* com.example.service.*.*(..))") public void serviceMethods() { // ポイントカットの定義(例: カレーの時だけ!) }
// アドバイス: ポイントカットにマッチするメソッドに適用 @Around("serviceMethods()") public Object aroundServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable { // 例: カレーの時だけトランザクションを開始 return joinPoint.proceed(); }}AOPの実践例(Spring Boot)
Section titled “AOPの実践例(Spring Boot)”// アスペクトの定義@Aspect@Component@Slf4jpublic class LoggingAspect {
// ポイントカット: サービス層のすべてのメソッド @Pointcut("execution(* com.example.service.*.*(..))") public void serviceMethods() { }
// アドバイス: メソッド実行前にログを出力(ジョインポイント: Before) @Before("serviceMethods()") public void logBefore(JoinPoint joinPoint) { log.info("Before method: {} with args: {}", joinPoint.getSignature(), Arrays.toString(joinPoint.getArgs())); }
// アドバイス: メソッド実行後にログを出力(ジョインポイント: AfterReturning) @AfterReturning(pointcut = "serviceMethods()", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { log.info("After method: {} returned: {}", joinPoint.getSignature(), result); }
// アドバイス: 例外発生時にログを出力(ジョインポイント: AfterThrowing) @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex") public void logAfterThrowing(JoinPoint joinPoint, Exception ex) { log.error("Exception in method: {}", joinPoint.getSignature(), ex); }
// アドバイス: メソッド実行の前後で処理(ジョインポイント: Around) @Around("serviceMethods()") public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); // 元のメソッドを実行 long duration = System.currentTimeMillis() - start; log.info("Method: {} took {}ms", joinPoint.getSignature(), duration); return result; } catch (Exception e) { long duration = System.currentTimeMillis() - start; log.error("Method: {} failed after {}ms", joinPoint.getSignature(), duration, e); throw e; } }}
// 使用例: サービスクラス(何も変更不要)@Servicepublic class UserService {
public User findById(Long id) { // このメソッドは自動的にログ出力される(AOPにより) return userRepository.findById(id).orElseThrow(); }}動的プロキシの仕組み
Section titled “動的プロキシの仕組み”Spring AOPは、実行時に動的プロキシを生成して、メソッド呼び出しをインターセプトします。
具体例:
UserServiceのメソッドを呼び出す- Springが動的プロキシ(
UserServiceのプロキシ)を生成 - プロキシがメソッド呼び出しをインターセプト
- アドバイス(ログ出力など)を実行
- 元の
UserServiceのメソッドを実行 - アドバイス(ログ出力など)を実行
- 結果を返す
AOPと@Transactional
Section titled “AOPと@Transactional”Spring Bootの@Transactionalアノテーションも、AOPの仕組みを使用して実装されています。
@Servicepublic class UserService {
// @Transactionalアノテーション // → AOPにより、このメソッド実行時に自動的にトランザクションを開始・終了 @Transactional public User createUser(UserCreateRequest request) { User user = new User(); user.setName(request.getName()); return userRepository.save(user); // メソッド終了時に自動的にコミット(例外発生時はロールバック) }}動作の流れ:
createUserメソッドを呼び出す- AOPがメソッド呼び出しをインターセプト
- トランザクションを開始(アドバイス: Before)
- 元のメソッドを実行
- トランザクションをコミット(アドバイス: AfterReturning)またはロールバック(アドバイス: AfterThrowing)
頻出度別アノテーション一覧
Section titled “頻出度別アノテーション一覧”★★★★★(必須レベル):
@Service、@Repository、@Controller、@RestController@Autowired、@Component@GetMapping、@PostMapping、@PathVariable、@RequestParam、@RequestBody@Entity、@Id、@GeneratedValue@Valid、@NotNull、@NotBlank@Transactional@Data、@Slf4j(Lombok)
★★★★☆(推奨レベル):
@Value、@RequestMapping@PutMapping、@DeleteMapping@Table、@Column@Size、@Email@Getter、@Setter、@Builder(Lombok)@Configuration、@Bean
★★★☆☆(中頻度):
@PatchMapping@Deprecated@NoArgsConstructor、@AllArgsConstructor(Lombok)
ベストプラクティス
Section titled “ベストプラクティス”- 適切なアノテーションの選択: より具体的なアノテーションを優先(
@Service>@Component) - コンストラクタインジェクション:
@Autowiredのフィールドインジェクションは避ける - バリデーション:
@Validと組み合わせてリクエストデータを検証 - トランザクション管理: サービス層で
@Transactionalを使用 - ロギング: Lombokの
@Slf4jを使用してロガーを自動生成
これらのアノテーションを適切に使用することで、保守性の高いSpring Bootアプリケーションを構築できます。