Spring Boot チートシート

プロジェクト作成・初期設定

基本的なプロジェクトのセットアップ手順

Spring Initializr (https://start.spring.io/) を利用するのが一般的です。

主要な依存関係

目的依存関係 (Maven/Gradle)主な用途
Webアプリケーション開発spring-boot-starter-webRESTful API、MVC Webアプリケーション構築 (Tomcat組込)
データアクセス (JPA)spring-boot-starter-data-jpaHibernateを利用したORM、リポジトリパターン
ビューテンプレート (Thymeleaf)spring-boot-starter-thymeleafサーバーサイドHTMLレンダリング
セキュリティspring-boot-starter-security認証・認可機能の実装
設定プロパティ処理spring-boot-configuration-processor@ConfigurationProperties のメタデータ生成 (IDE補完用)
テストspring-boot-starter-testJUnit 5, Spring Test, Mockito, AssertJ などを含むテスト基盤
運用監視spring-boot-starter-actuatorヘルスチェック、メトリクス、環境情報などのエンドポイント提供
リラクティブWebspring-boot-starter-webflux非同期・ノンブロッキングWebアプリケーション (Netty組込)
バリデーションspring-boot-starter-validationBean Validation (JSR-380) のサポート

設定ファイル

src/main/resources 配下に配置します。

application.properties
# サーバーポート設定
server.port=8080
# アプリケーション名
spring.application.name=my-spring-boot-app
# データベース接続設定 (例: H2)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# JPA/Hibernate設定
spring.jpa.hibernate.ddl-auto=update # create, create-drop, validate, none など
spring.jpa.show-sql=true # 実行されるSQLをログに出力
# Thymeleaf設定
spring.thymeleaf.cache=false # 開発中はキャッシュ無効化 
application.yml

Properties形式の代わりにYAML形式も利用可能です。

server: port: 8080
spring: application: name: my-spring-boot-app datasource: url: jdbc:h2:mem:testdb driverClassName: org.h2.Driver username: sa password: password jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: update show-sql: true thymeleaf: cache: false 

プロファイル設定

環境ごとに設定を切り替えるためにプロファイルを使用します。

  • ファイル名で指定: application-{profile}.properties or application-{profile}.yml (例: application-dev.yml, application-prod.properties)
  • YAMLドキュメント区切り: --- で区切り、spring.config.activate.on-profile: {profile} を指定
例: application.yml でのプロファイル指定
# デフォルト設定 (共通)
server: port: 8080
spring: application: name: my-app
---
# 開発環境 (dev) プロファイル
spring: config: activate: on-profile: dev datasource: url: jdbc:h2:mem:devdb jpa: show-sql: true
---
# 本番環境 (prod) プロファイル
spring: config: activate: on-profile: prod datasource: url: jdbc:mysql://prod-db.example.com:3306/mydb username: prod_user password: ${DB_PASSWORD} # 環境変数や外部設定から取得推奨 jpa: hibernate: ddl-auto: validate # 本番では validate や none を推奨 

有効化するプロファイルは、環境変数 SPRING_PROFILES_ACTIVE や JVM引数 -Dspring.profiles.active=dev で指定します。

Web開発 (Spring MVC)

REST API や Web アプリケーションの構築

コントローラー

アノテーション説明主な用途
@RestController@Controller@ResponseBody の組み合わせ。各メソッドの戻り値が直接レスポンスボディとなる。REST API の開発
@ControllerSpring MVC のコントローラーを示す。メソッドの戻り値は通常、ビュー名を表す。従来のMVC Webアプリケーション (Thymeleafなどビューと連携)

リクエストマッピング

アノテーションHTTPメソッド説明
@RequestMapping指定可能 (デフォルトは全メソッド)クラスレベル、メソッドレベルでパスとHTTPメソッドをマッピング@RequestMapping("/api/users")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@GetMappingGETGETリクエスト用のショートカット@GetMapping("/items")
@PostMappingPOSTPOSTリクエスト用のショートカット@PostMapping("/items")
@PutMappingPUTPUTリクエスト用のショートカット@PutMapping("/items/{id}")
@DeleteMappingDELETEDELETEリクエスト用のショートカット@DeleteMapping("/items/{id}")
@PatchMappingPATCHPATCHリクエスト用のショートカット@PatchMapping("/items/{id}")

リクエストデータの取得

アノテーション取得対象説明
@PathVariableパス変数URLパスの一部をメソッド引数にバインド@GetMapping("/users/{userId}")
public User getUser(@PathVariable Long userId)
@RequestParamクエリパラメータ
フォームデータ
リクエストパラメータをメソッド引数にバインド。required, defaultValue 属性あり。@GetMapping("/search")
public List<Item> search(@RequestParam String query, @RequestParam(required = false, defaultValue = "1") int page)
@RequestBodyリクエストボディリクエストボディ (JSON, XMLなど) をオブジェクトにデシリアライズしてバインド。通常、POSTやPUTで使用。@PostMapping("/users")
public User createUser(@RequestBody @Valid User newUser)
@RequestHeaderリクエストヘッダー特定のリクエストヘッダーの値をメソッド引数にバインド。public void process(@RequestHeader("User-Agent") String userAgent)
@CookieValueクッキー特定のクッキーの値をメソッド引数にバインド。public void handle(@CookieValue("sessionId") String sessionId)
(アノテーションなし)フォームデータ
クエリパラメータ
リクエストパラメータ名をフィールド名とするオブジェクトにバインド (JavaBean)。public String search(SearchForm form)
// SearchForm のフィールドにバインド

レスポンス

戻り値の型 / アノテーション説明用途
ResponseEntity<T>レスポンスボディ、ステータスコード、ヘッダーを完全に制御可能。詳細なレスポンス制御が必要な場合 (REST API)
T (任意のオブジェクト) + @ResponseBody (@RestController 内では不要)戻り値オブジェクトが自動的にJSONなどにシリアライズされ、レスポンスボディとなる。ステータスコードは通常 200 OK。REST API のシンプルなレスポンス
String (@Controller 内)ビュー名 (例: Thymeleaf テンプレート名) を返す。MVC Web アプリケーションの画面遷移
voidレスポンスを直接操作する場合 (HttpServletResponse を引数に取るなど)。または、ステータスコードのみ返す場合 (@ResponseStatus と併用)。特定の状況下でのレスポンス制御
ModelAndViewモデルデータとビュー名をまとめて返す。従来の Spring MVC スタイル
例: ResponseEntity の使用
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) { Optional<User> userOpt = userService.findById(id); if (userOpt.isPresent()) { return ResponseEntity.ok(userOpt.get()); // 200 OK と User オブジェクト } else { return ResponseEntity.notFound().build(); // 404 Not Found }
}
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody @Valid User user) { User createdUser = userService.createUser(user); URI location = URI.create("/users/" + createdUser.getId()); return ResponseEntity.created(location).body(createdUser); // 201 Created と Location ヘッダー
} 

