SpringBoot 的注解系统是其"约定优于配置"哲学的核心体现。这篇文章不讲虚的,只讲实际开发中真正会用到的注解。我会按照功能分类,每个注解都配上最简示例。

核心启动注解

@SpringBootApplication

这是 SpringBoot 应用的入口注解,一个组合注解,等价于三个注解的叠加:

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

它的三个组成部分:

  • @SpringBootConfiguration - 标记为配置类(本质是 @Configuration 的特化)
  • @EnableAutoConfiguration - 启用自动配置机制
  • @ComponentScan - 自动扫描当前包及子包下的组件

实用主义提示:别自己拆这三个注解,除非你知道自己在做什么。

Bean 定义注解

@Component

最基础的组件注解,告诉 Spring"这是个 Bean,帮我管理"。

1
2
3
4
5
6
@Component
public class EmailService {
public void send(String to, String message) {
// 发送邮件
}
}

它的三个特化版本(语义更清晰):

1
2
3
@Service        // 服务层
@Repository // 数据访问层,自带异常转换
@Controller // 控制层,配合视图解析器

好品味提示@Repository 不只是标记,它会自动将 JDBC 异常转换为 Spring 的 DataAccessException 层次结构。

@Bean

用在配置类的方法上,声明方法返回值为 Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

使用场景:当你需要控制 Bean 的创建逻辑,或者第三方库的类无法添加 @Component 时使用。

@Configuration

标记类为配置类,相当于 XML 时代的 applicationContext.xml

1
2
3
4
5
6
7
8
9
10
@Configuration
public class DatabaseConfig {

@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
return new HikariDataSource(config);
}
}

依赖注入注解

@Autowired

Spring 提供的自动注入注解,按类型注入。

1
2
3
4
5
6
7
8
9
@Service
public class UserService {

@Autowired
private UserRepository userRepository;

@Autowired
private EmailService emailService;
}

实用主义提示:字段注入虽然被某些"原教旨主义者"鄙视,但在实际开发中是最简洁的方案。构造器注入适合强制依赖,字段注入适合可选依赖。

@Qualifier

配合 @Autowired 使用,当有多个同类型 Bean 时指定具体使用哪个。

1
2
3
4
5
6
7
@Service
public class PaymentService {

@Autowired
@Qualifier("alipayStrategy")
private PaymentStrategy paymentStrategy;
}

@Resource

JSR-250 标准注解,按名称注入(默认)。

1
2
3
4
5
6
@Service
public class OrderService {

@Resource(name = "userRepository")
private UserRepository userRepository;
}

对比@Autowired 是 Spring 原生,@Resource 是 Java 标准。团队如果用 Spring 全家桶,用 @Autowired 没问题;如果要保持框架无关性,用 @Resource

Web 相关注解

@RestController

组合注解,@Controller + @ResponseBody,用于构建 REST API。

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/api/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping
public ResponseEntity<ApiResponse<List<UserDTO>>> getAll() {
List<UserDTO> users = userService.findAll();
return ResponseEntity.ok(new ApiResponse<>("SUCCESS", "获取成功", users));
}
}

@RequestMapping 及其变体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/api/users") // 类级别路径
public class UserController {

@GetMapping // GET /api/users
public List<User> list() { ... }

@GetMapping("/{id}") // GET /api/users/{id}
public User get(@PathVariable Long id) { ... }

@PostMapping // POST /api/users
public User create(@RequestBody UserCreateRequest req) { ... }

@PutMapping("/{id}") // PUT /api/users/{id}
public User update(@PathVariable Long id, @RequestBody UserUpdateRequest req) { ... }

@DeleteMapping("/{id}") // DELETE /api/users/{id}
public void delete(@PathVariable Long id) { ... }
}

@RequestBody/@ResponseBody

1
2
3
4
5
6
7
8
9
10
11
@PostMapping
public User create(@RequestBody UserCreateRequest request) {
// request 自动从 JSON 反序列化
return userService.create(request);
}

@GetMapping("/{id}")
@ResponseBody // @RestController 中可省略
public UserDTO get(@PathVariable Long id) {
return userService.findById(id);
}

@PathVariable/@RequestParam/@RequestHeader

1
2
3
4
5
6
7
8
9
@GetMapping("/{id}/posts")
public List<Post> getUserPosts(
@PathVariable Long id, // 路径参数
@RequestParam(defaultValue = "0") int page, // 查询参数 ?page=0
@RequestParam(required = false) String keyword, // 可选查询参数
@RequestHeader("Authorization") String token // 请求头
) {
return postRepository.findByUserId(id, page, keyword);
}

@RestControllerAdvice

