上周接了个电话面试,对面第一句就问:"Spring Boot 常用注解你平时怎么用的?"我当时脑子一抽,从 @Controller 开始背,背到 @Transactional 的时候对面已经沉默了。后来复盘才发现,注解这东西根本不该背,得按场景记。
我把平时项目里真正常用的注解整理了一下,按功能分了五类。每个注解都带实际代码,看完能直接用到项目里。
一、启动和配置类注解:项目跑起来的第一步
每个 Spring Boot 项目都有一个启动类,上面顶着那个经典的 @SpringBootApplication。这个注解其实是个三合一的组合:
@SpringBootApplication // 组合注解,包含下面三个功能
public class DemoApplication {
public static void main(String[] args) {
// SpringApplication.run 启动内嵌 Tomcat,加载自动配置
SpringApplication.run(DemoApplication.class, args);
}
}
它等价于 @Configuration + @EnableAutoConfiguration + @ComponentScan 三个注解叠在一起。大多数时候你不需要拆开写,除非有特殊需求。
比如你想排除某个包不被扫描,就得拆开用了:
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) // 排除数据源自动配置,用自定义的
@ComponentScan(basePackages = {"com.example.service", "com.example.controller"}) // 只扫描指定包
public class CustomConfig {
}
@Configuration 标记的类会被当作配置源,里面用 @Bean 定义的对象会注册到容器:
@Configuration
public class AppConfig {
@Bean // 把方法返回值注册为 Spring Bean,名字默认是方法名
public RestTemplate restTemplate() {
return new RestTemplate(); // 实际创建了一个 RestTemplate 实例
}
@Bean(initMethod = "init", destroyMethod = "cleanup") // 指定生命周期回调
public CacheManager cacheManager() {
return new CacheManager(); // 容器启动时调 init(),关闭时调 cleanup()
}
}
这里有个坑要注意:同一个配置类里,@Bean 方法之间互相调用不会创建多个实例,Spring 会做代理保证单例。
二、组件注册注解:四个兄弟别搞混
Spring 提供了四个语义不同的组件注解,底层都是 @Component 的特化:
@Component // 通用组件,没有明确语义,万能兜底
@Service // 业务逻辑层,标注在 Service 类上
@Repository // 数据访问层,标注在 DAO/Mapper 类上,额外提供数据库异常翻译
@Controller // Web 控制层,标注在 MVC 控制器上
实际项目中这样用:
@Service
public class UserService {
@Autowired // 按类型注入,容器里只有一个 UserMapper 时直接匹配
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.selectById(id); // 调用数据层查库
}
}
@Repository
public class UserMapper {
// MyBatis 或 JPA 的数据访问实现
}
@Autowired 默认按类型匹配。如果同类型有多个实现,配合 @Qualifier 指定名字:
@Autowired
@Qualifier("smsSender") // 指定注入名为 smsSender 的实现
private MessageSender sender;
现在更推荐构造器注入,字段注入在测试时不好 mock:
@Service
public class OrderService {
private final UserService userService;
private final PayService payService;
// 构造器注入,Spring 4.3+ 单构造器时可以省略 @Autowired
public OrderService(UserService userService, PayService payService) {
this.userService = userService;
this.payService = payService;
}
}
三、Web 请求注解:接口怎么接参数怎么绑
写 REST 接口最常用的组合是 @RestController + @RequestMapping。@RestController 等于 @Controller + @ResponseBody,方法返回值直接序列化成 JSON。
@RestController
@RequestMapping("/api/users") // 类级别的路径前缀
public class UserController {
@GetMapping("/{id}") // GET 请求,路径参数用 {} 占位
public User getUser(
@PathVariable Long id, // 从 URL 路径里取值,/api/users/123 → id=123
@RequestParam(defaultValue = "true") boolean active // 查询参数,带默认值
) {
return userService.getUserById(id);
}
@PostMapping // POST 请求,接收 JSON 请求体
public User createUser(@RequestBody UserDTO dto) { // @RequestBody 把请求体 JSON 反序列化成对象
return userService.create(dto);
}
@DeleteMapping("/{id}") // DELETE 请求
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}
@RequestParam 和 @PathVariable 的区别很多人搞不清。简单说:@PathVariable 取 URL 路径里的值,@RequestParam 取 ?key=value 里的值。
还有个容易忘的 @RequestHeader,用来取请求头:
@GetMapping("/info")
public String getInfo(
@RequestHeader("Authorization") String token, // 取请求头里的 Authorization
@CookieValue("session_id") String sessionId // 取 Cookie 里的 session_id
) {
return "token: " + token + ", session: " + sessionId;
}
四、配置绑定注解:yml 里的值怎么到代码里
@Value 是最简单的取值方式,适合取单个配置项:
@Component
public class SmsConfig {
@Value("${sms.api-key}") // 从 application.yml 里读 sms.api-key 的值
private String apiKey;
@Value("${sms.retry-count:3}") // 冒号后面是默认值,配置没写就用 3
private int retryCount;
@Value("${app.name:默认应用}") // 中文默认值也没问题
private String appName;
}
配置项一多,@Value 就散得到处都是。这时候用 @ConfigurationProperties 批量绑定更干净:
@Component
@ConfigurationProperties(prefix = "sms") // 把 sms 开头的所有配置批量绑到这个类上
public class SmsProperties {
private String apiKey; // 对应 sms.api-key(注意驼峰转短横线)
private int retryCount; // 对应 sms.retry-count
private List<String> channels; // 对应 sms.channels[0], sms.channels[1]...
// 必须提供 getter/setter,否则绑定不上
public String getApiKey() { return apiKey; }
public void setApiKey(String apiKey) { this.apiKey = apiKey; }
public int getRetryCount() { return retryCount; }
public void setRetryCount(int retryCount) { this.retryCount = retryCount; }
public List<String> getChannels() { return channels; }
public void setChannels(List<String> channels) { this.channels = channels; }
}
对应的 yml 配置:
sms:
api-key: abc123
retry-count: 5
channels:
- aliyun
- tencent
两种方式对比:@Value 适合零散取一两个值,@ConfigurationProperties 适合一组相关配置。后者还支持类型校验,加上 @Validated 就能在启动时检查配置是否合法。
五、常用功能注解速查
下面这张表把前面提到的注解做了个分类汇总,方便你快速定位:
| 分类 | 注解 | 作用 | 常见位置 |
|---|---|---|---|
| 启动配置 | @SpringBootApplication | 启动类标记,开启自动配置和包扫描 | 主类 |
| 启动配置 | @Configuration + @Bean | 手动注册 Bean 到容器 | 配置类 |
| 组件注册 | @Service / @Repository | 标记业务层 / 数据访问层组件 | 类级别 |
| 依赖注入 | @Autowired | 按类型自动注入依赖 | 构造器 / 字段 |
| 配置绑定 | @Value | 注入单个配置值 | 字段 / 方法参数 |
| 配置绑定 | @ConfigurationProperties | 批量绑定配置前缀到 POJO | 类级别 |
| Web 请求 | @GetMapping / @PostMapping | 映射 GET / POST 请求 | 方法级别 |
| 参数绑定 | @PathVariable / @RequestParam | 绑定路径参数 / 查询参数 | 方法参数 |
| 参数绑定 | @RequestBody | 请求体 JSON 反序列化为对象 | 方法参数 |
还有几个日常高频用到但容易忽略的:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
public Order create(@RequestBody @Valid OrderDTO dto) { // @Valid 触发 JSR-303 参数校验
return orderService.create(dto);
}
@GetMapping
@Transactional(readOnly = true) // 只读事务,查询场景性能更好
public List<Order> list(@RequestParam(required = false) String status) {
return orderService.list(status);
}
@ExceptionHandler(IllegalArgumentException.class) // 捕获特定异常,返回友好错误
public Map<String, String> handleBadReq(IllegalArgumentException e) {
return Map.of("error", e.getMessage());
}
}
@Valid 配合 DTO 上的 @NotBlank、@Size 等校验注解一起用,能在 Controller 层直接拦截非法参数,不用手写一堆 if 判断。
最后
整理下来就几条经验:
- 注解按场景分类记,别按字母表背。启动配置、组件注册、Web 请求、配置绑定,四个类就够了。
@Autowired优先用构造器注入,方便测试也方便排查。- 配置少用
@Value,多了就上@ConfigurationProperties,类型安全还能校验。 @RestController和@Controller的区别就是返回 JSON 还是返回视图,别搞混。@PathVariable取路径值,@RequestParam取问号后面的值,记住这个就行。
你平时项目里还有哪些注解用得特别多?评论区聊聊,说不定下次面试就考到了。