エラーハンドリング

  • @ExceptionHandler: コントローラー内で特定例外を処理するメソッドに付与。
  • @ControllerAdvice / @RestControllerAdvice: グローバルな例外ハンドリングクラスに付与。@ExceptionHandler と組み合わせて使用。
  • ResponseStatusException: 例外発生時に特定のHTTPステータスコードとメッセージを返すための例外クラス。
例: グローバル例外ハンドラー
@RestControllerAdvice
public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) { ErrorResponse error = new ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage()); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) { // エラー詳細を取得して整形するなど String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", message); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) { // 予期せぬエラー ErrorResponse error = new ErrorResponse("INTERNAL_SERVER_ERROR", "An unexpected error occurred."); return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); } // ErrorResponse DTO (例) record ErrorResponse(String code, String message) {}
}
// カスタム例外 (例)
public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); }
} 

CORS (Cross-Origin Resource Sharing) 設定

異なるオリジンからのリクエストを許可するための設定。

  • @CrossOrigin アノテーション: コントローラーやメソッド単位で設定。
  • グローバル設定: WebMvcConfigurer を実装したクラスで設定。
例: グローバルCORS設定
@Configuration
public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") // 対象パスパターン .allowedOrigins("http://localhost:3000", "https://example.com") //許可するオリジン .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") //許可するHTTPメソッド .allowedHeaders("*") //許可するヘッダー .allowCredentials(true) //クッキー許可 .maxAge(3600); // pre-flightリクエストのキャッシュ時間(秒) }
} 

データアクセス (Spring Data JPA)

データベース操作の簡略化

エンティティ定義

JPA の標準アノテーションを使用して、Java クラスをデータベーステーブルにマッピングします。

例: User エンティティ
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity // このクラスがJPAエンティティであることを示す
@Table(name = "users") // 対応するテーブル名を指定 (省略するとクラス名が使われる)
public class User { @Id // プライマリキーを示す @GeneratedValue(strategy = GenerationType.IDENTITY) // ID自動生成戦略 (DB依存) private Long id; @Column(nullable = false, unique = true, length = 100) // カラム制約 private String username; @Column(nullable = false) private String password; // 注意: パスワードはハッシュ化して保存すること @Column(unique = true) private String email; @Column(name = "created_at", updatable = false) // カラム名指定、更新不可 private LocalDateTime createdAt; private LocalDateTime updatedAt; // コンストラクタ、Getter/Setter、equals/hashCode など (Lombok を使うと簡潔) @PrePersist // 永続化前に実行 protected void onCreate() { createdAt = updatedAt = LocalDateTime.now(); } @PreUpdate // 更新前に実行 protected void onUpdate() { updatedAt = LocalDateTime.now(); }
} 

リポジトリインターフェース

JpaRepository またはその親インターフェース (CrudRepository, PagingAndSortingRepository) を継承して作成します。

