Spring Boot 常用注解速查表(30 个必会注解 + 实战案例)
一、前言
Spring Boot 开发中,注解无处不在。但注解太多,容易忘记怎么用?
本文整理了30 个最常用的注解,分为 7 大类,每个注解都配有:
- 作用说明
- 真实代码示例
- 使用场景
- 常见错误
总结了 Spring Boot 开发中常用的 30 个核心注解,涵盖核心、Web、Service、MyBatis-Plus、AOP 及 Lombok 六大类。内容包含各注解的作用说明、真实代码示例、使用场景及常见错误分析,旨在帮助开发者快速掌握依赖注入、请求映射、事务管理、数据持久化及切面编程等关键功能,提升开发效率并规范代码结构。
Spring Boot 开发中,注解无处不在。但注解太多,容易忘记怎么用?
本文整理了30 个最常用的注解,分为 7 大类,每个注解都配有:
@SpringBootApplication 是 Spring Boot 核心组合注解,整合 @Configuration、@EnableAutoConfiguration、@ComponentScan 三大核心注解的功能,用于标注 Spring Boot 项目的主启动类,是开启 Spring Boot 自动配置、组件扫描的核心入口,简化项目配置,一键启动 Spring Boot 应用。
@Configuration:将类标记为配置类,支持注解式配置;@EnableAutoConfiguration:开启自动配置,自动加载适配的框架配置;@ComponentScan:扫描主类所在包及子包的 @Component 系列注解(@Controller/@Service /@Mapper 等),将 Bean 纳入容器。实战代码:
package com.liu;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.liu.mapper") // 扫描 Mapper 接口
@SpringBootApplication // 核心注解
public class SimpleAccountingApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleAccountingApplication.class, args);
}
}
使用场景: 每个 Spring Boot 项目有且仅有一个启动类
常见错误:
// 错误:启动类放在子包里,扫描不到其他组件
package com.liu.config;
@SpringBootApplication
public class Application {}
// 正确:启动类放在根包
package com.liu;
@SpringBootApplication
public class Application {}
作用: @Component 是 Spring 框架的核心通用组件注解,用于标记普通 Java 类为Spring 容器可管理的 Bean,标注后类会被 Spring 扫描并实例化,纳入 IoC 容器统一管理,实现对象的依赖注入与生命周期管控,解耦组件间依赖。
它是 @ComponentScan 扫描的基础注解,@Controller、@Service、@Repository 等注解均基于它扩展,分别适配控制层、业务层、数据层等场景,@Component 则适用于无明确分层的通用组件,是 Spring 组件化开发的基础。
实战代码:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
public String generateToken(Long userId, Integer role, String username) {
// 生成 JWT Token
}
}
使用场景: 工具类、拦截器、切面等
衍生注解:
@Controller:控制器@Service:服务层@Repository:数据访问层@Configuration:配置类作用: @Autowired 是 Spring 核心的自动依赖注入注解,用于实现 Spring IoC 容器中 Bean 的自动装配,简化组件间依赖管理,无需手动创建对象实例,直接注入所需依赖 Bean,大幅降低代码耦合度。
其默认按类型(byType) 匹配容器中的 Bean 完成注入,支持标注在类的构造方法、字段、setter 方法上;若同类型 Bean 存在多个,可结合 @Qualifier 按名称精准匹配,或用 @Primary 指定默认注入 Bean。
实战代码:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper; // 自动注入 Mapper
@Autowired
private JwtUtil jwtUtil; // 自动注入工具类
public User login(String username, String password) {
return userMapper.selectByUsername(username);
}
}
使用场景: 注入 Service、Mapper、工具类等
推荐写法(构造器注入):
@Service
@RequiredArgsConstructor // Lombok 注解,自动生成构造器
public class UserServiceImpl implements UserService {
private final UserMapper userMapper; // final 修饰
private final JwtUtil jwtUtil; // Lombok 自动生成构造器,Spring 自动注入
}
为什么推荐构造器注入?
作用:注入配置文件中的值。@Value 是 Spring 框架的属性注入注解,核心用于将配置文件值、系统属性、常量值注入到 Spring 容器管理的 Bean 的字段 / 方法参数中,实现配置与代码解耦,无需硬编码配置信息,简化配置读取与使用。
支持直接注入常量(如 @Value("hello")),更常用的是通过 ${key} 读取 application.properties/yaml 中的配置项(如 @Value("${server.port}")),可标注在 Bean 的字段、构造方法参数、setter 方法上,适配 Spring 管理的所有组件(@Component/@Service/@Controller 等)。
实战代码:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret; // 注入配置:jwt.secret
@Value("${jwt.expiration}")
private Long expiration; // 注入配置:jwt.expiration
}
配置文件(application.yml):
jwt:
secret: my-secret-key-12345
expiration: 7200000 # 2 小时
使用场景: 注入配置参数(密钥、超时时间、文件路径等)
常见错误:
// 错误:配置文件中没有这个 key
@Value("${jwt.secretKey}")
private String secret;
// 正确:设置默认值
@Value("${jwt.secret:default-secret}")
private String secret;
作用: @Configuration 是 Spring 框架的核心配置类注解,用于标记 Java 类为Spring 配置类,替代传统 XML 配置文件,实现纯注解式配置开发。
标注该注解的类会被 Spring 解析,类中通过 @Bean 注解修饰的方法,其返回对象会被注册为 Spring IoC 容器中的 Bean,由容器统一管理实例化、依赖注入与生命周期,是 Spring 注解驱动开发的核心基础。
实战代码:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor; // 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor).addPathPatterns("/**");
}
// 配置跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "POST", "DELETE", "PUT");
}
}
使用场景: 配置拦截器、跨域、数据源、线程池等
作用: @RestController 是 Spring MVC 的组合注解,整合 @Controller和@ResponseBody 核心功能,专门用于标注RESTful 风格的控制器类,是 Spring Boot 开发接口的核心注解,无需额外配置即可实现 JSON/XML 等数据的返回。
@Controller:将类标记为 Spring MVC 控制器,接收前端请求;@ResponseBody:默认对类中所有 @RequestMapping 系列注解的方法生效,自动将方法返回值序列化为 JSON/XML 格式,直接写入响应体,替代传统视图跳转,适配前后端分离场景。对比单独使用 @Controller+ 方法级 @ResponseBody,该注解简化了 REST 接口的开发,无需在每个接口方法上重复标注 @ResponseBody。
等价于: @Controller + @ResponseBody
实战代码:
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public Result<String> login(@RequestParam String username, @RequestParam String password) {
// 返回 JSON:{"code":200,"msg":"登录成功","data":"token"}
return Result.success("登录成功", token);
}
}
使用场景: 前后端分离项目,返回 JSON 数据
对比:
| 注解 | 返回类型 | 使用场景 |
|---|---|---|
@Controller | 视图(HTML) | 传统 MVC 项目 |
@RestController | JSON 数据 | 前后端分离项目 |
作用: @RequestMapping 是 Spring MVC核心请求映射注解,用于将前端 HTTP 请求与后端控制器方法建立绑定关系,使 Spring 能精准匹配请求并调用对应方法处理,是开发 Web 接口 / 页面请求的基础注解,可标注在控制器类或方法上。
实战代码:
@RestController
@RequestMapping("/user") // 类级别:所有方法的 URL 前缀
public class UserController {
// 方法级别:完整 URL = /user/login
@RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST})
public Result<String> login() {
// 支持 GET 和 POST 请求
}
}
简化衍生注解(Spring MVC 推荐)
为简化 method 属性配置,Spring 提供了 @RequestMapping 的专用衍生注解,语义更清晰,开发中优先使用:
@GetMapping:等价于 @RequestMapping(method = RequestMethod.GET),处理 GET 查询请求;@PostMapping:等价于 @RequestMapping(method = RequestMethod.POST),处理 POST 新增 / 提交请求;@PutMapping:等价于 @RequestMapping(method = RequestMethod.PUT),处理 PUT 更新请求;@DeleteMapping:等价于 @RequestMapping(method = RequestMethod.DELETE),处理 DELETE 删除请求。@GetMapping("/list") // 等价于 @RequestMapping(method = RequestMethod.GET)
@PostMapping("/add") // 等价于 @RequestMapping(method = RequestMethod.POST)
@PutMapping("/update") // 等价于 @RequestMapping(method = RequestMethod.PUT)
@DeleteMapping("/delete") // 等价于 @RequestMapping(method = RequestMethod.DELETE)
作用: 获取 URL 参数(?key=value)
实战代码:
@PostMapping("/login")
public Result<String> login(@RequestParam String username, @RequestParam String password) {
// 请求:POST /user/login?username=admin&password=123456
// username = "admin"
// password = "123456"
}
可选参数:
@GetMapping("/list")
public Result<List<Bill>> list(
@RequestParam(required = false) String keyword, // 可选参数
@RequestParam(defaultValue = "1") Integer page // 默认值
) {
// 请求:GET /bill/list
// keyword = null
// page = 1
}
作用:
@RequestBody 是 Spring MVC 的核心请求体解析注解,专门用于接收前端通过 HTTP 请求体传递的非表单格式数据(如 JSON、XML),并自动将请求体中的数据反序列化为指定的 Java 实体类 / Map / 字符串对象,绑定到控制器方法的参数上,是前后端分离场景中接收 JSON 请求的必备注解。
实战代码:
@PostMapping("/register")
public Result<User> register(@RequestBody User user) {
// 请求体:{"username":"admin","password":"123456"}
// 自动转换为 User 对象
// user.getUsername() = "admin"
// user.getPassword() = "123456"
}
使用场景: POST/PUT 请求,提交 JSON 数据
常见错误:
// 错误:GET 请求不能用@RequestBody
@GetMapping("/list")
public Result<List<Bill>> list(@RequestBody Map<String,Object> params) {
// GET 请求没有请求体,会报错
}
// 正确:GET 请求用@RequestParam
@GetMapping("/list")
public Result<List<Bill>> list(@RequestParam String keyword) {
// 正确
}
作用:
@PathVariable 是 Spring MVC 的路径参数绑定注解,核心用于提取 URL 路径中的动态占位符参数,并自动将其转换为指定类型后绑定到控制器方法的入参上,是开发RESTful 风格动态接口的核心注解(如 /user/123、/order/456/detail这类含动态 ID 的请求)。
实战代码:
@DeleteMapping("/delete/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
// 请求:DELETE /bill/delete/123
// id = 123
billService.deleteById(id);
return Result.success("删除成功");
}
@GetMapping("/user/{userId}/bill/{billId}")
public Result<Bill> getBill(@PathVariable Long userId, @PathVariable Long billId) {
// 请求:GET /user/10/bill/20
// userId = 10
// billId = 20
}
使用场景: RESTful 风格的 URL
作用: @ResponseBody 是 Spring MVC 的核心响应体处理注解,核心作用是将控制器方法的返回值,直接序列化为 JSON/XML 等格式的数据流,写入 HTTP 响应体(Response Body),替代传统的视图跳转(如跳转到 JSP/HTML 页面),是实现前后端分离接口和RESTful 风格接口的关键注解。
核心使用特性
@RestController是@Controller + @ResponseBody的组合注解,标注 @RestController 的类,底层已默认添加类级别的 @ResponseBody,无需重复标注。实战代码:
@Controller // 注意:不是@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/toLogin")
public String toLogin() {
return "redirect:/login.html"; // 返回视图
}
@ResponseBody // 单独使用,返回 JSON
@PostMapping("/login")
public Result<String> login() {
return Result.success("登录成功"); // 返回 JSON
}
}
使用场景: 混合使用视图和 JSON 返回时
作用: @Valid 和 @Validated 均是 Spring 中用于开启参数校验的核心注解,基于 JSR-380(Bean Validation 2.0)规范实现,配合 @NotNull/@NotBlank/@Min 等校验注解,可自动完成对实体类参数的合法性校验,替代手动 if 判断,简化参数校验逻辑,是开发接口时的必备注解。
二者核心作用一致,但所属规范、功能特性、适用场景存在明确区别,其中 @Validated 是 Spring 对 @Valid 的增强扩展版,开发中可根据需求选择。
实战代码:
// 实体类(User.java)
@Data
public class User {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在 3-20 个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 10, message = "密码长度必须在 6-10 个字符之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$", message = "密码必须包含大小写字母和数字")
private String password;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
// 控制器
@PostMapping("/register")
public Result<User> register(@RequestBody @Valid User user) {
// 如果校验失败,自动返回 400 错误,不会执行方法体
// 如果校验成功,继续执行
return userService.register(user);
}
常用校验注解:
| 注解 | 作用 | 示例 |
|---|---|---|
@NotNull | 不能为 null | @NotNull(message = "ID 不能为空") |
@NotBlank | 不能为空字符串 | @NotBlank(message = "用户名不能为空") |
@Size | 长度限制 | @Size(min = 3, max = 20) |
@Pattern | 正则验证 | @Pattern(regexp = "^1[3-9]\\d{9}$") |
@Min / @Max | 数值范围 | @Min(value = 0, message = "金额不能为负") |
@Email | 邮箱格式 | @Email(message = "邮箱格式不正确") |
作用: @ExceptionHandler 是 Spring MVC 提供的异常处理核心注解,核心作用是捕获并处理 Spring 容器中指定类型的异常,可标注在控制器(@Controller/@RestController)或全局异常处理器的方法上,实现异常的局部 / 全局统一处理,替代传统的 try-catch 代码块,简化异常处理逻辑,让接口返回统一、友好的错误响应。
核心核心特性
@ExceptionHandler(NullPointerException.class)),仅捕获匹配的异常;实战代码:
@RestControllerAdvice // 全局异常处理
public class GlobalExceptionHandler {
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.fail(message); // 返回:{"code":400,"msg":"用户名不能为空"}
}
// 处理业务异常
@ExceptionHandler(RuntimeException.class)
public Result<String> handleRuntimeException(RuntimeException e) {
return Result.fail(e.getMessage());
}
// 处理数据库唯一约束异常
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public Result<String> handleSQLException(SQLIntegrityConstraintViolationException e) {
if (e.getMessage().contains("Duplicate entry")) {
return Result.fail("数据已存在");
}
return Result.fail("数据库操作失败");
}
}
使用场景: 统一处理异常,避免在每个方法中 try-catch
类上标注的是全局异常处理注解:@RestControllerAdvice 是 Spring MVC 提供的全局异常处理 + 数据绑定 + 返回值增强的核心组合注解,是 @ControllerAdvice + @ResponseBody 的整合版,专门适配前后端分离项目,核心作用是实现全局统一的异常处理、全局数据绑定、全局返回值封装,无需在每个控制器重复编写逻辑,让接口返回格式统一、异常处理更简洁。
作用: @Service 是 Spring 框架中业务层组件专用注解,属于 @Component 的分层扩展注解,核心作用是标记普通 Java 类为Spring IoC 容器可管理的业务层 Bean,让类被 @ComponentScan 自动扫描并纳入容器统一管理,是 Spring 分层开发(控制层 / 业务层 / 数据层)的核心标识之一。
核心核心特性:
@Component,让代码分层更清晰、可读性更强;@Component 一致,Spring 启动时通过组件扫描,自动将标注该注解的类实例化并注册到 IoC 容器,无需手动配置 Bean;@Service 组件,可通过 @Autowired/@Resource 等注解,自动注入依赖的其他 Bean(如 @Repository 数据层组件);@Transactional 注解,可直接在 @Service 方法上标注,实现业务层的事务控制(核心适用场景)。实战代码:
@Service
@RequiredArgsConstructor // Lombok 注解,自动生成构造器,构造器注入,避免字段注入
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Override
public User login(String username, String password) {
// 业务逻辑
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new RuntimeException("用户不存在");
}
if (!user.getPassword().equals(password)) {
throw new RuntimeException("密码错误");
}
return user;
}
}
使用场景: 业务逻辑层
作用: @Transactional 是 Spring 框架实现声明式事务管理的核心注解,基于 AOP(面向切面编程)实现,无需手动编写 try-catch 事务提交 / 回滚代码,仅通过注解标注即可为方法 / 类开启事务控制,保证一组数据库操作的原子性(要么全部成功提交,要么任意一步失败则整体回滚),是解决数据库事务问题的标准方案。
核心注解属性(常用配置)
@Transactional 提供多个属性用于自定义事务行为,开发中可根据业务需求配置,核心常用属性如下:
| 属性名 | 作用 | 可选值示例、默认值 |
|---|---|---|
readOnly | 设置事务是否为只读(适用于仅查询操作,提升数据库性能) | true/false(默认) |
isolation | 设置事务隔离级别(解决并发事务的脏读 / 不可重复读 / 幻读问题) | READ_UNCOMMITTED/READ_COMMITTED/REPEATABLE_READ/SERIALIZABLE 数据库默认隔离级别(如 MySQL 为 REPEATABLE_READ) |
propagation | 设置事务传播行为(解决方法嵌套调用时,事务的创建 / 复用规则) | REQUIRED(最常用)/REQUIRES_NEW/SUPPORTS 等 |
rollbackFor | 指定触发回滚的异常类型(可包含 checked 异常) | Exception.class/BusinessException.class 默认:RuntimeException.class |
noRollbackFor | 指定不触发回滚的异常类型 | IllegalArgumentException.class 默认:无 |
timeout | 设置事务超时时间(单位:秒),超时则自动回滚 | 3/5 默认:-1(无超时限制) |
实战代码:
@Service
public class AnnouncementServiceImpl implements AnnouncementService {
@Transactional // 开启事务
public void publishAnnouncement(Announcement announcement) {
// 1. 保存公告
announcementMapper.insert(announcement);
// 2. 记录操作日志
logMapper.insert(new Log("发布公告", announcement.getId()));
// 如果第 2 步失败,第 1 步会自动回滚
}
}
使用场景: 多个数据库操作需要保证原子性
常见错误:
// 错误:@Transactional 只对 public 方法有效
@Transactional
private void saveUser(User user) {
// 事务不生效
}
// 正确:必须是 public 方法
@Transactional
public void saveUser(User user) {
// 事务生效
}
作用: @TableName 是 MyBatis-Plus(MP)框架的核心表映射注解,专门用于解决Java 实体类与数据库表之间的名称映射问题,通过注解直接指定实体类对应的数据库表名,替代传统 MyBatis 中手动编写 SQL 映射或 XML 配置表名的操作,简化 MP 的单表 CRUD 开发,是 MP 实现零 SQL 单表操作的基础注解之一。
核心设计背景
MyBatis-Plus 有默认表名映射规则:实体类名(驼峰命名)自动转换为数据库表名(下划线命名)(如 Java 实体 UserInfo → 数据库表 user_info)。
当实体类名与数据库表名不一致 / 不满足驼峰转下划线规则时(如实体 User 对应表 t_user、实体 Order 对应表 order_info),默认规则失效,需通过 @TableName 手动指定映射关系,否则 MP 执行 CRUD 时会因表名不存在抛出 SQL 异常。
实战代码:
@Data
@TableName("user") // 对应数据库的 user 表
public class User {
private Long userId;
private String username;
}
使用场景: 实体类名和表名不一致时
作用: @TableId 是 MyBatis-Plus(MP)框架的专属主键映射注解,核心作用是将Java 实体类的属性与数据库表的主键字段建立绑定关系,同时指定主键的生成策略(如自增、雪花算法、UUID 等),是 MP 实现单表 CRUD 时主键自动处理的核心注解,替代传统 MyBatis 手动配置主键映射和生成逻辑的操作。
核心设计背景
MyBatis-Plus 默认会将实体类中名为 id 的属性映射为数据库表的主键字段,且默认使用雪花算法(IdType.ASSIGN_ID) 生成主键值。
当实体主键属性名与表主键字段名不一致(如实体 userId 对应表 user_id)、需要自定义主键生成策略(如数据库自增、UUID)时,默认规则失效,需通过 @TableId 显式配置,否则 MP 执行新增 / 查询等操作时会因主键映射错误 / 生成规则不符抛出异常。
实战代码:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO) // 主键自增
private Long userId;
}
主键策略:
| 策略 | 说明 |
|---|---|
IdType.AUTO | 数据库自增 |
IdType.ASSIGN_ID | 雪花算法生成 ID(默认) |
IdType.INPUT | 手动输入 |
作用: @TableField 是 MyBatis-Plus(MP)框架的普通字段专属映射注解,核心作用是将Java 实体类的非主键属性与数据库表的普通字段建立绑定关系,同时支持配置字段是否参与 CRUD、是否为查询条件、字段填充规则等特性,是 MP 解决非主键字段映射不一致、实现字段精细化控制的核心注解,替代传统 MyBatis 手动在 XML/SQL 中配置字段映射的操作。
核心设计背景
MyBatis-Plus 有默认普通字段映射规则:实体类属性名(驼峰命名)自动转换为数据库表字段名(下划线命名)(如实体 userName → 表 user_name)。
当实体属性名与表字段名不一致 / 不满足驼峰转下划线规则、需要排除某些字段不参与 MP 的自动 CRUD、需要配置字段自动填充(如创建时间、更新时间)时,默认规则无法满足需求,需通过 @TableField 显式配置,否则 MP 执行操作时会因字段映射错误抛出 SQL 异常,或无法实现字段的精细化控制。
实战代码:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long userId;
// 自动填充:插入时填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 自动填充:插入和更新时都填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
自动填充处理器:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
作用: @Version 是 MyBatis-Plus(MP)框架实现乐观锁机制的专属核心注解,核心作用是将Java 实体类的属性与数据库表的版本数字段绑定,让 MP 自动基于该字段实现乐观锁的版本控制,解决多线程 / 多用户并发更新数据时的脏写问题(避免多个请求同时修改同一条数据,导致数据覆盖、一致性丢失),是 MP 简化乐观锁开发的关键注解。
实战代码:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long userId;
@Version // 乐观锁
private Integer version;
}
工作原理:
-- 更新时自动加上 version 条件
UPDATE user SET username = ?, version = version + 1 WHERE user_id = ? AND version = ?
-- 如果 version 不匹配(被其他线程修改了),更新失败
使用场景: 防止并发修改冲突
作用: @TableLogic 是 MyBatis-Plus(MP)框架实现逻辑删除的专属核心注解,核心作用是将 Java 实体类的属性与数据库表的逻辑删除标识字段绑定,让 MP 自动将单表 CRUD 中的物理删除(DELETE)操作替换为逻辑删除(UPDATE)操作,同时自动过滤查询 / 更新结果中已逻辑删除的数据,实现数据的「假删除、软删除」,避免真实删除数据导致的历史数据丢失、关联查询异常问题,是 MP 简化逻辑删除开发的关键注解。
实战代码:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long userId;
@TableLogic // 逻辑删除:0=正常,1=已删除
private Integer deleted;
}
效果:
-- 调用删除方法 userMapper.deleteById(1)
-- 实际执行的 SQL(不是真删除)
UPDATE user SET deleted = 1 WHERE user_id = 1 AND deleted = 0
-- 查询时自动过滤已删除的数据
SELECT * FROM user WHERE deleted = 0
作用: @MapperScan 是 MyBatis 及 MyBatis-Plus 框架的核心 Mapper 接口扫描注解,核心作用是指定项目中 Mapper 接口的扫描包路径,让框架自动扫描该包下所有的 Mapper 接口,并为每个接口动态生成代理实现类,最终将这些代理类注册到 Spring IoC 容器中,实现 Mapper 接口的依赖注入和数据库操作调用,是整合 MyBatis/MP 与 SpringBoot 的关键注解。
实战代码:
@MapperScan("com.liu.mapper") // 扫描 mapper 包下的所有接口
@SpringBootApplication
public class SimpleAccountingApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleAccountingApplication.class, args);
}
}
使用场景: 启动类上,扫描所有 Mapper 接口
作用: @Select、@Insert、@Update、@Delete 是 MyBatis/MyBatis-Plus(MP)框架的核心原生 SQL 注解,属于 MyBatis 基础注解(MP 完全兼容),核心作用是直接在 Mapper 接口的方法上标注对应的 SQL 语句,让框架将接口方法与 SQL 语句直接绑定,调用接口方法时自动执行标注的 SQL,实现数据库的查询、新增、更新、删除操作。
这组注解替代了传统 MyBatis 中XML 映射文件的编写(如 UserMapper.xml 中的 <select>/<insert>标签),实现「SQL 与 Mapper 接口方法一体化」,简化单表 / 多表的 SQL 开发,是 MyBatis/MP 中除了 MP 自动 CRUD 外,自定义 SQL 的核心实现方式。
实战代码:
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM user WHERE username = #{username}")
User selectByUsername(String username);
@Update("UPDATE user SET status = #{status} WHERE user_id = #{userId}")
int updateStatus(@Param("userId") Long userId, @Param("status") Integer status);
}
1. 参数绑定规范
#{参数名}:实现预编译参数绑定,MyBatis 会自动处理参数类型转换,有效防止 SQL 注入,是开发中的标准用法;${参数名}:直接将参数拼接到 SQL 中,无预编译处理,有 SQL 注入风险,仅适用于表名、字段名动态拼接(如 SELECT * FROM ${tableName});多参数绑定:若方法有多个独立入参,可通过 @Param("参数名") 指定参数名,保证#{}中名称匹配:
// 多参数需标注@Param
@Select("SELECT * FROM t_user WHERE username = #{name} AND age = #{age}")
User selectByUsernameAndAge(@Param("name") String username, @Param("age") Integer age);
使用场景: 简单 SQL 可以用注解,复杂 SQL 建议用 XML
作用: @Aspect 是 Spring 框架实现 AOP(面向切面编程)的核心注解,核心作用是标记一个普通 Java 类为「切面类(Aspect)」,让 Spring 识别该类为 AOP 的核心载体 —— 用于封装切入点(Pointcut) 和通知(Advice) 逻辑,实现业务代码与非业务横切逻辑(如日志记录、事务控制、权限校验、性能监控)的解耦,是 Spring AOP 开发的基础注解。
实战代码:
@Aspect
@Component
@Slf4j
public class RoleCheckAspect {
@Before("@annotation(requireRole)") // 在切点之前运行的代码
public void checkRole(JoinPoint joinPoint, RequireRole requireRole) {
// 权限验证逻辑
int[] allowedRoles = requireRole.value();
int userRole = TokenHelper.getRole(request);
boolean hasPermission = Arrays.stream(allowedRoles).anyMatch(role -> role == userRole);
if (!hasPermission) {
throw new RuntimeException("无权限访问");
}
}
}
@Aspect 关键使用注意事项(避坑重点)
@Aspect仅标记该类为切面,不会自动将其注册到 Spring IoC 容器,必须配合 @Component(或 @Service/@Repository),让 Spring 扫描并管理该 Bean,否则 AOP 逻辑完全失效。
@Around是唯一能控制目标方法执行的通知,必须遵守 3 个规则,否则会导致业务异常:
joinPoint.proceed():执行目标业务方法,省略则目标方法不会执行;proceed() 的结果:否则目标方法的返回值会丢失(前端获取不到数据);execution(* *..*.*(..))),会匹配所有方法,导致性能损耗;Spring AOP 默认基于动态代理实现,存在 2 个核心限制,会导致切面逻辑不生效:
非 Spring 管理的类(未标注 @Component/@Service 等),其方法无法被切面增强,解决方案:将类注册为 Spring Bean。
private/protected/default 访问权限的方法,Spring AOP 不会生成代理,切面逻辑无法织入,解决方案:将方法改为 public(若需增强非公共方法,需使用 AspectJ 原生织入,而非 Spring AOP)。
同一类中,无切面增强的方法调用有切面增强的方法,因未经过 Spring 代理对象,切面逻辑无法织入,这是 Spring AOP 最常见的坑:
@Service
public class UserService {
// 无切面增强的方法
public void test() {
this.saveUser(); // 内部自调用,saveUser 的切面逻辑失效
}
// 有切面增强的方法(如@Transactional/@Around)
public void saveUser() {
// 业务逻辑
}
}
解决方案:
AopContext.currentProxy() 获取代理对象调用。使用场景: 权限验证、日志记录、性能监控
作用: @Before、@After、@Around 是 Spring AOP 中最核心的三大通知注解,与 @Aspect 切面类配合使用,核心作用是定义横切逻辑的织入时机—— 将日志记录、性能监控、权限校验等通用非业务逻辑,精准织入到目标方法的执行前、执行后、执行前后全生命周期,实现业务代码与横切逻辑的解耦,是 Spring AOP 实现「无侵入式功能增强」的核心载体。
三者均标注在切面类(@Aspect + @Component 标注)的方法上,需绑定切入点(@Pointcut) 指定增强的目标方法,共同覆盖目标方法的核心执行节点,其中 @Around 功能最全面,可实现前两者的所有效果。
三大注解核心定义与核心作用:
- @Before:前置通知注解
核心定义:标注在切面类方法上,指定横切逻辑在目标方法执行前执行,是最基础的通知类型。
核心作用:用于实现目标方法执行前的预处理逻辑,无返回值,无法修改目标方法的入参,也无法控制目标方法是否执行。
核心适用场景:权限校验、接口入参日志记录、资源初始化(如数据库连接准备)、请求参数预处理等。
- @After:最终通知注解
核心定义:标注在切面类方法上,指定横切逻辑在目标方法执行结束后执行(无论目标方法是否抛出异常),是「必执行」的通知类型。
核心作用:用于实现目标方法执行后的收尾逻辑,无返回值,无法获取目标方法返回值或异常信息,仅做最终兜底处理。
核心适用场景:资源统一释放(如关闭数据库连接、清空临时缓存、关闭文件流)、方法执行完成的通用日志标记等。
- @Around:环绕通知注解
核心定义:标注在切面类方法上,指定横切逻辑包裹目标方法的全执行生命周期(执行前 + 执行中 + 执行后 + 异常时),是功能最强大、最灵活的通知类型。
核心作用:可实现目标方法的全流程控制:能修改入参、手动控制目标方法是否执行、获取 / 修改返回值、捕获处理异常,完全覆盖 @Before 和 @After 的能力。
核心适用场景:性能监控(统计方法执行耗时)、全流程日志记录(入参 + 执行结果 + 耗时)、核心接口的权限控制、方法返回值缓存等。
核心使用差异
| 注解 | 织入时机 | 核心能力 | 方法入参 | 关键特性 | 核心限制 |
|---|---|---|---|---|---|
| @Before | 目标方法执行前 | 仅做前置预处理,无返回值 | JoinPoint(获取方法信息) | 执行时机固定,逻辑简单 | 无法修改入参、无法控制目标方法执行、无返回值 |
| @After | 目标方法执行结束后(必执行) | 仅做最终收尾处理,无返回值 | JoinPoint(获取方法信息) | 无论异常与否都会执行 | 无法获取返回值、无法捕获异常、无返回值 |
| @Around | 目标方法执行前后全生命周期 | 修改入参、控制执行、获取 / 修改返回值、捕获异常,覆盖前两者所有能力 | ProceedingJoinPoint(含执行方法) | 功能最全面,灵活性最高 | 必须调用 proceed() 执行目标方法,必须返回结果 |
关键入参说明
@Before/@After 可直接使用;JoinPoint,新增 proceed() 方法 ——手动执行目标业务方法,是 @Around 的专属入参,无此方法则无法触发目标方法执行。完整使用示例(同一切面,对比实现)
以业务层方法(@Service) 为增强目标,实现「日志记录 + 性能监控」功能,分别演示三者的使用方式,统一绑定同一个切入点,便于对比差异。
环境前提
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 正常方法:返回结果
public String getUserById(Long id) throws InterruptedException {
Thread.sleep(80); // 模拟业务执行耗时
return "查询到用户 ID:" + id;
}
// 异常方法:抛出运行时异常
public void deleteUserById(Long id) {
throw new RuntimeException("模拟删除失败:用户不存在");
}
}
切面类实现(@Aspect + @Component)
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// 1. 标记为切面类 2. 注册为 Spring Bean(必须,否则 AOP 失效)
@Aspect
@Component
public class AopAdviceDemoAspect {
private static final Logger log = LoggerFactory.getLogger(AopAdviceDemoAspect.class);
// 定义公共切入点:匹配 UserService 类的所有公共方法(精准增强目标)
@Pointcut("execution(public * com.example.demo.service.UserService.*(..))")
public void userServicePointcut() {
// 切入点方法:仅做标识,无需编写任何逻辑
}
// ==================== 1. @Before 前置通知 ====================
@Before("userServicePointcut()")
public void doBefore(JoinPoint joinPoint) {
// 获取方法信息:类名。方法名
String methodFullName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
// 获取方法入参
Object[] args = joinPoint.getArgs();
// 前置逻辑:记录方法开始执行 + 入参
log.info("【@Before 前置通知】方法{}开始执行,入参:{}", methodFullName, Arrays.toString(args));
}
// ==================== 2. @After 最终通知 ====================
@After("userServicePointcut()")
public void doAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
// 最终逻辑:标记方法执行结束(无论是否异常,都会打印)
log.info("【@After 最终通知】方法{}执行结束(必执行)", methodName);
}
// ==================== 3. @Around 环绕通知 ====================
@Around("userServicePointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// ① 执行前逻辑:记录开始时间 + 获取方法信息(等效@Before)
long startTime = System.currentTimeMillis();
String methodFullName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("【@Around 执行前】方法{}入参:{}", methodFullName, Arrays.toString(args));
Object result = null;
try {
// ② 手动执行目标方法(核心:必须调用,否则目标方法不执行)
result = joinPoint.proceed();
// ③ 执行成功后逻辑:记录返回值 + 耗时
long costTime = System.currentTimeMillis() - startTime;
log.info("【@Around 执行成功】方法{}返回值:{},耗时:{}ms", methodFullName, result, costTime);
} catch (Throwable e) {
// ④ 执行异常时逻辑:记录异常信息 + 耗时
long costTime = System.currentTimeMillis() - startTime;
log.error("【@Around 执行异常】方法{}执行失败,耗时:{}ms,异常:{}", methodFullName, costTime, e.getMessage());
throw e; // 重新抛出异常,让全局异常处理器处理(不可省略)
}
// ⑤ 返回目标方法结果(核心:必须返回,否则调用方获取不到返回值)
return result;
}
}
实战代码:
@Aspect
@Component
public class LogAspect {
// 前置通知:方法执行前
@Before("execution(* com.liu.controller.*.*(..))")
public void before(JoinPoint joinPoint) {
log.info("方法执行前:{}", joinPoint.getSignature().getName());
}
// 后置通知:方法执行后
@After("execution(* com.liu.controller.*.*(..))")
public void after(JoinPoint joinPoint) {
log.info("方法执行后:{}", joinPoint.getSignature().getName());
}
// 环绕通知:方法执行前后
@Around("execution(* com.liu.controller.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行方法
long end = System.currentTimeMillis();
log.info("方法执行耗时:{}ms", end - start);
return result;
}
}
作用:自定义注解是 Java 提供的元编程能力,允许开发者根据业务需求定义专属的注解类型,结合 Spring 框架的 AOP、反射等特性,可实现无侵入式的业务增强(如自定义权限校验、操作日志、接口限流等),是企业级开发中实现通用逻辑复用、简化业务代码的核心手段。
自定义注解的核心价值是将通用逻辑与业务代码解耦:通过注解标记需要增强的类 / 方法 / 属性,再通过注解解析器(反射 / AOP)识别注解并执行对应的横切逻辑,无需修改业务代码即可实现功能增强。
实战代码:
// 1. 定义注解
@Target(ElementType.METHOD) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
public @interface RequireRole {
int[] value() default {}; // 允许的角色
}
// 2. 使用注解
@RestController
@RequestMapping("/admin")
public class AdminController {
@RequireRole({Constants.ROLE_ADMIN}) // 只允许管理员访问
@GetMapping("/users")
public Result<List<User>> getAllUsers() {
return Result.success(userService.list());
}
}
// 3. 切面处理注解
@Aspect
@Component
public class RoleCheckAspect {
@Before("@annotation(requireRole)")
public void checkRole(JoinPoint joinPoint, RequireRole requireRole) {
// 验证权限
}
}
使用场景: 权限验证、日志记录、缓存控制
首先先引入依赖:
<!-- Maven 依赖(与其他 Lombok 注解共用) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
作用: @Data 是 Lombok 框架的核心注解,核心作用是自动为 Java 类生成通用的模板代码,包括所有成员变量的 getter/setter 方法、toString()、equals()、hashCode() 方法,以及全参 / 无参构造器的核心部分,彻底消除实体类、POJO 类中大量重复的模板代码,大幅简化类的编写,提升开发效率并减少代码维护成本。
实战代码:
@Data
public class User {
private Long userId;
private String username;
private String password;
}
作用: @NoArgsConstructor 和 @AllArgsConstructor 是 Lombok 框架的核心基础注解,二者配合使用可分别为 Java 类自动生成无参构造器和全参构造器,彻底消除手动编写构造器的模板代码,简化对象实例化操作,常与 @Data 组合成为实体类、POJO 类的标配注解。
构造器是 Java 类实例化对象的核心入口,手动编写多参构造器不仅繁琐,还会在新增 / 删除字段时需同步修改,而这两个注解通过编译期字节码增强自动生成构造器,开发时仅需注解标注,编译后字节码中会包含对应构造器,与手动编写完全一致且运行时无性能损耗。
实战代码:
@Data
@NoArgsConstructor // 无参构造器
@AllArgsConstructor // 全参构造器
public class User {
private Long userId;
private String username;
}
作用: @RequiredArgsConstructor 是 Lombok 框架的核心构造器注解,核心作用是为 Java 类自动生成包含「必须初始化成员变量」的构造器,这里的「必须初始化变量」特指被 final 修饰的成员变量和被 @NonNull 注解标注的成员变量(这两类变量在 Java 语法中要求必须初始化,否则编译报错)。该注解精准解决了「仅为关键变量生成带参构造器」的需求,避免全参构造器参数冗余,常与 @Data、@NoArgsConstructor 等注解组合使用,是实体类、配置类、服务类开发中的常用注解。
实战代码:
@Service
@RequiredArgsConstructor // 自动生成构造器
public class UserServiceImpl implements UserService {
private final UserMapper userMapper; // final 修饰,依赖注入
private final JwtUtil jwtUtil; // Lombok 自动生成:
// public UserServiceImpl(UserMapper userMapper, JwtUtil jwtUtil) {
// this.userMapper = userMapper;
// this.jwtUtil = jwtUtil;
// }
}
为什么推荐?
作用: @Slf4j 是 Lombok 框架的核心日志注解,核心作用是为 Java 类自动生成基于 SLF4J 规范的日志对象(private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(当前类.class);),彻底消除手动声明日志对象的模板代码,直接通过 log.info()/log.error() 等方法打印日志,是企业级 Java 开发中日志记录的标配注解,与 @Data、@Service、@RestController 等注解无缝配合使用。
实战代码:
@Service
@Slf4j // 自动生成 log 对象
public class UserServiceImpl implements UserService {
public User login(String username, String password) {
log.info("用户登录:{}", username); // 直接使用 log
log.error("登录失败:{}", username);
log.debug("调试信息:{}", username);
}
}
作用: @Builder 是 Lombok 框架的核心创建型模式注解,核心作用是为 Java 类自动实现建造者(Builder)设计模式,通过链式调用的方式灵活创建对象,替代传统的无参构造 + 多次 setter、多参构造器的对象实例化方式,解决多字段对象创建时代码繁琐、参数顺序易混淆、可选参数处理麻烦的问题,是企业级开发中创建复杂对象的标配注解,常与 @Data、@NoArgsConstructor、@AllArgsConstructor 组合使用。
实战代码:
@Data
@Builder
public class User {
private Long userId;
private String username;
private String password;
}
使用场景: 构建复杂对象
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final UserService userService;
@PostMapping("/login")
public Result<String> login(@RequestParam String username, @RequestParam String password) {
log.info("用户登录:{}", username);
return userService.login(username, password);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long userId;
@NotBlank(message = "用户名不能为空")
private String username;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Version
private Integer version;
@TableLogic
private Integer deleted;
}
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Transactional
public void saveUser(User user) {
log.info("保存用户:{}", user.getUsername());
userMapper.insert(user);
}
}
// 错误:没有@Service 注解,Spring 不会管理这个类
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper; // 注入失败,userMapper 为 null
}
// 正确:加上@Service
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper; // 注入成功
}
// 错误:UserMapper 没有被 Spring 管理
public interface UserMapper {
// 缺少@Mapper 注解
User selectById(Long id);
}
// 正确:加上@Mapper 或在启动类加@MapperScan
@Mapper
public interface UserMapper {
User selectById(Long id);
}
// 错误:private 方法,事务不生效
@Transactional
private void saveUser(User user) {
userMapper.insert(user);
}
// 正确:必须是 public 方法
@Transactional
public void saveUser(User user) {
userMapper.insert(user);
}
// 错误:不能同时使用
@PostMapping("/add")
public Result<Boolean> add(@RequestBody Bill bill, @RequestParam Long userId) {
// 报错
}
// 正确:要么全用@RequestBody,要么全用@RequestParam
@PostMapping("/add")
public Result<Boolean> add(@RequestBody Map<String,Object> params) {
Long userId = (Long) params.get("userId");
Bill bill = (Bill) params.get("bill");
}
| 排名 | 注解 | 使用频率 | 重要程度 |
|---|---|---|---|
| 1 | @RestController | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 2 | @RequestMapping | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 3 | @Autowired | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 4 | @Service | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 5 | @Data | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 6 | @RequestParam | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 7 | @RequestBody | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 8 | @PathVariable | ⭐⭐⭐ | ⭐⭐⭐ |
| 9 | @Transactional | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 10 | @Valid | ⭐⭐⭐ | ⭐⭐⭐⭐ |
需要返回 JSON? → @RestController
需要获取 URL 参数? → @RequestParam
需要获取 JSON 数据? → @RequestBody
需要获取路径参数? → @PathVariable
需要参数校验? → @Valid + 校验注解
需要依赖注入? → @Autowired 或 @RequiredArgsConstructor
需要事务? → @Transactional
需要日志? → @Slf4j
需要权限验证? → 自定义注解 + @Aspect

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online