Spring Bootで日常的に使うアノテーションを、起動設定・DI・Web・データアクセス・入力チェック・AOP・セキュリティ・テストの役割別に一覧表で並べました。Spring Boot 4ではテスト系を中心にimport先が変わったため、表にはパッケージ名も載せています。アノテーションが動く仕組みと、@Autowired・@Bean・@Qualifier のように取り違えやすいものの使い分けも扱います。表とコードは、2026年10月22日に公開されたSpring Boot 4.1.2(Spring Framework 7.0.10)で確かめました。
まとめ:Springアノテーション一覧の要点
- アノテーションはJava標準の注釈で、それだけでは何もしません。Springなどの処理系が読み取り、Bean登録・リクエスト処理・入力検証などに反映します。Lombokのようにコンパイル時に処理されるものもあります。
- 最低限の核は
@SpringBootApplication、@RestControllerと@GetMapping系、@Service・@Repository、@Transactionalの4グループです。 - パッケージ名で提供元が分かります。
@DataはLombok、@EntityはJakarta Persistence、@NotBlankはJakarta Validationで、Springのものではありません。 - 自分で書いたクラスは
@Component系、ライブラリのクラスは@Beanで登録します。同じ型が複数あるときは@Primaryか@Qualifierで1つに絞ります。 @Transactionalは既定のプロキシ方式では自己呼び出しやprivateメソッドに適用されません。また、チェック例外では既定でロールバックしません。- Spring Boot 4では
@MockBeanが削除され、@WebMvcTestなどはパッケージが移動しました。javax.annotationの@PostConstructはエラーも出さずに無視されます。
Springアノテーションとは:起動時にSpringが読み取る仕組み
アノテーションは、クラス・メソッド・フィールド・引数に @ で付ける注釈です。処理を書くものではなく、「このクラスをBeanとして登録する」といった指示を、それを解釈するフレームワークに伝えます。XMLの設定ファイルに書いていた内容を、コードのすぐ横に書けるようにしたものと考えると分かりやすくなります。
アノテーションの正体:@interfaceとRUNTIME保持・メタアノテーション
Springのアノテーションも、Javaの @interface で定義された普通のアノテーションです。@Service のクラスファイルを javap で見ると、@Retention(RetentionPolicy.RUNTIME) と @Component が付いています。RUNTIME保持なので実行時にリフレクションで読め、@Component を持つので @Service のクラスは @Component の一種として登録されます。この「アノテーションに付けたアノテーション」をメタアノテーションと呼びます。@RestController が @Controller と @ResponseBody の合成になっているのも同じ仕組みです。
同じ仕組みで、自分で合成アノテーションを作れます。次のコードは @Service と @Transactional を1つにまとめたものです。
// @Service と @Transactional を1つにまとめた合成アノテーション
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
@Transactional
public @interface TransactionalService {
}
起動時の処理順序:スキャンからプロキシ生成まで
- コンポーネントスキャン:
@SpringBootApplicationに含まれる@ComponentScanが、mainクラスのパッケージ配下から@Component系の付いたクラスを集めます。 - Bean定義への変換:見つけたクラスを、スコープや遅延初期化の有無を持つBeanDefinition(設計図)に変えて登録します。インスタンスはまだ作りません。
- 生成と注入:設計図からインスタンスを作り、コンストラクタ引数などに型が合うBeanを注入します。
@PostConstructのメソッドはこの後に呼ばれます。 - プロキシ生成:
@Transactional・@Async・@Cacheableなどが付いたBeanは、元のオブジェクトを包むプロキシに置き換えて登録します。
スキャンの既定範囲はmainクラスのパッケージとその配下だけです。mainクラスが com.example.app にあると、com.example.common に置いた @Service は見つかりません。「付けたのにBeanが無い」と言われたら、まずパッケージの位置を疑います。範囲を広げるときは @SpringBootApplication(scanBasePackages = "com.example") と書きます。コンテナとBeanの関係そのものはIoCコンテナとは?Spring IoCコンテナ・Beanの仕組みで扱っています。
パッケージ名で見分ける提供元:@DataはLombok、@EntityはJPA
「Spring Bootのアノテーション」として紹介されるものには、Spring以外が定義したものが多く混じっています。付けたのに効かないときは、まずimport文のパッケージを見ると、足りない依存の見当がつきます。ただし定義元と処理する側が同じとは限りません。jakarta.validation の制約はHibernate Validatorが検査し、org.aspectj の記法はSpring AOPが解釈します。
| パッケージの先頭 | 提供元 | 代表例 | 必要な依存(Spring Boot 4.1) |
|---|---|---|---|
| org.springframework.boot | Spring Boot | @SpringBootApplication | spring-boot-starter |
| org.springframework | Spring Framework | @Service, @GetMapping | 各starterに同梱 |
| org.springframework.data | Spring Data | @Query, @Modifying | spring-boot-starter-data-jpa |
| org.springframework.security | Spring Security | @PreAuthorize | spring-boot-starter-security |
| jakarta.persistence | Jakarta Persistence | @Entity, @JoinColumn | spring-boot-starter-data-jpa |
| jakarta.validation | Jakarta Validation | @NotBlank, @Valid | spring-boot-starter-validation |
| jakarta.annotation | Jakarta Annotations | @PostConstruct | spring-boot-starterに同梱 |
| org.aspectj | AspectJ | @Aspect, @Around | spring-boot-starter-aspectj |
| lombok | Lombok | @Data, @RequiredArgsConstructor | lombok(版はBootのBOMが管理) |
| java.lang | Java標準 | @Override, @Deprecated | 不要 |
Lombokの @Data はgetter・setter・equals・hashCode・toStringと必須引数のコンストラクタをコンパイル時に生成するもので、Springは関与しません。Spring BootのBOMはLombokの版(4.1.2では1.18.48)を管理しているだけで、依存は自分で足します。JPAのエンティティに @Data を付けるのは避けます。生成されるequals・hashCodeは既定でstaticとtransientを除くフィールドを使い、JPAの関連フィールドも含むため、遅延ロードの関連に触れて余計なSQLが走ったり、保存前後でhashCodeが変わったりします。エンティティには @Getter と @Setter を個別に付けます。
同じ名前の別物にも注意します。@Transactional にはJakarta版の jakarta.transaction.Transactional もあり、IDEの補完で取り込むと rollbackFor や readOnly が使えません。
起動と設定のアノテーション一覧
アプリケーションの入口と、設定値・Bean定義・登録条件を扱うアノテーションです。@ConditionalOn で始まる条件系はSpring Boot固有で、自動構成の仕組みそのものです。
| アノテーション | 付ける場所 | 役割 |
|---|---|---|
| @SpringBootApplication | mainクラス | 下の3つをまとめた合成アノテーション |
| @SpringBootConfiguration | mainクラス | 設定クラスであることの宣言 |
| @EnableAutoConfiguration | mainクラス | 依存に応じた自動構成を有効化 |
| @ComponentScan | mainクラス | 同じパッケージ以下のBeanを検出 |
| @Configuration | クラス | @Beanメソッドを持つ設定クラス |
| @Bean | メソッド | 戻り値をBeanとして登録 |
| @Value | フィールド・引数 | 設定値を1つずつ注入 |
| @ConfigurationProperties | クラス・record | 接頭辞単位で設定値を束ねる |
| @ConfigurationPropertiesScan | mainクラス | 上記を付けた型を自動登録 |
| @Profile | クラス・メソッド | プロファイル有効時だけ登録 |
| @ConditionalOnProperty | クラス・メソッド | 設定値が条件に合うときだけ登録 |
| @ConditionalOnClass | クラス・メソッド | 指定クラスがあるときだけ登録 |
| @ConditionalOnMissingBean | クラス・メソッド | 指定条件を満たすBeanが無い場合に登録 |
| @Import | 設定クラス | 別の設定クラスを取り込む |
同じ接頭辞に属する設定値をまとめて扱うなら、@Value を並べるより @ConfigurationProperties でrecordにまとめるほうが見通しがよくなります。@ConfigurationProperties を付けただけではBeanにならないので、mainクラスに @ConfigurationPropertiesScan を付けます。自分でBeanを定義すると自動構成が引っ込むのは、自動構成側が @ConditionalOnMissingBean で書かれているからです。
@SpringBootApplication
@ConfigurationPropertiesScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
// application.yml の app.greeting / app.max-items を束ねて受け取る
@ConfigurationProperties("app")
public record AppProperties(String greeting, int maxItems) {
}
Bean登録とDIのアノテーション一覧
クラスをDIコンテナに登録するステレオタイプと、注入先を決めるアノテーションです。
| アノテーション | パッケージ | 役割 |
|---|---|---|
| @Component | org.springframework.stereotype | 汎用のBean登録 |
| @Service | org.springframework.stereotype | 業務ロジック層のBean |
| @Repository | org.springframework.stereotype | データアクセス層のBean+例外変換 |
| @Controller | org.springframework.stereotype | 画面を返すWebコントローラー |
| @RestController | org.springframework.web.bind.annotation | @Controller+@ResponseBody |
| @Autowired | org.springframework.beans.factory.annotation | 型が一致するBeanを注入 |
| @Qualifier | org.springframework.beans.factory.annotation | 同じ型の候補を修飾子で絞り込む |
| @Primary | org.springframework.context.annotation | 同じ型が複数あるときの既定 |
| @Lazy | org.springframework.context.annotation | 初回利用時まで生成を遅らせる |
| @Scope | org.springframework.context.annotation | singleton・prototypeなどのスコープを指定 |
| @DependsOn | org.springframework.context.annotation | 指定Beanより後に生成する |
| @PostConstruct | jakarta.annotation | 注入完了後に1回呼ぶ初期化 |
| @PreDestroy | jakarta.annotation | コンテナ終了時の後始末 |
@Autowiredとは:読み方と、省略できる条件
@Autowired(読み方はオートワイヤード)は、コンテナに登録されたBeanから型が一致するものを探して注入するアノテーションです。フィールド・セッター・コンストラクタに付けられますが、注入はコンストラクタで受け取るのが基本です。依存をfinalにでき、テストでは new で差し替えられます。Spring Frameworkのリファレンスにあるとおり、コンストラクタが1つしかないクラスでは @Autowired を書かなくても注入されます。フィールド注入・セッター注入との比較はSpringのDI(依存性注入)の仕組みと注入方法にまとめています。
@Service
public class BookService {
private final BookRepository repository;
// コンストラクタが1つだけなので @Autowired は不要
public BookService(BookRepository repository) {
this.repository = repository;
}
@Transactional(readOnly = true)
public Book find(Long id) {
return repository.findById(id).orElseThrow(() -> new BookNotFoundException(id));
}
}
ステレオタイプ4種のうち、登録以外の機能を持つのは2つだけです。@Repository はJPAプロバイダの例外をSpringの DataAccessException へ変換する対象になり、@Controller はリクエストマッピングの走査対象になります。@Service を @Component に替えても動作は変わりません。それでも @Service を使うのは、層の境界がコードから読め、AOPのポイントカットで層単位に指定できるからです。サービス層の役割はサービスクラスとは?Java・Springでの役割と書き方で扱っています。
@Componentと@Beanの使い分け:ソースの編集可否と生成処理
判断の基準は1つです。自分で書いたクラスは @Component 系をクラスに付け、ソースを編集できないライブラリのクラスは @Configuration クラスの @Bean メソッドで登録します。RestClient や ObjectMapper には @Component を付けられないからです。生成時に引数を組み立てたり条件で分岐したりしたいときも @Bean を使います。次の例で RestClient.Builder の自動構成を使うには、Spring Boot 4の依存に spring-boot-starter-restclient を追加します。
@Configuration
public class HttpClientConfig {
// RestClient はライブラリのクラスなので @Component を付けられない。@Bean で登録する
@Bean
public RestClient inventoryClient(RestClient.Builder builder) {
return builder.baseUrl("https://inventory.example.com").build();
}
@Bean
public AuditLog auditLog() {
return new AuditLog();
}
@Bean
public ReportService reportService() {
// proxyBeanMethods = true(既定)なので、auditLog() を呼んでもコンテナ内の同じBeanが返る
return new ReportService(auditLog());
}
}
@Configuration クラスには既定でCGLIBのプロキシが掛かります(proxyBeanMethods = true)。7.0.10で試すと、上の reportService() の中で呼んだ auditLog() はコンテナ内と同じインスタンスを返し、@Configuration(proxyBeanMethods = false) にすると呼ぶたびに別のインスタンスになりました。Spring Boot自身の自動構成は起動を速くするためにfalseを多用していますが、アプリ側で @Bean メソッドを呼び合うなら既定のままにします(@Configurationの説明)。
@Primaryと@Qualifierの使い方:同じ型のBeanが複数あるとき
同じ型のBeanが2つ以上あり、1つの注入先に対して @Primary や名前の一致でも候補を1つに絞れないと、起動に失敗します。実際に試すと NoUniqueBeanDefinitionException(expected single matching bean but found 2)で止まりました。解決は2通りで、選ぶ基準は「既定を決められるか」です。
@Primary
@Component("emailNotifier")
public class EmailNotifier implements Notifier {
public void send(String message) { }
}
@Component("slackNotifier")
public class SlackNotifier implements Notifier {
public void send(String message) { }
}
@Service
public class AlertService {
private final Notifier defaultNotifier;
private final Notifier slackNotifier;
// 1つ目は @Primary の EmailNotifier、2つ目は名前で SlackNotifier を指定
public AlertService(Notifier defaultNotifier,
@Qualifier("slackNotifier") Notifier slackNotifier) {
this.defaultNotifier = defaultNotifier;
this.slackNotifier = slackNotifier;
}
}
このコードを起動すると、1つ目の引数には @Primary の EmailNotifier、@Qualifier("slackNotifier") の引数には SlackNotifier が入りました。大半の呼び出し元が同じ実装を使うなら @Primary を付け、例外だけを @Qualifier で指定します。用途ごとに実装が分かれていて既定が無いなら、@Primary は付けずに全員が @Qualifier を書きます。この場合に @Primary を付けると、@Qualifier を書き忘れた新しい呼び出し元にも黙って既定の実装が入り、起動時エラーで気づく機会を失います。
Bean名と同じ引数名でも解決できますが、引数名をクラスファイルに残す -parameters オプションが前提で、リファクタリングで引数名を変えると壊れます。明示の @Qualifier のほうが確実です。
初期化と後始末:@PostConstruct・@PreDestroyと呼ばれる順序
@PostConstruct は依存の注入が終わった直後に1回呼ばれます。@PreDestroy の呼ばれる時点はスコープで変わり、singletonはコンテナの終了時、requestとsessionはそのスコープの終了時で、prototypeはSpringが破棄を管理しないため呼ばれません。どちらも jakarta.annotation パッケージで、spring-boot-starterが依存に含めています。
@Component
public class CacheWarmer {
// 依存の注入が終わった直後に1回だけ呼ばれる
@PostConstruct
void warmUp() {
}
// このBeanは既定のsingletonなので、コンテナの終了時に呼ばれる
@PreDestroy
void shutdown() {
}
}
Awareインタフェースや @Bean の initMethod と併用したときの順序は、1つのBeanにすべてを実装して7.0.10で起動・終了すると次のとおりでした。
- コンストラクタ →
@Autowiredのセッター(依存の注入) BeanNameAware#setBeanName→BeanFactoryAware#setBeanFactory@PostConstruct→InitializingBean#afterPropertiesSet→@Bean(initMethod)- 終了時:
@PreDestroy→DisposableBean#destroy→@Bean(destroyMethod)
Bean名や BeanFactory を @PostConstruct の中で使っても、Awareのセッターは先に呼ばれているので値は入っています。初期化処理は @PostConstruct 1つに寄せ、Springのインタフェースを実装するのはライブラリのクラスに限るほうが、コードがSpringに縛られません。
古い記事のコードにある javax.annotation.PostConstruct は、Spring Framework 7.0のリリースノートでサポートが削除されました。javax版のjarをクラスパスに置いたまま7.0.10で起動すると、コンパイルも起動も通り、jakarta版のメソッドだけが呼ばれ、javax版はエラーも警告も出さずに無視されました。@javax.inject.Inject と @javax.annotation.Resource も同じ扱いです。
Web(Spring MVC)のアノテーション一覧
HTTPリクエストを受けてレスポンスを返すコントローラーで使うアノテーションです。すべて org.springframework.web.bind.annotation パッケージにあります。
| アノテーション | 付ける場所 | 役割 |
|---|---|---|
| @RequestMapping | クラス・メソッド | URLとHTTPメソッドの対応づけ |
| @GetMapping ほか | メソッド | GET/POST/PUT/PATCH/DELETE別の短縮形 |
| @PathVariable | 引数 | URLの {id} 部分を受け取る |
| @RequestParam | 引数 | クエリ文字列・フォーム値を受け取る |
| @RequestBody | 引数 | JSON本文をオブジェクトへ変換 |
| @ModelAttribute | 引数・メソッド | フォーム値をオブジェクトへ束ねる |
| @RequestHeader | 引数 | リクエストヘッダーを受け取る |
| @ResponseBody | クラス・メソッド | 戻り値をそのまま本文にする |
| @ResponseStatus | メソッド・コントローラークラス・例外クラス | 返すステータスコードを固定 |
| @ExceptionHandler | メソッド | 指定した例外を処理 |
| @ControllerAdvice | クラス | 全コントローラー共通の例外処理など |
| @RestControllerAdvice | クラス | @ControllerAdvice+@ResponseBody |
| @CrossOrigin | クラス・メソッド | CORSを許可 |
@ControllerAdvice と @RestControllerAdvice の差は、@Controller と @RestController の差と同じです。JSONを返すAPIでは @RestControllerAdvice を使い、@ExceptionHandler の戻り値をそのまま本文にします。属性を書かなければ全コントローラーが対象で、basePackages・assignableTypes・annotations で対象を絞れます。同じ例外を扱うアドバイスが複数あるときは、@Order の値が小さいほうが選ばれます。4.1.2で2つの @RestControllerAdvice に @Order(0) と @Order(1) を付け、値を入れ替えても、毎回0のほうが応答しました。コントローラー自身に @ExceptionHandler があれば、アドバイスより先にそちらが使われます(Controller Adviceの説明)。
Spring Framework 7から、@RequestMapping と @GetMapping 系に version 属性が加わりました。@GetMapping(path = "/books", version = "1.1") のようにAPIのバージョンごとにハンドラを分けられます。ただし属性を書くだけでは働かず、Spring Boot 4.1では spring.mvc.apiversion.use.header などのプロパティか WebMvcConfigurer#configureApiVersioning で、バージョンをどこから読むかを決めておく必要があります。Spring MVC自体の構成と依存名の変更はSpring MVCとspring-webmvcの役割で扱っています。
@RestController
@RequestMapping("/api/books")
public class BookController {
private final BookService service;
public BookController(BookService service) {
this.service = service;
}
@GetMapping("/{id}")
public String show(@PathVariable("id") Long id) {
return service.find(id).getTitle();
}
// 入力検証とステータス指定の例。DBへの保存処理は省略しています
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public BookRequest create(@Valid @RequestBody BookRequest request) {
return request;
}
}
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
public ProblemDetail handleNotFound(BookNotFoundException e) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
}
}
データアクセス(JPA・Spring Data JPA)のアノテーション一覧
データアクセスのアノテーションは提供元が2つに分かれます。エンティティ側はJakarta Persistenceの仕様、リポジトリ側はSpring Dataです。
エンティティとリレーション:@Entity・@OneToMany・@JoinColumn
| アノテーション | 役割 | fetchの既定 |
|---|---|---|
| @Entity | テーブルに対応するクラス | – |
| @Table | テーブル名を指定 | – |
| @Id | 主キー | – |
| @GeneratedValue | 主キーの採番方法 | – |
| @Column | 列名・NOT NULL・長さ | – |
| @ManyToOne | 多対一(子から親) | EAGER |
| @OneToOne | 一対一 | EAGER |
| @OneToMany | 一対多(親から子) | LAZY |
| @ManyToMany | 多対多 | LAZY |
| @JoinColumn | 外部キー列を指定 | – |
表のfetch既定値は、jakarta.persistence 3.2.0の定義から読んだものです。@ManyToOne は既定でEAGER(関連を即時に読む)なので、一覧取得で親を毎回読み込みN+1問題の原因になります。fetch = FetchType.LAZY を明示しておくのが無難です。LAZYにしても一覧の各行で関連を参照すれば行ごとに追加のクエリが走るので、関連まで表示するならJPQLの join fetch でまとめて読み込みます。
@JoinColumn(name = "author_id") は、外部キーを持つ側(多対一の「多」側)に付けます。反対側の @OneToMany には mappedBy で相手のフィールド名を書き、外部キーの管理を片側に寄せます。エンティティクラスの設計そのものはエンティティクラスとは|JPAとEF Coreの書き方を参照してください。
@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String title;
private int stock;
// books.author_id 列で Author エンティティの主キーを参照する
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
public String getTitle() {
return title;
}
}
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 外部キーは Book 側が持つ。mappedBy には相手側のフィールド名を書く
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books = new ArrayList<>();
}
リポジトリとクエリ:@Query・@Modifying・@Param
| アノテーション | パッケージ | 役割 |
|---|---|---|
| @Query | org.springframework.data.jpa.repository | JPQLやネイティブSQLを直接書く |
| @Modifying | org.springframework.data.jpa.repository | @QueryがUPDATE/DELETEであることの宣言 |
| @Param | org.springframework.data.repository.query | 名前付きパラメータを引数と対応づけ |
| @Transactional | org.springframework.transaction.annotation | メソッドをトランザクション内で実行 |
@Param は同じ名前のアノテーションがMyBatisにもあり(org.apache.ibatis.annotations.Param)、Mapperインタフェースではこちらを使います。Spring Data JPAのリポジトリにMyBatis版をimportしても名前付きパラメータとは対応づきません。
@Query で更新系のJPQLを書くときは @Modifying が必須で、付け忘れると実行時に失敗します。一括UPDATEは永続化コンテキストを経由せずにDBを書き換えるため、実行前に読み込んだエンティティは古い値のまま残ります。clearAutomatically = true(既定false)は実行後に永続化コンテキストを空にするだけで、手元のオブジェクトは最新になりません。最新の値が要るなら取得し直します。clearで未反映の変更が捨てられないよう、flushAutomatically = true(既定false)も併せて付けます。
更新系のクエリはトランザクションの中で動かす必要があります。サービス層のメソッドに @Transactional を付けるのが基本ですが、下の例のようにリポジトリのメソッド側に付けても構いません。トランザクション終了後もWeb層でEntityManagerを開いておく既定設定の問題はspring.jpa.open-in-viewとは?OSIVの既定値trueで扱っています。
public interface BookRepository extends JpaRepository<Book, Long> {
@Query("select b from Book b where b.author.name = :name")
List<Book> findByAuthorName(@Param("name") String name);
// UPDATE/DELETE の JPQL には @Modifying が要る
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Transactional
@Query("update Book b set b.stock = b.stock - 1 where b.id = :id and b.stock > 0")
int decrementStock(@Param("id") Long id);
}
入力チェック(Jakarta Validation)のアノテーション一覧
値の条件を表す制約アノテーションは jakarta.validation.constraints パッケージ、検査対象を指定する @Valid は jakarta.validation パッケージにあります。Spring Bootでは spring-boot-starter-validation を足すと、APIと実装のHibernate Validator(4.1.2のBOMでは9.1.5)がそろいます。検証機能は実装がクラスパスにあることを条件に有効になるので、Validation APIだけを入れた構成では、コンパイルは通っても検査されません。
| アノテーション | 検査内容 | nullの扱い |
|---|---|---|
| @NotNull | nullでない | NG |
| @NotEmpty | nullでも長さ0でもない | NG |
| @NotBlank | 空白以外の文字を1つ以上含む | NG |
| @Size(min, max) | 文字数・要素数の範囲 | OK(素通し) |
| @Min / @Max | 数値の下限・上限 | OK(素通し) |
| @Positive / @PositiveOrZero | 正の数/0以上 | OK(素通し) |
| メールアドレス形式 | OK(素通し) | |
| @Pattern(regexp) | 正規表現に一致 | OK(素通し) |
| @Past / @Future | 過去/未来の日時 | OK(素通し) |
| @Valid | 引数・入れ子のオブジェクトを検査対象にする | – |
null・空文字・空白の扱い:@NotNull・@NotEmpty・@NotBlankの違い
取り違えやすいのが、nullと空文字の扱いです。Hibernate Validator 9.1.5で次の3つの入力を検査しました。
| 入力 | @NotNull | @NotEmpty | @NotBlank | @Size(min = 1) |
|---|---|---|---|---|
| null | NG | NG | NG | OK |
| “”(空文字) | OK | NG | NG | NG |
| ” “(空白1つ) | OK | OK | NG | OK |
必須入力の文字列には @NotBlank を選びます。@Size(min = 1) だけでは、nullも空白だけの入力も通ってしまいます。@Min(1) を付けた Integer も同じで、nullは合格扱いです。必須にしたい項目は @NotNull を重ねます。@NotBlank は文字列専用なので、リストや配列が空でないことを求めるなら @NotEmpty を使います(空の List は違反になりました)。
@Size・@Pattern・@Emailの判定:文字数の数え方と全体一致
同じ9.1.5で、境界になりそうな値を流した結果です。
- @Sizeの文字数:Javaの
String#length()と同じくUTF-16の単位で数えます。「𠮷」のようなサロゲートペアの文字は1文字で2と数えられ、@Size(max = 2)に「𠮷a」(見た目は2文字)を渡すと違反になりました。人名や住所の上限をぎりぎりに設定すると、画面の文字数カウントと食い違います。 - @Patternは全体一致:
regexp = "\\d{3}"は「123」だけを通し、「1234」も「a123」も違反になりました。部分一致ではないので^と$は要りません。nullは素通りしますが、空文字は検査されて違反になります。 - @Emailは緩い:「a@b」のようにドメインにドットが無い値や、「あ@example.jp」も通りました。空文字も合格です。「a@」と「[email protected]」は違反でした。必須にするなら
@NotBlankと重ね、ドメインの形まで縛りたいなら@Email(regexp = ...)で条件を足します。
下の BookRequest では、isbnがnullなら @Pattern を素通りし、”978-4″ なら @Pattern 違反、title=” “・stock=-1 なら @NotBlank と @Min の2件が違反になりました。
public record BookRequest(
@NotBlank @Size(max = 100) String title,
@Min(0) int stock,
@Pattern(regexp = "\\d{13}") String isbn) {
}
@Validと@Validatedの使い分け
検査を起動する側には jakarta.validation.Valid の @Valid と、Springの org.springframework.validation.annotation.Validated の @Validated があります。コントローラーの @RequestBody 引数には @Valid を付けます。@Validated はクラスに付けてサービスのメソッド引数を検査したいときや、グループ指定で検査項目を切り替えたいときに使います。検証をどの層に置くかという設計はバリデーションとは?入力検証の実装パターンと設計で扱っています。
AOP・トランザクション・非同期・リトライのアノテーション一覧
メソッドの前後に処理を差し込むアノテーションです。@Transactional もこの仕組み(プロキシ)で動きます。Spring Bootが自動で有効にするものと、@Enable で始まるアノテーションを自分で付けないと働かないものがあります。
| アノテーション | 役割 | 有効化に必要なもの |
|---|---|---|
| @Aspect | 横断処理をまとめるクラス | starter-aspectj(Bootが自動有効化) |
| @Before / @After | 対象メソッドの前/後 | 同上 |
| @AfterReturning / @AfterThrowing | 正常終了時/例外時 | 同上 |
| @Around | 前後を1つのメソッドで囲む | 同上 |
| @Pointcut | 対象メソッドの条件に名前を付ける | 同上 |
| @Order | 複数のアスペクトの適用順 | 不要 |
| @Transactional | トランザクション境界 | TransactionManager(Bootが自動構成) |
| @Async | 別スレッドで実行 | @EnableAsync |
| @Scheduled | 定期実行 | @EnableScheduling |
| @Cacheable / @CacheEvict | 結果のキャッシュ | @EnableCaching |
| @Retryable | 失敗時の再試行 | @EnableResilientMethods |
| @ConcurrencyLimit | 同時実行数の制限 | @EnableResilientMethods |
| @EventListener | アプリ内イベントの受信 | 不要 |
AOPのアノテーションは org.aspectj.lang.annotation パッケージで、AspectJの記法をSpring AOPが解釈します。Spring Boot 4ではstarterの名前が spring-boot-starter-aop から spring-boot-starter-aspectj に変わり、4.1.2でも旧名のstarterは公開されていません。アスペクトが複数あるときは org.springframework.core.annotation.Order の @Order で順番を決め、値の小さいほうが外側(先に開始し、後に終わる)になります。AOPの仕組みはSpring AOPの仕組みと使い方で詳しく扱っています。
@Aspect
@Order(1)
@Component
public class TimingAspect {
@Around("execution(* com.example.demo..*Service.*(..))")
public Object measure(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed();
} finally {
System.out.printf("%s: %d ms%n", pjp.getSignature().toShortString(),
(System.nanoTime() - start) / 1_000_000);
}
}
}
@Transactionalが適用されない条件とロールバック規則
@Transactional を付けたのにトランザクションが始まらない、またはロールバックされないときは、次の3点を確認します。1と2はプロキシが適用されないケース、3は適用されたうえで既定の規則によりコミットされるケースです。
- 同じクラス内からの呼び出し:プロキシは外から呼ばれたときだけ割り込めます。下の
importAll()からsaveOne()を呼ぶのはthisへの直接呼び出しなので、トランザクションは始まりません。境界は別のBeanへ切り出します。 - privateメソッド・finalメソッド:CGLIBのプロキシが上書きできないので効きません。Spring 6.0以降はクラスベースのプロキシならprotectedとパッケージプライベートのメソッドも対象です。
- チェック例外:既定でロールバックするのは
RuntimeExceptionとErrorだけで、IOExceptionなどのチェック例外ではコミットされます(ロールバック規則)。@Transactional(rollbackFor = Exception.class)と書くか、アプリ全体を@EnableTransactionManagement(rollbackOn = RollbackOn.ALL_EXCEPTIONS)に切り替えます。
@Service
public class OrderImportService {
public void importAll(List<Long> ids) {
for (Long id : ids) {
saveOne(id); // this.saveOne() と同じ。プロキシを通らないのでトランザクションは始まらない
}
}
@Transactional
public void saveOne(Long id) {
}
}
どの層に付けるかと分離レベルの考え方はトランザクションとは?分離レベルの違い・ACID・commit/rollbackで扱っています。
Spring Framework 7で本体入りした@Retryable・@ConcurrencyLimit
@Retryable と @ConcurrencyLimit はSpring Framework 7で本体(org.springframework.resilience.annotation)に入り、設定クラスに @EnableResilientMethods を付けると有効になります。7.0.10のjarでは、@Retryable の既定は maxRetries = 3、delay = 1000(ミリ秒)でした。リファレンスのとおり総試行回数は「初回1回+maxRetries」で、外部ライブラリSpring Retryの maxAttempts(初回を含む回数)とは数え方が違います。値をそのまま移すと試行が1回ずれます。既存のSpring Retryから移るかどうかはSpring Retryの依存関係と@Retryable/@Recover実装で判断材料をまとめています。
セキュリティ(Spring Security)のアノテーション一覧
メソッド単位で認可を掛けるアノテーションです。Spring Boot 4.1.2が管理するSpring Security 7.1.2のjarで確認しました。
| アノテーション | パッケージ(org.springframework.security以下) | 有効化の条件 |
|---|---|---|
| @EnableWebSecurity | config.annotation.web.configuration | Bootでは自動構成 |
| @EnableMethodSecurity | config.annotation.method.configuration | 自分で付ける |
| @PreAuthorize | access.prepost | prePostEnabled(既定true) |
| @PostAuthorize | access.prepost | prePostEnabled(既定true) |
| @Secured | access.annotation | securedEnabled(既定false) |
| @RolesAllowed | (jakarta.annotation.security) | jsr250Enabled(既定false) |
@EnableMethodSecurity の属性の既定値は、jarの定義では prePostEnabled だけがtrueで、securedEnabled と jsr250Enabled はfalseでした。@Secured や @RolesAllowed を付けても、属性をtrueにしていなければ検査されずに素通りします。新しく書くなら、SpELで条件を書ける @PreAuthorize に寄せるのが簡単です。設定方法はMethod Securityのリファレンス、Spring Security全体の仕組みはSpring Securityとは?認証・認可の仕組みと基本設定にあります。
// prePostEnabled は既定で true。@Secured を使うなら securedEnabled = true を足す
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}
@Service
public class DocumentService {
// 呼び出す前に検査する
@PreAuthorize("hasRole('ADMIN')")
public void delete(Long id) {
}
// 戻り値を見てから検査する。returnObject が戻り値
@PostAuthorize("returnObject.owner() == authentication.name")
public Document load(Long id) {
return new Document(id, "alice");
}
}
テストのアノテーション一覧
Spring Bootのテストは、アプリ全体を起動するか、特定の層だけを起動するか(スライステスト)をアノテーションで選びます。4.1.2でのパッケージを載せています。
| アノテーション | 起動する範囲 | パッケージ(4.1.2) |
|---|---|---|
| @SpringBootTest | アプリ全体 | org.springframework.boot.test.context |
| @WebMvcTest | コントローラー層だけ | org.springframework.boot.webmvc.test.autoconfigure |
| @DataJpaTest | JPAリポジトリ層だけ | org.springframework.boot.data.jpa.test.autoconfigure |
| @AutoConfigureMockMvc | @SpringBootTestにMockMvcを追加 | org.springframework.boot.webmvc.test.autoconfigure |
| @TestConfiguration | テスト専用のBean定義 | org.springframework.boot.test.context |
| @MockitoBean | BeanをMockitoのモックに差し替え | org.springframework.test.context.bean.override.mockito |
| @MockitoSpyBean | Beanをスパイで包む | org.springframework.test.context.bean.override.mockito |
| @WithMockUser | 認証済みユーザーとして実行 | org.springframework.security.test.context.support |
スライステスト用のアノテーションは、4系では層ごとに別のモジュールへ分かれました。@WebMvcTest を使うには spring-boot-starter-webmvc-test、@DataJpaTest には spring-boot-starter-data-jpa-test を依存に加えます。
@WebMvcTest(BookController.class)
class BookControllerTest {
@Autowired
MockMvc mockMvc;
@MockitoBean
BookService bookService;
@Test
void returns404WhenMissing() throws Exception {
when(bookService.find(99L)).thenThrow(new BookNotFoundException(99L));
mockMvc.perform(get("/api/books/99")).andExpect(status().isNotFound());
}
}
Spring Securityを使うアプリでは、テストの依存に spring-boot-starter-security-test を足すかどうかで結果が変わります。4.1.2では @WebMvcTest にセキュリティの自動構成を差し込む設定がこのモジュール側にあり、足さないとセキュリティ無しでコントローラーだけが動きます。足すと、上のテストは404ではなく401で失敗しました。テストメソッドに @WithMockUser を付けると、認証済みとして扱われて404の検証が通りました。
この記事のコード例は、import文と補助クラス(BookNotFoundException・AuditLog・ReportService・Notifier・Document)を省いた抜粋です。省いた部分を補って1つのMavenプロジェクト(Spring Boot 4.1.2・JDK 25)にまとめ、すべてコンパイルが通ること、上の @WebMvcTest のテストが通ることを確かめました。DIの挙動(@Primary・@Qualifier・proxyBeanMethods・初期化の順序・javax版 @PostConstruct)と入力チェックも実際に動かした結果です。
Spring Boot 4で削除・移動したアノテーションと置き換え先
検索で見つかるSpring Bootのアノテーション解説は、多くが2系・3系の時代に書かれています。アノテーション名は同じでも、4系ではimport文が変わったものがあり、古いコードを貼るとコンパイルの段階で止まります。3.5.16と4.1.2のjarを突き合わせた結果が次の表です。
| アノテーション | 3.5までのパッケージ | 4.1での扱い |
|---|---|---|
| @MockBean | org.springframework.boot.test.mock.mockito | 削除。@MockitoBeanへ |
| @SpyBean | org.springframework.boot.test.mock.mockito | 削除。@MockitoSpyBeanへ |
| @WebMvcTest | org.springframework.boot.test.autoconfigure.web.servlet | org.springframework.boot.webmvc.test.autoconfigureへ移動 |
| @AutoConfigureMockMvc | org.springframework.boot.test.autoconfigure.web.servlet | 同上 |
| @DataJpaTest | org.springframework.boot.test.autoconfigure.orm.jpa | org.springframework.boot.data.jpa.test.autoconfigureへ移動 |
| @EntityScan | org.springframework.boot.autoconfigure.domain | org.springframework.boot.persistence.autoconfigureへ移動 |
| @NonNull / @Nullable | org.springframework.lang | 非推奨。org.jspecify.annotationsへ |
| @PostConstruct ほか | javax.annotation / javax.inject | サポート削除。jakarta版へ |
たとえば3系の書き方のまま import org.springframework.boot.test.mock.mockito.MockBean; を残してコンパイルすると、package org.springframework.boot.test.mock.mockito does not exist で失敗します。IDEの自動importで直る移動と違い、@MockBean は名前ごと変わるので、プロジェクト全体を検索して置き換えます。javax版は逆にコンパイルが通ってしまうので、import javax. で検索して洗い出します。
4系で増えたものは @RequestMapping の version 属性、本体入りした @Retryable・@ConcurrencyLimit、HTTPインタフェースのクライアントをまとめて登録する @ImportHttpServices です。3系のプロジェクトに先回りして書くとコンパイルできません。古いSpring Bootのアプリを4系へ移す作業ごと相談したい場合は、フルスクラッチ開発の一環で既存システムの改修も受けています。アノテーション以外を含むSpring Boot 4全体の変更点はSpring Boot 4とは?最新バージョン4.1の変更点、各版のサポート期限はSpring Bootバージョン一覧とサポート期限を参照してください。
よくある質問
@Autowiredは付けなくても動きますか?
コンストラクタが1つだけのクラスなら、付けなくても注入されます。コンストラクタが複数あるときは使うほうに @Autowired を付けます。付けないと引数なしのコンストラクタが選ばれ、それも無ければ起動時にエラーになります。
@DataはSpringのアノテーションですか?
いいえ、Lombokのアノテーションです。@RequiredArgsConstructor も同じくLombokで、未初期化のfinalフィールドと、Lombokの @NonNull を付けた未初期化フィールドを引数に取るコンストラクタを生成します。生成されるコンストラクタが1つだけなら、前の質問のとおり @Autowired なしで注入されます。依存に org.projectlombok:lombok を足さないと使えません。
@Componentと@Serviceはどう違いますか?
動作の差はありません。@Service は @Component をメタアノテーションに持つ別名で、どちらもBeanとして登録されます。@Service を選ぶのは、層の役割をコードで示し、AOPで層単位に対象を選べるようにするためです。
@Controllerと@RestControllerの違いは何ですか?
@RestController は @Controller と @ResponseBody を合わせたものです。@Controller のメソッドが文字列を返すとテンプレート(ThymeleafのHTMLなど)の名前として扱われ、@RestController では戻り値がそのままJSONなどの本文になります。REST APIには @RestController、画面を返すアプリには @Controller を使います。
@Transactionalはどの層に付けるべきですか?
複数のリポジトリ呼び出しを1つの単位にまとめるサービス層のメソッドに付けるのが基本です。コントローラーに付けると外部API呼び出しまでトランザクションに含まれ得て、リポジトリだけに付けると複数の更新が別々にコミットされます。