例: UserRepository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.List;
@Repository // データアクセス層のコンポーネントであることを示す (省略可能)
public interface UserRepository extends JpaRepository<User, Long> { // <エンティティクラス, 主キーの型> // メソッド名からクエリを自動生成 (Query Methods) Optional<User> findByUsername(String username); List<User> findByEmailContainingIgnoreCase(String email); List<User> findByCreatedAtAfter(LocalDateTime dateTime); // exists クエリ boolean existsByEmail(String email); // delete クエリ long deleteByUsername(String username);
} 

基本的なCRUD操作

JpaRepository が提供するメソッドを利用します。

メソッド説明
save(S entity)エンティティの保存 (新規作成または更新)
saveAll(Iterable<S> entities)複数のエンティティを一括保存
findById(ID id)ID でエンティティを検索 (Optional<T> を返す)
existsById(ID id)指定した ID のエンティティが存在するか確認
findAll()すべてのエンティティを取得
findAllById(Iterable<ID> ids)複数の ID でエンティティを検索
count()エンティティの総数を取得
deleteById(ID id)ID でエンティティを削除
delete(T entity)エンティティを指定して削除
deleteAll(Iterable<? extends T> entities)複数のエンティティを一括削除
deleteAll()すべてのエンティティを削除 (注意して使用)

カスタムクエリ (`@Query`)

メソッド名だけでは表現できない複雑なクエリは JPQL や ネイティブ SQL で記述します。

例: @Query の使用
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> { // JPQL (Java Persistence Query Language) @Query("SELECT u FROM User u WHERE u.email LIKE %:keyword% OR u.username LIKE %:keyword%") List<User> searchByKeyword(@Param("keyword") String keyword); // ネイティブ SQL (データベース固有のSQL) @Query(value = "SELECT * FROM users WHERE YEAR(created_at) = :year", nativeQuery = true) List<User> findUsersCreatedInYear(@Param("year") int year); // 更新/削除クエリ ( @Modifying と @Transactional が必要になる場合が多い) @Modifying @Query("UPDATE User u SET u.email = :newEmail WHERE u.id = :id") int updateUserEmail(@Param("id") Long id, @Param("newEmail") String newEmail);
} 

ページングとソート

JpaRepository (または PagingAndSortingRepository) を継承し、メソッドの引数に Pageable を追加します。

例: ページングとソートの実装
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ItemRepository extends JpaRepository<Item, Long> { // 全件検索 (ページング・ソート付き) Page<Item> findAll(Pageable pageable); // 条件付き検索 (ページング・ソート付き) Page<Item> findByCategory(String category, Pageable pageable);
}
// サービス層での利用例
@Service
public class ItemService { @Autowired private ItemRepository itemRepository; public Page<Item> findItems(int page, int size, String sortField, String sortDirection) { Sort sort = Sort.by(Sort.Direction.fromString(sortDirection), sortField); Pageable pageable = PageRequest.of(page, size, sort); return itemRepository.findAll(pageable); }
} 

Pageable の作成: PageRequest.of(pageNumber, pageSize, sort)

Page オブジェクトからは、コンテンツリスト (getContent())、総ページ数 (getTotalPages())、総要素数 (getTotalElements()) などを取得できます。

トランザクション管理 (`@Transactional`)

複数のデータ操作を一つの作業単位として扱うためのアノテーション。

  • 通常、サービスクラスやそのメソッドに付与します。
  • メソッドが正常終了すればコミット、例外が発生すればロールバックされます。
  • readOnly = true: 読み取り専用トランザクション (最適化)
  • propagation: トランザクションの伝播動作 (REQUIRED, REQUIRES_NEW など)
  • isolation: トランザクション分離レベル (READ_COMMITTED, SERIALIZABLE など)
  • rollbackFor / noRollbackFor: ロールバック対象/非対象の例外を指定
例: サービスメソッドでの利用
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private OrderItemRepository orderItemRepository; @Autowired private InventoryService inventoryService; // 在庫管理サービス @Transactional // このメソッド全体が1つのトランザクションになる public Order placeOrder(OrderRequest request) { // 注文情報作成 Order order = new Order(/* ... */); orderRepository.save(order); // 注文明細作成 & 在庫引き当て for (OrderItemRequest itemRequest : request.getItems()) { inventoryService.decreaseStock(itemRequest.getProductId(), itemRequest.getQuantity()); // 在庫がなければ例外発生 OrderItem item = new OrderItem(order, /* ... */); orderItemRepository.save(item); } // 例外が発生しなければ、ここまでのDB操作がコミットされる // 例外が発生すれば、全てのDB操作がロールバックされる return order; } @Transactional(readOnly = true) // 読み取り専用 public Order getOrderDetails(Long orderId) { return orderRepository.findById(orderId) .orElseThrow(() -> new ResourceNotFoundException("Order not found")); }
} 
@Transactional を有効にするには、@EnableTransactionManagement を設定クラスに追加する必要がありますが、Spring Boot では通常自動構成されます。@Transactional は public メソッドにのみ適用されます。同じクラス内のメソッド呼び出しではトランザクション境界が適用されない場合があるので注意が必要です (プロキシの問題)。

