概述
Spring Framework 廣泛使用註解(Annotations)來簡化配置和提升開發效率。從 Spring 2.5 開始引入註解驅動配置,到 Spring Boot 的自動配置,註解已成為現代 Spring 應用程式開發的核心。本文將系統性地整理 Spring 生態系統中的重要註解,並提供實用的程式碼範例。
核心容器註解
1. Bean 定義與管理
@Component 系列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| // 通用組件
@Component
public class UserValidator {
public boolean validate(User user) {
return user != null && user.getEmail() != null;
}
}
// 服務層組件
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
// 資料存取層組件
@Repository
public class UserRepositoryImpl implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Optional<User> findById(Long id) {
User user = entityManager.find(User.class, id);
return Optional.ofNullable(user);
}
}
// Web 層組件
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users/{id}")
@ResponseBody
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
// RESTful API 控制器
@RestController // 等同於 @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserRestController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
|
@Configuration 和 @Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| @Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example")
public class AppConfiguration {
@Bean
@Primary // 當有多個相同類型的 Bean 時,優先使用這個
public DataSource primaryDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/primary");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
@Bean
@Qualifier("secondary") // 使用限定符區分
public DataSource secondaryDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/secondary");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
@Bean
@Scope("prototype") // 每次注入都創建新實例
public OrderProcessor orderProcessor() {
return new OrderProcessor();
}
@Bean
@Lazy // 延遲初始化
public ExpensiveService expensiveService() {
return new ExpensiveService();
}
}
|
@Configuration(proxyBeanMethods = false)
預設 proxyBeanMethods = true:Spring 會用 CGLIB 代理 @Configuration 類別,確保 @Bean 方法之間互相呼叫時回傳的是同一個 singleton。
設為 false 時不代理,效能略好(適合 Spring Boot 的 auto-configuration 內部使用),但 @Bean 方法互相呼叫時會產生新 instance,而非 singleton。
Rule:除非明確需要 @Bean 互呼且要確保 singleton,否則保持預設 true。
@ConditionalOnMissingBean / @ConditionalOnProperty
這兩個是 Spring Boot auto-configuration 的核心機制:
1
2
3
4
5
6
7
| @Bean
@ConditionalOnMissingBean(DataSource.class) // 只在沒有 DataSource bean 時才建立
public DataSource embeddedDataSource() { ... }
@Bean
@ConditionalOnProperty(name = "feature.cache.enabled", havingValue = "true")
public CacheManager cacheManager() { ... }
|
@ConditionalOnMissingBean 是「讓使用者覆蓋預設設定」的標準做法。
2. 依賴注入註解
@Autowired 和相關註解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| @Service
public class OrderService {
// 建構子注入(推薦)
private final PaymentService paymentService;
private final EmailService emailService;
@Autowired
public OrderService(PaymentService paymentService, EmailService emailService) {
this.paymentService = paymentService;
this.emailService = emailService;
}
// 可選依賴
@Autowired(required = false)
private Optional<SmsService> smsService;
// 集合注入
@Autowired
private List<NotificationProvider> notificationProviders;
// 使用限定符
@Autowired
@Qualifier("primary")
private DataSource primaryDataSource;
// JSR-330 標準註解
@Inject // 等同於 @Autowired
private AuditService auditService;
@Named("cache") // 等同於 @Qualifier
private CacheManager cacheManager;
}
|
@Value 屬性注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| @Component
public class ConfigurationService {
// 基本屬性注入
@Value("${app.name}")
private String appName;
// 預設值
@Value("${app.timeout:30}")
private int timeout;
// 系統屬性
@Value("#{systemProperties['user.home']}")
private String userHome;
// SpEL 表達式
@Value("#{T(java.lang.Math).random() * 100}")
private double randomNumber;
// 陣列注入
@Value("${app.allowed-origins}")
private String[] allowedOrigins;
// List 注入
@Value("#{'${app.features}'.split(',')}")
private List<String> features;
// Map 注入
@Value("#{${app.database-config}}")
private Map<String, String> databaseConfig;
// 建構子參數注入
public ConfigurationService(@Value("${app.version}") String version) {
this.version = version;
}
}
|
Web 相關註解
1. MVC 控制器註解
@RequestMapping 系列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| @RestController
@RequestMapping("/api/v1/orders")
@CrossOrigin(origins = "http://localhost:3000") // CORS 設定
public class OrderController {
@Autowired
private OrderService orderService;
// GET 請求
@GetMapping
public Page<Order> getOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String status,
Pageable pageable) {
return orderService.findOrders(status, pageable);
}
// 路徑變數
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id) {
return orderService.findById(id)
.map(order -> ResponseEntity.ok(order))
.orElse(ResponseEntity.notFound().build());
}
// POST 請求
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Order createOrder(@RequestBody @Valid CreateOrderRequest request) {
return orderService.createOrder(request);
}
// PUT 請求
@PutMapping("/{id}")
public ResponseEntity<Order> updateOrder(
@PathVariable Long id,
@RequestBody @Valid UpdateOrderRequest request) {
return orderService.updateOrder(id, request)
.map(order -> ResponseEntity.ok(order))
.orElse(ResponseEntity.notFound().build());
}
// DELETE 請求
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrder(@PathVariable Long id) {
orderService.deleteOrder(id);
}
// 檔案上傳
@PostMapping("/{id}/attachments")
public ResponseEntity<String> uploadAttachment(
@PathVariable Long id,
@RequestParam("file") MultipartFile file) {
String fileUrl = orderService.uploadAttachment(id, file);
return ResponseEntity.ok(fileUrl);
}
// 自訂 HTTP 方法
@RequestMapping(value = "/{id}/status", method = RequestMethod.PATCH)
public Order updateOrderStatus(
@PathVariable Long id,
@RequestBody OrderStatusUpdate statusUpdate) {
return orderService.updateStatus(id, statusUpdate);
}
}
|
參數處理註解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| @RestController
public class UserController {
// 請求頭處理
@GetMapping("/profile")
public UserProfile getProfile(
@RequestHeader("Authorization") String authToken,
@RequestHeader(value = "User-Agent", required = false) String userAgent) {
return userService.getProfile(authToken);
}
// Cookie 處理
@GetMapping("/preferences")
public UserPreferences getPreferences(
@CookieValue(name = "sessionId", required = false) String sessionId) {
return userService.getPreferences(sessionId);
}
// Session 屬性
@PostMapping("/login")
public ResponseEntity<String> login(
@RequestBody LoginRequest request,
@SessionAttribute(required = false) String captcha,
HttpSession session) {
if (userService.validateLogin(request, captcha)) {
session.setAttribute("userId", request.getUsername());
return ResponseEntity.ok("Login successful");
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login failed");
}
// Model 屬性
@ModelAttribute("commonData")
public CommonData getCommonData() {
return new CommonData();
}
@GetMapping("/dashboard")
public String dashboard(@ModelAttribute("commonData") CommonData commonData) {
return "dashboard";
}
}
|
2. 驗證註解
Bean Validation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| @RestController
@Validated // 開啟驗證
public class UserController {
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody @Valid CreateUserRequest request) {
User user = userService.createUser(request);
return ResponseEntity.ok(user);
}
@GetMapping("/users/{id}")
public User getUser(@PathVariable @Min(1) Long id) {
return userService.findById(id);
}
}
// 請求 DTO 與驗證
public class CreateUserRequest {
@NotBlank(message = "姓名不能為空")
@Size(min = 2, max = 50, message = "姓名長度必須在 2-50 字元之間")
private String name;
@NotBlank(message = "信箱不能為空")
@Email(message = "信箱格式不正確")
private String email;
@NotNull(message = "年齡不能為空")
@Min(value = 18, message = "年齡必須大於等於 18")
@Max(value = 120, message = "年齡必須小於等於 120")
private Integer age;
@Pattern(regexp = "^\\+?[1-9]\\d{1,14}$", message = "電話號碼格式不正確")
private String phone;
@Valid // 巢狀物件驗證
private Address address;
@NotEmpty(message = "至少要有一個興趣")
private List<@NotBlank String> interests;
// getters and setters...
}
// 自訂驗證註解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
String message() default "信箱已被使用";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
@Autowired
private UserRepository userRepository;
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
return email == null || !userRepository.existsByEmail(email);
}
}
|
資料存取註解
1. JPA 和 Hibernate 註解
實體定義
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| @Entity
@Table(name = "users", indexes = {
@Index(name = "idx_email", columnList = "email"),
@Index(name = "idx_created_at", columnList = "created_at")
})
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "full_name", nullable = false, length = 100)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Enumerated(EnumType.STRING)
@Column(name = "user_status")
private UserStatus status = UserStatus.ACTIVE;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@CreatedBy
@Column(name = "created_by", updatable = false)
private String createdBy;
@LastModifiedBy
@Column(name = "updated_by")
private String updatedBy;
// 一對多關係
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
private List<Order> orders = new ArrayList<>();
// 多對一關係
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
// 多對多關係
@ManyToMany
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
// 版本控制(樂觀鎖定)
@Version
private Long version;
// getters and setters...
}
|
Repository 介面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| @Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
// 查詢方法名稱規則
Optional<User> findByEmail(String email);
List<User> findByStatusAndCreatedAtAfter(UserStatus status, LocalDateTime date);
Page<User> findByNameContainingIgnoreCase(String name, Pageable pageable);
// 自訂查詢
@Query("SELECT u FROM User u WHERE u.email = ?1 AND u.status = ?2")
Optional<User> findByEmailAndStatus(String email, UserStatus status);
// 命名參數
@Query("SELECT u FROM User u WHERE u.name LIKE %:name% ORDER BY u.createdAt DESC")
List<User> findByNameContaining(@Param("name") String name);
// 原生 SQL
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);
// 更新查詢
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateUserStatus(@Param("id") Long id, @Param("status") UserStatus status);
// 刪除查詢
@Modifying
@Query("DELETE FROM User u WHERE u.status = :status")
void deleteByStatus(@Param("status") UserStatus status);
// 投影查詢
@Query("SELECT new com.example.dto.UserSummary(u.id, u.name, u.email) FROM User u")
List<UserSummary> findUserSummaries();
// 計數查詢
long countByStatus(UserStatus status);
boolean existsByEmail(String email);
}
|
2. 交易管理
@Transactional 註解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| @Service
@Transactional // 類別層級的交易設定
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
// 預設交易設定
public Order createOrder(CreateOrderRequest request) {
Order order = new Order(request);
order = orderRepository.save(order);
// 這些操作都在同一個交易中
paymentService.processPayment(order);
inventoryService.updateStock(order.getItems());
return order;
}
// 唯讀交易(效能優化)
@Transactional(readOnly = true)
public List<Order> findOrdersByUser(Long userId) {
return orderRepository.findByUserId(userId);
}
// 指定傳播行為
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderEvent(Long orderId, String event) {
// 這個方法總是在新的交易中執行
OrderEvent orderEvent = new OrderEvent(orderId, event);
orderEventRepository.save(orderEvent);
}
// 指定隔離級別
@Transactional(isolation = Isolation.SERIALIZABLE)
public void processHighValueOrder(Order order) {
// 序列化隔離級別,避免併發問題
processOrder(order);
}
// 異常回滾設定
@Transactional(rollbackFor = {Exception.class}, noRollbackFor = {ValidationException.class})
public void complexOperation() {
// 遇到任何 Exception 都回滾,除了 ValidationException
}
// 超時設定
@Transactional(timeout = 30) // 30 秒超時
public void longRunningOperation() {
// 長時間運行的操作
}
// 指定交易管理器
@Transactional(transactionManager = "secondaryTransactionManager")
public void operationOnSecondaryDatabase() {
// 使用指定的交易管理器
}
}
|
排程和非同步處理
1. 排程任務註解
@Scheduled 和 @EnableScheduling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
| @Configuration
@EnableScheduling
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
return scheduler;
}
}
@Component
@Slf4j
public class ScheduledTasks {
@Autowired
private OrderService orderService;
@Autowired
private EmailService emailService;
// 固定延遲執行(上次執行完成後多久再執行)
@Scheduled(fixedDelay = 5000) // 5 秒
public void processOrders() {
log.info("處理待處理訂單");
orderService.processePendingOrders();
}
// 固定頻率執行(固定間隔執行)
@Scheduled(fixedRate = 10000) // 10 秒
public void heartbeat() {
log.info("系統心跳檢查");
// 系統健康檢查邏輯
}
// 初始延遲
@Scheduled(fixedDelay = 30000, initialDelay = 60000) // 1 分鐘後開始,然後每 30 秒執行
public void cleanupTempFiles() {
log.info("清理暫存檔案");
// 清理邏輯
}
// Cron 表達式 - 每分鐘執行
@Scheduled(cron = "0 * * * * *")
public void everyMinute() {
log.info("每分鐘執行的任務");
}
// Cron 表達式 - 每天凌晨 2 點執行
@Scheduled(cron = "0 0 2 * * *")
public void dailyReport() {
log.info("產生每日報告");
emailService.sendDailyReport();
}
// Cron 表達式 - 工作日上午 9 點執行
@Scheduled(cron = "0 0 9 * * MON-FRI")
public void workdayMorningTask() {
log.info("工作日早晨任務");
}
// Cron 表達式 - 每月最後一天執行
@Scheduled(cron = "0 0 0 L * *")
public void monthlyTask() {
log.info("月末任務");
}
// 使用配置屬性
@Scheduled(cron = "${app.cleanup.cron:0 0 3 * * *}") // 預設每天凌晨 3 點
public void configuredTask() {
log.info("可配置的排程任務");
}
// 條件性排程
@Scheduled(fixedDelay = 60000)
@ConditionalOnProperty(name = "app.monitoring.enabled", havingValue = "true")
public void monitoringTask() {
log.info("監控任務");
}
}
|
Cron 表達式詳解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| /**
* Cron 表達式格式:秒 分 時 日 月 週
*
* 欄位說明:
* - 秒:0-59
* - 分:0-59
* - 時:0-23
* - 日:1-31
* - 月:1-12 或 JAN-DEC
* - 週:0-7 或 SUN-SAT (0 和 7 都代表週日)
*
* 特殊字符:
* * : 匹配任意值
* ? : 只能用在日和週,表示不指定值
* - : 表示範圍 (例如:1-5)
* , : 表示列舉 (例如:1,3,5)
* / : 表示增量 (例如:0/15 表示從 0 開始每 15 分鐘)
* L : 表示最後 (例如:L 在日欄位表示該月最後一天)
* W : 表示工作日 (例如:15W 表示距離 15 號最近的工作日)
* # : 表示第幾個週幾 (例如:6#3 表示第三個週五)
*/
@Component
public class CronExamples {
@Scheduled(cron = "0 0 * * * *") // 每小時整點執行
public void hourly() {}
@Scheduled(cron = "*/10 * * * * *") // 每 10 秒執行
public void every10Seconds() {}
@Scheduled(cron = "0 0 8-18 * * *") // 每天 8-18 點整點執行
public void businessHours() {}
@Scheduled(cron = "0 0/30 8-18 * * *") // 每天 8-18 點每半小時執行
public void businessHalfHour() {}
@Scheduled(cron = "0 0 9-17 * * MON-FRI") // 工作日 9-17 點整點執行
public void workingHours() {}
@Scheduled(cron = "0 0 0 25 12 ?") // 每年聖誕節 00:00 執行
public void christmas() {}
@Scheduled(cron = "0 0 0 L * *") // 每月最後一天 00:00 執行
public void lastDayOfMonth() {}
@Scheduled(cron = "0 0 0 LW * *") // 每月最後一個工作日 00:00 執行
public void lastWorkdayOfMonth() {}
@Scheduled(cron = "0 0 0 * * 1#1") // 每月第一個週一 00:00 執行
public void firstMondayOfMonth() {}
}
|
2. 非同步處理註解
@Async 和 @EnableAsync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
| @Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-task-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("非同步方法 {} 執行異常", method.getName(), ex);
};
}
}
@Service
@Slf4j
public class NotificationService {
// 簡單非同步方法
@Async
public void sendEmail(String to, String subject, String content) {
log.info("發送郵件到 {}", to);
// 模擬郵件發送
try {
Thread.sleep(2000);
log.info("郵件發送完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 返回 CompletableFuture
@Async
public CompletableFuture<String> sendSms(String phone, String message) {
log.info("發送簡訊到 {}", phone);
try {
Thread.sleep(1000);
return CompletableFuture.completedFuture("簡訊發送成功");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(e);
}
}
// 指定執行器
@Async("taskExecutor")
public CompletableFuture<Boolean> processLargeFile(String filePath) {
log.info("開始處理大檔案:{}", filePath);
try {
// 模擬檔案處理
Thread.sleep(5000);
log.info("檔案處理完成:{}", filePath);
return CompletableFuture.completedFuture(true);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(e);
}
}
// 批量非同步處理
public CompletableFuture<Void> sendBulkNotifications(List<User> users, String message) {
List<CompletableFuture<Void>> futures = users.stream()
.map(user -> sendNotificationAsync(user, message))
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
@Async
private CompletableFuture<Void> sendNotificationAsync(User user, String message) {
// 發送通知邏輯
return CompletableFuture.completedFuture(null);
}
}
|
Spring Boot 特定註解
1. 自動配置註解
@SpringBootApplication 分解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // @SpringBootApplication 等同於以下三個註解的組合:
// @Configuration + @EnableAutoConfiguration + @ComponentScan
@SpringBootApplication
// 等同於:
// @Configuration
// @EnableAutoConfiguration
// @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
// @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 自訂掃描配置
@SpringBootApplication(
scanBasePackages = {"com.example.app", "com.example.shared"},
exclude = {DataSourceAutoConfiguration.class} // 排除特定自動配置
)
public class CustomApplication {
public static void main(String[] args) {
SpringApplication.run(CustomApplication.class, args);
}
}
|
條件註解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
| @Configuration
public class ConditionalConfiguration {
// 當類別存在時
@Bean
@ConditionalOnClass(RedisTemplate.class)
public RedisService redisService() {
return new RedisService();
}
// 當類別不存在時
@Bean
@ConditionalOnMissingClass("com.example.CustomService")
public DefaultService defaultService() {
return new DefaultService();
}
// 當 Bean 存在時
@Bean
@ConditionalOnBean(DataSource.class)
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 當 Bean 不存在時
@Bean
@ConditionalOnMissingBean(EmailService.class)
public EmailService mockEmailService() {
return new MockEmailService();
}
// 當屬性存在且符合條件時
@Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true", matchIfMissing = false)
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
// 當 Web 應用程式時
@Bean
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public WebMvcConfigurer webMvcConfigurer() {
return new CustomWebMvcConfigurer();
}
// 當非 Web 應用程式時
@Bean
@ConditionalOnNotWebApplication
public CommandLineRunner commandLineRunner() {
return args -> System.out.println("Non-web application started");
}
// 當特定 Profile 啟用時
@Bean
@Profile("development")
public DataSource devDataSource() {
return new H2DataSource();
}
@Bean
@Profile("production")
public DataSource prodDataSource() {
return new MySQLDataSource();
}
// 自訂條件
@Bean
@ConditionalOnCustomCondition
public CustomService customService() {
return new CustomService();
}
}
// 自訂條件實作
public class CustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 自訂條件邏輯
String osName = context.getEnvironment().getProperty("os.name");
return osName != null && osName.toLowerCase().contains("windows");
}
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(CustomCondition.class)
public @interface ConditionalOnCustomCondition {
}
|
2. 配置屬性註解
@ConfigurationProperties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
| @ConfigurationProperties(prefix = "app")
@Component
@Validated
@Data
public class AppProperties {
@NotBlank
private String name;
@NotBlank
private String version;
@Valid
private Database database = new Database();
@Valid
private Cache cache = new Cache();
private List<String> allowedOrigins = new ArrayList<>();
private Map<String, String> headers = new HashMap<>();
@Data
public static class Database {
@NotBlank
private String url;
@NotBlank
private String username;
@NotBlank
private String password;
@Min(1)
@Max(100)
private int maxConnections = 10;
@DurationMin(seconds = 1)
@DurationMax(minutes = 5)
private Duration timeout = Duration.ofSeconds(30);
}
@Data
public static class Cache {
private boolean enabled = true;
@Positive
private int maxSize = 1000;
@DurationMin(minutes = 1)
private Duration ttl = Duration.ofMinutes(10);
private CacheType type = CacheType.MEMORY;
}
public enum CacheType {
MEMORY, REDIS, HAZELCAST
}
}
// 使用配置
@Service
@RequiredArgsConstructor
public class AppService {
private final AppProperties appProperties;
public void printConfig() {
System.out.println("App Name: " + appProperties.getName());
System.out.println("Database URL: " + appProperties.getDatabase().getUrl());
System.out.println("Cache Enabled: " + appProperties.getCache().isEnabled());
}
}
|
快取註解
@EnableCaching 和快取操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
| @Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setAllowNullValues(false);
return cacheManager;
}
}
@Service
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
// 快取結果
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
log.info("從資料庫載入使用者:{}", id);
return userRepository.findById(id).orElse(null);
}
// 條件快取
@Cacheable(value = "users", key = "#email", condition = "#email.length() > 5")
public User findByEmail(String email) {
return userRepository.findByEmail(email).orElse(null);
}
// 快取多個值
@Cacheable(value = "userProfiles", key = "#user.id", unless = "#result.isEmpty()")
public List<UserProfile> getUserProfiles(User user) {
return userProfileRepository.findByUserId(user.getId());
}
// 更新快取
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
log.info("更新使用者並刷新快取:{}", user.getId());
return userRepository.save(user);
}
// 清除快取
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
log.info("刪除使用者並清除快取:{}", id);
userRepository.deleteById(id);
}
// 清除所有快取
@CacheEvict(value = "users", allEntries = true)
public void clearUserCache() {
log.info("清除所有使用者快取");
}
// 組合快取操作
@Caching(
cacheable = @Cacheable(value = "users", key = "#id"),
evict = @CacheEvict(value = "userStats", key = "#id")
)
public User findAndUpdateStats(Long id) {
User user = userRepository.findById(id).orElse(null);
if (user != null) {
updateUserStats(user);
}
return user;
}
// 自訂快取鍵生成器
@Cacheable(value = "userSearch", keyGenerator = "customKeyGenerator")
public List<User> searchUsers(UserSearchCriteria criteria) {
return userRepository.findByCriteria(criteria);
}
}
@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + "_" + Arrays.toString(params);
}
}
|
測試註解
Spring Boot 測試註解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
| // 完整的 Spring Boot 測試
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:testdb",
"spring.jpa.hibernate.ddl-auto=create-drop"
})
class IntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserService userService;
@Test
void testCreateUser() {
CreateUserRequest request = new CreateUserRequest("John", "john@example.com");
ResponseEntity<User> response = restTemplate.postForEntity("/api/users", request, User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody().getName()).isEqualTo("John");
}
}
// Web 層測試
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testGetUser() throws Exception {
User user = new User(1L, "John", "john@example.com");
when(userService.findById(1L)).thenReturn(user);
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"))
.andExpect(jsonPath("$.email").value("john@example.com"));
}
}
// 資料層測試
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void testFindByEmail() {
// Given
User user = new User("John", "john@example.com");
entityManager.persistAndFlush(user);
// When
Optional<User> found = userRepository.findByEmail("john@example.com");
// Then
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("John");
}
}
// JSON 序列化測試
@JsonTest
class UserJsonTest {
@Autowired
private JacksonTester<User> json;
@Test
void testSerialize() throws Exception {
User user = new User(1L, "John", "john@example.com");
assertThat(json.write(user)).isEqualToJson("user.json");
assertThat(json.write(user)).hasJsonPathStringValue("@.name");
assertThat(json.write(user)).extractingJsonPathStringValue("@.name").isEqualTo("John");
}
@Test
void testDeserialize() throws Exception {
String content = """
{
"id": 1,
"name": "John",
"email": "john@example.com"
}
""";
assertThat(json.parse(content)).usingRecursiveComparison()
.isEqualTo(new User(1L, "John", "john@example.com"));
}
}
|
事件處理註解
@EventListener 和 @TransactionalEventListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
| // 事件定義
public class OrderCreatedEvent {
private final Order order;
private final LocalDateTime timestamp;
public OrderCreatedEvent(Order order) {
this.order = order;
this.timestamp = LocalDateTime.now();
}
// getters...
}
// 事件發布
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher eventPublisher;
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = new Order(request);
order = orderRepository.save(order);
// 發布事件
eventPublisher.publishEvent(new OrderCreatedEvent(order));
return order;
}
}
// 事件監聽
@Component
@Slf4j
public class OrderEventListener {
@Autowired
private EmailService emailService;
@Autowired
private InventoryService inventoryService;
// 基本事件監聽
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
log.info("訂單創建事件:{}", event.getOrder().getId());
}
// 條件事件監聽
@EventListener(condition = "#event.order.amount > 1000")
public void handleHighValueOrder(OrderCreatedEvent event) {
log.info("高價值訂單創建:{}", event.getOrder().getId());
// 特殊處理邏輯
}
// 非同步事件處理
@EventListener
@Async
public void sendOrderConfirmationEmail(OrderCreatedEvent event) {
log.info("發送訂單確認郵件:{}", event.getOrder().getId());
emailService.sendOrderConfirmation(event.getOrder());
}
// 交易事件監聽(在交易提交後執行)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void updateInventoryAfterOrderCreated(OrderCreatedEvent event) {
log.info("交易提交後更新庫存:{}", event.getOrder().getId());
inventoryService.updateStock(event.getOrder().getItems());
}
// 交易回滾時執行
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleOrderCreationRollback(OrderCreatedEvent event) {
log.warn("訂單創建回滾:{}", event.getOrder().getId());
// 回滾處理邏輯
}
// 監聽多種事件類型
@EventListener({OrderCreatedEvent.class, OrderUpdatedEvent.class})
public void handleOrderEvents(Object event) {
log.info("處理訂單事件:{}", event.getClass().getSimpleName());
}
}
|
總結
Spring Framework 的註解系統為開發者提供了強大且靈活的配置方式。透過合理使用這些註解,可以大幅簡化配置工作並提升開發效率。
最佳實踐建議
- 優先使用註解配置:相比 XML 配置,註解更直觀且易於維護
- 組合使用註解:善用
@SpringBootApplication 等組合註解 - 適當使用條件註解:根據環境動態配置 Bean
- 驗證配置屬性:使用
@Validated 確保配置正確性 - 合理使用快取:在適當的地方使用快取提升效能
- 測試覆蓋:使用專門的測試註解進行各層測試
- 事件驅動設計:使用事件機制降低組件耦合度
注意事項
- 註解過載:避免在單一類別或方法上使用過多註解
- 配置外部化:敏感配置應使用外部屬性檔案
- 效能考量:注意代理模式可能帶來的效能影響
- 測試友善:設計時考慮測試的便利性
掌握這些註解的正確使用方式,將能大幅提升 Spring 應用程式的開發效率和程式碼品質。
參考資料
留言討論