全局异常处理和模型增强的利器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<?>> handleIllegalArgument(IllegalArgumentException ex) {
ApiResponse<?> response = new ApiResponse<>("ERROR", ex.getMessage(), null);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<?>> handleGeneric(Exception ex) {
ApiResponse<?> response = new ApiResponse<>("ERROR", "服务器内部错误", null);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

配置相关注解

@Value

注入配置文件中的值。

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class EmailService {

@Value("${email.smtp.host}")
private String smtpHost;

@Value("${email.smtp.port:587}") // 带默认值
private int smtpPort;

@Value("${app.feature.new-ui:false}") // 带默认值
private boolean enableNewUi;
}

@ConfigurationProperties

类型安全地绑定配置属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ConfigurationProperties(prefix = "app.email")
@Data
public class EmailProperties {
private String smtpHost;
private int smtpPort;
private String username;
private String password;
}

// 使用方式
@Service
public class EmailService {

private final EmailProperties properties;

public EmailService(EmailProperties properties) {
this.properties = properties;
}

public void send() {
// 使用 properties.getSmtpHost() 等
}
}

好品味提示:当配置项超过 3 个且有关联性时,用 @ConfigurationProperties 替代一堆 @Value,代码更清晰。

@PropertySource

指定额外的配置文件。

1
2
3
4
5
@Configuration
@PropertySource("classpath:custom.properties")
public class CustomConfig {
// ...
}

数据访问注解

@Entity

JPA 实体类标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String username;

@Column(nullable = false)
private String email;

@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
}

@Repository

数据访问层注解。

1
2
3
4
5
6
7
8
9
10
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByUsername(String username);

List<User> findByEmailContaining(String keyword);

@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);
}

@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
@Service
public class OrderService {

@Autowired
private OrderRepository orderRepository;

@Autowired
private InventoryRepository inventoryRepository;

@Transactional // 方法内所有数据库操作在同一事务中
public Order createOrder(OrderCreateRequest request) {
// 扣减库存
inventoryRepository.decrease(request.getProductId(), request.getQuantity());

// 创建订单
Order order = new Order();
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
return orderRepository.save(order);
}

@Transactional(readOnly = true) // 只读事务,性能优化
public List<Order> findAll() {
return orderRepository.findAll();
}
}

铁律提示@Transactional 只能用于 public 方法,自调用不会触发事务。

@EntityGraph

解决 N+1 查询问题。

1
2
3
4
5
6
7
8
9
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

@EntityGraph(attributePaths = {"user", "items"})
List<Order> findAll();

@EntityGraph(attributePaths = "items.product")
Optional<Order> findById(Long id);
}

条件注解

SpringBoot 自动配置的核心机制。

@ConditionalOnClass

当类路径存在指定类时生效。

1
2
3
4
5
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
// 只有存在 DataSource 类时才配置数据源
}

@ConditionalOnProperty

当配置属性满足条件时生效。

1
2
3
4
5
@Configuration
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public class CacheConfig {
// 只有 app.cache.enabled=true 时才启用缓存配置
}

@ConditionalOnBean / @ConditionalOnMissingBean

1
2
3
4
5
6
7
8
9
@Configuration
public class CustomConfig {

@Bean
@ConditionalOnMissingBean // 当容器中没有该类型的 Bean 时才创建
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

其他实用注解

@Profile

环境隔离。

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@Profile("dev")
public class DevConfig {
// 仅 dev 环境生效
}

@Service
@Profile("!prod") // 非生产环境
public class MockEmailService implements EmailService {
// 开发环境使用的模拟邮件服务
}

@Scheduled

定时任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class CleanupService {

@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点
public void cleanupTempFiles() {
// 清理临时文件
}

@Scheduled(fixedRate = 60000) // 每 60 秒
public void checkHealth() {
// 健康检查
}
}

@Async

异步执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class NotificationService {

@Async // 异步执行,不阻塞调用线程
public void sendNotification(User user, String message) {
// 发送邮件/短信
}
}

// 需要在配置类上添加 @EnableAsync
@Configuration
@EnableAsync
public class AsyncConfig {
// 异步配置
}

@Validated / @Valid

参数校验。

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
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {

@PostMapping
public User create(@Valid @RequestBody UserCreateRequest request) {
return userService.create(request);
}
}

// DTO 中的校验注解
public record UserCreateRequest(
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度 3-20 字符")
String username,

@NotBlank
@Email(message = "邮箱格式不正确")
String email,

@NotNull
@Min(value = 1, message = "年龄最小为 1")
Integer age
) {}

注解使用最佳实践

分层清晰

1
2
3
4
5
@RestController     → 处理 HTTP 请求/响应

@Service → 业务逻辑

@Repository → 数据访问

避免循环依赖

1
2
3
4
5
// 错误示例:循环依赖
@Service A { @Autowired B b; }
@Service B { @Autowired A a; }

// 解决方案:抽取共同逻辑到 C,A 和 B 都依赖 C

事务边界合理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 错误:事务范围过大
@Transactional
public void processBatch(List<Order> orders) {
for (Order order : orders) {
// 处理每个订单,可能调用外部 API
}
}

// 正确:事务范围最小化
public void processBatch(List<Order> orders) {
for (Order order : orders) {
processSingle(order); // 这个方法加 @Transactional
}
}

总结

SpringBoot 的注解系统看似繁杂,实则有其内在逻辑:

分类核心注解
启动@SpringBootApplication
Bean 定义@Component, @Service, @Repository, @Bean
依赖注入@Autowired, @Qualifier, @Resource
Web@RestController, @RequestMapping, @RequestBody
配置@Value, @ConfigurationProperties
数据@Entity, @Transactional, @EntityGraph
条件@ConditionalOnClass, @ConditionalOnProperty
其他@Profile, @Scheduled, @Async, @Validated

总结:注解是工具,不是银弹。理解每个注解背后的机制,比死记硬背更重要。代码是写给人看的,注解也是——让读代码的人一眼就能明白你的意图,这才是好注解的用法。