DI (Dependency Injection)

コンポーネント間の依存関係の管理

コンポーネントスキャン

  • @SpringBootApplication: このアノテーションが付与されたクラスのパッケージ以下を自動的にスキャンします (@ComponentScan を内包)。
  • @ComponentScan: スキャン対象のベースパッケージを明示的に指定する場合に使用。

Bean 定義 (ステレオタイプアノテーション)

アノテーション説明主な用途
@Component汎用的なコンポーネントを示す基本的なアノテーション。特定の層に属さないユーティリティクラスなど。
@Serviceビジネスロジック層のコンポーネントを示す。@Component の特殊化。サービスクラス。
@Repositoryデータアクセス層 (永続化層) のコンポーネントを示す。@Component の特殊化。例外変換機能を持つ。DAO (Data Access Object) や Spring Data JPA リポジトリインターフェース。
@Controllerプレゼンテーション層 (Web) のコントローラーを示す。@Component の特殊化。Spring MVC コントローラー。
@RestControllerREST API 用のコントローラーを示す。@Controller + @ResponseBodyREST API コントローラー。
@Configuration設定クラスを示す。@Bean メソッドを持つことができる。@Component の特殊化。Javaベースの設定クラス。

Bean のインジェクション (依存性の注入)

方法説明推奨度
コンストラクタインジェクションクラスのコンストラクタ引数に必要な依存性を定義する。Spring が自動で Bean を探し注入する。推奨 (不変性、テスト容易性)
@Service
public class MyService { private final UserRepository userRepository; // final推奨 // @Autowired はコンストラクタが1つの場合は省略可能 public MyService(UserRepository userRepository) { this.userRepository = userRepository; } // ...
} 
セッターインジェクション依存性を注入するためのセッターメソッドを用意し、@Autowired を付与する。任意依存 (Optional) の場合など
@Service
public class MyService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } // ...
} 
フィールドインジェクションフィールドに直接 @Autowired を付与する。非推奨 (テスト困難、DIコンテナ依存)
@Service
public class MyService { @Autowired private UserRepository userRepository; // 非推奨 // ...
} 

インジェクション候補の限定 (`@Qualifier`)

同じ型の Bean が複数存在する場合、どちらを注入するかを指定します。

例: @Qualifier の使用
// Bean 定義
@Component("emailNotificationService")
public class EmailNotificationService implements NotificationService { ... }
@Component("smsNotificationService")
public class SmsNotificationService implements NotificationService { ... }
// インジェクション
@Service
public class UserRegistrationService { private final NotificationService notificationService; // "emailNotificationService" という名前の Bean を注入 public UserRegistrationService(@Qualifier("emailNotificationService") NotificationService notificationService) { this.notificationService = notificationService; } // ...
} 

プロファイルによる Bean 切り替え (`@Profile`)

特定のプロファイルがアクティブな場合にのみ Bean を有効化します。

例: @Profile の使用
// 開発環境用ダミーサービス
@Service
@Profile("dev") // "dev" プロファイルが有効な場合のみ Bean として登録
public class DevMailSender implements MailSender { ... }
// 本番環境用実サービス
@Service
@Profile("prod") // "prod" プロファイルが有効な場合のみ Bean として登録
public class ProdMailSender implements MailSender { ... }
// MailSender を利用するクラス
@Service
public class NotificationService { private final MailSender mailSender; public NotificationService(MailSender mailSender) { // 実行時のプロファイルに応じて適切な Bean が注入される this.mailSender = mailSender; } // ...
} 

設定管理

アプリケーション設定値の扱い方

`@Value` アノテーション

設定ファイル (application.properties/.yml) や環境変数から個々のプロパティ値をフィールドにインジェクトします。

例: @Value の使用
@Component
public class AppInfo { @Value("${spring.application.name}") // application.properties/yml から値を取得 private String appName; @Value("${server.port}") private int port; @Value("${myapp.feature.enabled:false}") // デフォルト値付き (プロパティが存在しない場合 false) private boolean featureEnabled; @Value("${JAVA_HOME}") // 環境変数も取得可能 private String javaHome; // ... Getters or usage
} 

`@ConfigurationProperties`

関連するプロパティ群を型安全なオブジェクト (POJO) にまとめてバインドします。

  • プレフィックスを指定して、そのプレフィックスで始まるプロパティをマッピングします。
  • クラスまたは @Bean メソッドに付与します。
  • @EnableConfigurationProperties(YourPropertiesClass.class) を設定クラスに追加するか、対象クラスに @Configuration または @Component などを付与します。
  • バリデーション (@Validated と Bean Validation アノテーション) も可能です。
例: application.yml
myapp: api: url: https://api.example.com timeout-ms: 5000 retry-count: 3 feature: new-ui: enabled: true 
例: @ConfigurationProperties クラス
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
@ConfigurationProperties(prefix = "myapp") // "myapp" プレフィックスのプロパティをバインド
@Validated // バリデーションを有効化
public class MyAppProperties { private final Api api = new Api(); private final Feature feature = new Feature(); // Getter が必要 public Api getApi() { return api; } public Feature getFeature() { return feature; } // ネストしたプロパティ用の内部クラス public static class Api { @NotBlank // バリデーションルール private String url; @Positive private int timeoutMs = 3000; // デフォルト値 @Positive private int retryCount = 1; // デフォルト値 // Getters and Setters が必要 public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getTimeoutMs() { return timeoutMs; } public void setTimeoutMs(int timeoutMs) { this.timeoutMs = timeoutMs; } public int getRetryCount() { return retryCount; } public void setRetryCount(int retryCount) { this.retryCount = retryCount; } } public static class Feature { private final NewUi newUi = new NewUi(); public NewUi getNewUi() { return newUi; } public static class NewUi { private boolean enabled = false; // デフォルト値 public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } } }
}
// 設定クラスで有効化
@Configuration
@EnableConfigurationProperties(MyAppProperties.class)
public class AppConfig { // MyAppProperties Bean が利用可能になる
}
// 利用例
@Service
public class SomeService { private final MyAppProperties props; public SomeService(MyAppProperties props) { this.props = props; String apiUrl = props.getApi().getUrl(); boolean isNewUiEnabled = props.getFeature().getNewUi().isEnabled(); // ... }
} 

設定の優先順位

Spring Boot は様々な場所から設定を読み込み、以下の順序 (上が高優先) で上書きします。

  1. Devtools のグローバル設定 (~/.spring-boot-devtools.properties)
  2. テスト中の @TestPropertySource
  3. テスト中の properties 属性 (@SpringBootTest など)
  4. コマンドライン引数 (--server.port=9000)
  5. SPRING_APPLICATION_JSON からのプロパティ (環境変数やシステムプロパティのインラインJSON)
  6. ServletConfig 初期化パラメータ
  7. ServletContext 初期化パラメータ
  8. java:comp/env の JNDI 属性
  9. Java システムプロパティ (System.getProperties())
  10. OS 環境変数
  11. random.* プロパティ (RandomValuePropertySource)
  12. jar 外のプロファイル固有 application-{profile}.properties/yml
  13. jar 内のプロファイル固有 application-{profile}.properties/yml
  14. jar 外の application.properties/yml
  15. jar 内の application.properties/yml
  16. @Configuration クラスの @PropertySource
  17. デフォルトプロパティ (SpringApplication.setDefaultProperties)

セキュリティ (Spring Security)

認証と認可の実装

spring-boot-starter-security 依存関係を追加すると、基本的な認証 (HTTP Basic 認証、フォームログイン) が自動構成されます。カスタマイズには設定クラスが必要です。

基本的な設定クラス

SecurityFilterChain Bean を定義して、HTTP セキュリティルールを設定します。

例: 基本的なセキュリティ設定
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity // Spring Security を有効化
public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public/**", "/h2-console/**").permitAll() // 認証不要なパス .requestMatchers("/admin/**").hasRole("ADMIN") // ADMIN ロールが必要なパス .anyRequest().authenticated() // その他のリクエストは認証が必要 ) .formLogin(withDefaults()) // フォームベースログインを有効化 (デフォルト設定) .httpBasic(withDefaults()) // HTTP Basic 認証を有効化 (デフォルト設定) .csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")) // H2コンソール用にCSRF無効化 (開発時) .headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin())); // H2コンソール用にX-Frame-Options設定 return http.build(); } // パスワードエンコーダーの Bean 定義 (必須) @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // --- 認証方法の選択 (以下のいずれか、またはカスタム実装) --- // 1. インメモリ認証 (テスト・開発用) @Bean public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user = User.builder() .username("user") .password(passwordEncoder.encode("password")) // パスワードはエンコードする .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password(passwordEncoder.encode("admin")) .roles("ADMIN", "USER") .build(); return new InMemoryUserDetailsManager(user, admin); } /* // 2. JDBC 認証 (データベースを利用) - application.properties に datasource 設定が必要 @Bean public UserDetailsService jdbcUserDetailsService(DataSource dataSource) { // デフォルトのスキーマを利用する場合 (users, authorities テーブル) return new JdbcUserDetailsManager(dataSource); // カスタムクエリを指定する場合 // JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource); // manager.setUsersByUsernameQuery("SELECT username, password, enabled FROM my_users WHERE username = ?"); // manager.setAuthoritiesByUsernameQuery("SELECT username, authority FROM my_authorities WHERE username = ?"); // return manager; } */ /* // 3. カスタム UserDetailsService (推奨される方法) // UserRepository を利用してDBからユーザー情報を取得する実装 @Bean public UserDetailsService customUserDetailsService(UserRepository userRepository) { return username -> userRepository.findByUsername(username) .map(user -> org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) // DBにはエンコード済みパスワードを保存 .roles("USER") // DBからロール情報を取得するロジックを追加 .build()) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); } */
} 

主な設定要素

設定メソッド説明
authorizeHttpRequests()URL パターンごとにアクセス許可ルール (認証要否、ロール/権限) を設定。
formLogin()フォームベースのログイン設定。ログインページ、成功/失敗時のハンドラなどをカスタマイズ可能。
httpBasic()HTTP Basic 認証を設定。
logout()ログアウト機能の設定。ログアウトURL、成功時の遷移先などをカスタマイズ可能。
csrf()CSRF (Cross-Site Request Forgery) 保護の設定。無効化や特定のパスを除外可能。
sessionManagement()セッション管理戦略 (ステートレス、セッション固定攻撃対策など) を設定。
oauth2Login()OAuth 2.0 を利用したログインを設定 (Google, Facebook など)。
oauth2ResourceServer()OAuth 2.0 リソースサーバーとしての設定 (JWT や Opaque Token の検証)。
addFilterBefore() / addFilterAfter()カスタムフィルターをセキュリティフィルターチェーンに追加。JWT 認証フィルターなどで使用。

JWT (JSON Web Token) 認証

ステートレス API で一般的に使用される認証方式。

  1. ユーザーがログインすると、サーバーは JWT を生成して返す。
  2. クライアントは以降のリクエストで、Authorization ヘッダー (Bearer スキーマ) に JWT を含めて送信する。
  3. サーバーはリクエストごとに JWT を検証し、ユーザーを認証する。

実装には以下の要素が必要:

  • JWT 生成・検証ライブラリ (例: java-jwt, jjwt)
  • JWT の生成ロジック (ログイン成功時)
  • JWT 検証フィルター (リクエストごとに実行され、SecurityContextHolder に認証情報を設定)
  • Spring Security 設定で、JWT フィルターを適切な位置に追加し、デフォルトのフォームログインなどを無効化。
JWT の実装は複雑になることがあるため、ライブラリや既存の実装例を参考に慎重に行う必要があります。特に秘密鍵の管理やトークンの有効期限設定が重要です。

テスト

アプリケーションの品質保証

spring-boot-starter-test 依存関係を追加すると、JUnit 5, Spring Test, Spring Boot Test, AssertJ, Mockito, Hamcrest, JSONassert, JsonPath などが含まれます。テストは src/test/java 配下に作成します。

主要なテスト用アノテーション

アノテーション説明主な用途
@SpringBootTestSpring Boot アプリケーションのコンテキスト全体をロードして統合テストを実行。アプリケーション全体の動作確認、コンポーネント間の連携テスト。
@WebMvcTestSpring MVC コンポーネント (コントローラー、@ControllerAdvice など) に焦点を当てたスライステスト。Web レイヤーのみロード。コントローラーの単体テスト (リクエスト処理、レスポンス検証)。MockMvc を使用。
@DataJpaTestJPA コンポーネント (リポジトリ、エンティティ) に焦点を当てたスライステスト。インメモリデータベース (H2 など) を自動構成。リポジトリの CRUD 操作、カスタムクエリのテスト。TestEntityManager を使用。
@RestClientTestRestTemplateWebClient を使用したクライアントサイドの REST 呼び出しテスト。外部 API 呼び出しのモック化とテスト。MockRestServiceServer を使用。
@JsonTestJSON のシリアライズ/デシリアライズに焦点を当てたテスト。DTO やエンティティの JSON 変換テスト。JacksonTester を使用。
@MockBeanSpring コンテキスト内の特定の Bean を Mockito モックに置き換える。依存する Bean (サービス、リポジトリなど) の動作をモック化したい場合 (@SpringBootTest, @WebMvcTest などと併用)。
@SpyBeanSpring コンテキスト内の特定の Bean を Mockito スパイに置き換える (実際のメソッドを呼び出しつつ、一部をモック化可能)。実際の Bean の動作を部分的に変更したい場合。
@TestPropertySourceテスト実行時に使用するプロパティソースを指定 (ファイルやインラインプロパティ)。テスト用の設定値を適用したい場合。
@ActiveProfilesテスト実行時に有効化するプロファイルを指定。特定のプロファイルでの動作をテストしたい場合。
@DirtiesContextテストメソッドまたはクラス実行後にアプリケーションコンテキストを破棄・再作成するようマークする。テストがコンテキストの状態を変更し、後続のテストに影響を与える可能性がある場合。

テスト例

例: コントローラーテスト (@WebMvcTest)
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.hamcrest.Matchers.*;
@WebMvcTest(UserController.class) // UserController をテスト対象とする
public class UserControllerTest { @Autowired private MockMvc mockMvc; // MVCリクエストをシミュレート @MockBean // UserService をモック化 private UserService userService; @Test void shouldGetUserById() throws Exception { User user = new User(1L, "testuser", "test@example.com"); given(userService.findById(1L)).willReturn(Optional.of(user)); mockMvc.perform(get("/api/users/1")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id", is(1))) .andExpect(jsonPath("$.username", is("testuser"))); } @Test void shouldReturnNotFoundForMissingUser() throws Exception { given(userService.findById(99L)).willReturn(Optional.empty()); mockMvc.perform(get("/api/users/99")) .andExpect(status().isNotFound()); } @Test void shouldCreateUser() throws Exception { User newUser = new User(null, "newuser", "new@example.com"); User createdUser = new User(2L, "newuser", "new@example.com"); given(userService.createUser(any(User.class))).willReturn(createdUser); String userJson = "{\"username\":\"newuser\",\"email\":\"new@example.com\"}"; // Jackson などで生成推奨 mockMvc.perform(post("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(userJson)) .andExpect(status().isCreated()) .andExpect(header().string("Location", "/api/users/2")) .andExpect(jsonPath("$.id", is(2))) .andExpect(jsonPath("$.username", is("newuser"))); }
} 
例: リポジトリテスト (@DataJpaTest)
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; // JPA操作用ユーティリティ
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Optional;
@DataJpaTest // JPA関連のコンポーネントのみロード、デフォルトで H2 を使用
public class UserRepositoryTest { @Autowired private TestEntityManager entityManager; // テストデータの準備や永続化状態の確認 @Autowired private UserRepository userRepository; @Test void shouldSaveAndFindUser() { User user = new User(null, "jpatest", "jpa@test.com"); User savedUser = entityManager.persistFlushFind(user); // 保存してDBから取得 Optional<User> foundUserOpt = userRepository.findById(savedUser.getId()); assertThat(foundUserOpt).isPresent(); assertThat(foundUserOpt.get().getUsername()).isEqualTo("jpatest"); } @Test void shouldFindByUsername() { User user1 = new User(null, "findme", "findme@test.com"); entityManager.persistAndFlush(user1); User user2 = new User(null, "another", "another@test.com"); entityManager.persistAndFlush(user2); Optional<User> foundUserOpt = userRepository.findByUsername("findme"); assertThat(foundUserOpt).isPresent(); assertThat(foundUserOpt.get().getId()).isEqualTo(user1.getId()); } @Test void shouldNotExistForUnknownUsername() { Optional<User> foundUserOpt = userRepository.findByUsername("unknown"); assertThat(foundUserOpt).isNotPresent(); }
} 

テストユーティリティ

  • MockMvc: Spring MVC コントローラーのテスト用。HTTP リクエストをシミュレートし、レスポンスを検証。
  • TestRestTemplate: 実際の HTTP サーバーを起動するテスト (@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)) で、アプリケーションにリクエストを送信。
  • TestEntityManager: JPA テストでエンティティの永続化、検索、フラッシュなどを実行。
  • AssertJ: 流れるようなインターフェースを持つアサーションライブラリ (assertThat(...))。
  • Mockito: モックオブジェクト作成ライブラリ (mock(), when(), verify())。

Actuator (運用監視)

アプリケーションの状態監視と管理

spring-boot-starter-actuator 依存関係を追加すると、様々な監視用エンドポイントが利用可能になります。デフォルトでは /actuator/health/actuator/info のみが公開されています (Web経由の場合)。

主要なエンドポイント

application.properties または .yml で公開するエンドポイントを設定します。

例: application.yml でのエンドポイント公開設定
management: endpoints: web: exposure: include: health, info, metrics, env, loggers, threaddump, heapdump # 公開するエンドポイントをカンマ区切りで指定 ( '*' ですべて公開) # exclude: heapdump # 特定のエンドポイントを除外することも可能 endpoint: health: show-details: when_authorized # /health の詳細表示権限 (always, never, when_authorized) # roles: ACTUATOR_ADMIN # 詳細表示に必要なロール (Spring Security連携時) info: enabled: true # /info エンドポイントの有効化 (デフォルトで有効) info: # /info エンドポイントで表示する情報 app: name: ${spring.application.name} description: My Awesome Spring Boot Application version: @project.version@ # Maven/Gradle プロジェクトのバージョン (ビルド時に置換) # git: # git.properties があればGit情報も表示可能 (要 maven/gradle plugin) # build: # build-info.properties があればビルド情報も表示可能 (要 maven/gradle plugin) 
エンドポイントパス説明デフォルト公開 (Web)
/actuator/healthアプリケーションのヘルスチェック情報 (状態: UP/DOWN、詳細: DB接続、ディスク空き容量など)。Yes
/actuator/infoアプリケーションの基本情報 (ビルド情報、Git情報、カスタム情報など)。Yes
/actuator/metricsアプリケーションの各種メトリクス情報 (JVMメモリ、CPU使用率、HTTPリクエスト数など)。Micrometer に基づく。No
/actuator/metrics/{metric.name}特定のメトリクスの詳細情報を取得。No
/actuator/envSpring Environment のプロパティ (設定値、環境変数、システムプロパティなど) を表示。機密情報が含まれる可能性あり。No
/actuator/loggersアプリケーションのロガーとそのレベルを表示・変更可能。No
/actuator/threaddumpJVM のスレッドダンプを取得。No
/actuator/heapdumpJVM のヒープダンプを取得。ファイルサイズが大きい可能性あり。No
/actuator/beansSpring アプリケーションコンテキストに登録されているすべての Bean を表示。No
/actuator/configpropsすべての @ConfigurationProperties Bean とその値を表示。No
/actuator/mappingsすべての @RequestMapping パスとそのハンドラーメソッドを表示。No
/actuator/scheduledtasksアプリケーション内のスケジュールされたタスクを表示。No
/actuator/shutdownアプリケーションを正常にシャットダウン (デフォルトで無効、有効化には management.endpoint.shutdown.enabled=true が必要)。No

セキュリティ設定

Actuator エンドポイントは機密情報を含む可能性があるため、本番環境では適切に保護する必要があります。

  • Spring Security を使用して、エンドポイントへのアクセスを認証・認可する (推奨)。
  • 異なるポート (management.server.port) やパス (management.endpoints.web.base-path) で公開し、ネットワークレベルでアクセスを制限する。
例: Spring Security で Actuator を保護
// SecurityConfig 内に追加
@Configuration
@EnableWebSecurity
public class SecurityConfig { // ... (他の設定) @Bean @Order(1) // 他のセキュリティ設定より優先度を高くする public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/actuator/**") // /actuator/** パスにのみ適用 .authorizeHttpRequests(authorize -> authorize .requestMatchers("/actuator/health", "/actuator/info").permitAll() // healthとinfoは認証不要 .anyRequest().hasRole("ACTUATOR_ADMIN") // 他のActuatorエンドポイントは ACTUATOR_ADMIN ロールが必要 ) .httpBasic(withDefaults()); // Basic認証を使用 (他の認証方法も可) return http.build(); } @Bean public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { // ... アプリケーション本体のセキュリティ設定 ... http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .formLogin(withDefaults()); return http.build(); } // ... (PasswordEncoder, UserDetailsService の Bean 定義) // ACTUATOR_ADMIN ロールを持つユーザーを作成する必要がある
} 

非同期処理

時間のかかる処理のバックグラウンド実行

有効化と基本設定

  1. 設定クラスに @EnableAsync アノテーションを付与。
  2. 非同期で実行したいメソッド (public) に @Async アノテーションを付与。
例: 設定クラス
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync // 非同期処理を有効化
public class AsyncConfig { // 必要に応じて TaskExecutor のカスタマイズ Bean を定義 /* @Bean(name = "taskExecutor") public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); executor.setThreadNamePrefix("Async-"); executor.initialize(); return executor; } */
} 
例: 非同期メソッド
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class EmailService { @Async // このメソッドは別のスレッドで実行される public void sendWelcomeEmail(String toAddress) { System.out.println("Sending welcome email to " + toAddress + " on thread: " + Thread.currentThread().getName()); // 時間のかかるメール送信処理 (例) try { Thread.sleep(2000); // 2秒待機 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Email sent to " + toAddress); } @Async("taskExecutor") // 使用する TaskExecutor Bean を指定する場合 public CompletableFuture<Boolean> checkEmailStatus(String emailId) { System.out.println("Checking email status for " + emailId + " on thread: " + Thread.currentThread().getName()); // 外部API呼び出しなどの時間のかかる処理 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return CompletableFuture.completedFuture(false); } boolean success = Math.random() > 0.5; // ダミーの結果 return CompletableFuture.completedFuture(success); }
}
// 呼び出し側
@RestController
public class UserController { @Autowired private EmailService emailService; @PostMapping("/register") public ResponseEntity<String> registerUser(@RequestBody UserRegistrationRequest request) { // ... ユーザー登録処理 ... // メール送信は非同期で実行 (呼び出し元は待たない) emailService.sendWelcomeEmail(request.getEmail()); // 結果が必要な非同期処理 CompletableFuture<Boolean> statusFuture = emailService.checkEmailStatus("some-id"); // 他の処理を進める... // ... // 結果が必要になったら待機 (または非同期でハンドリング) // try { // Boolean status = statusFuture.get(); // 結果を同期的に取得 (ブロックする) // System.out.println("Email status check result: " + status); // } catch (InterruptedException | ExecutionException e) { // // エラーハンドリング // } return ResponseEntity.ok("User registered, welcome email sending started."); }
} 

注意点

  • @Async メソッドは public である必要があります。
  • 同じクラス内のメソッドから @Async メソッドを呼び出しても非同期にはなりません (プロキシ経由で呼び出される必要がある)。別クラスに切り出すか、自身をインジェクションするなどの工夫が必要です。
  • 戻り値が void の場合、例外は呼び出し元に伝播しません。例外ハンドリングには AsyncUncaughtExceptionHandler を実装します。
  • 戻り値が Future, CompletableFuture, または ListenableFuture の場合、呼び出し元で結果や例外を取得できます。
  • デフォルトでは SimpleAsyncTaskExecutor が使用されますが、スレッドを再利用しないため、本番環境では ThreadPoolTaskExecutor などでスレッドプールを設定することが推奨されます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です