在很多 Spring Boot 项目里,Controller 层常常是最“拥挤”的地方——参数校验写在里面、转换逻辑写在里面、返回体构造也写在里面,甚至有些业务逻辑也会不知不觉混进来。
结果就是:接口越写越慢、越写越乱、越写越难维护。
本文将分享8个能立刻提升开发效率的 Spring Boot 最佳实践,帮你把 Controller 层精简到极致。

技巧1:参数校验不写在 Controller,用 @Valid + @Validated 自动完成
很多人写接口时会写一堆 if 判断:if (request.getName() == null) { throw new IllegalArgumentException(“name不能为空”);}
这种重复劳动完全没必要。
最简单的写法是借助验证注解:
@PostMapping(“/user”)
public UserVO createUser(@RequestBody @Valid CreateUserRequest request){
return userService.createUser(request);
}
配合 DTO 上的注解:
public class CreateUserRequest{
@NotBlank(message = “name不能为空”)
private String name;
@Min(value = 1, message = “age必须大于0”)
private Integer age;
}
然后通过全局异常处理器统一处理校验失败的情况:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<?> handleValidException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().
getFieldError().
getDefaultMessage();
return R.error(msg);
}
}
这样,Controller 立刻能瘦身一大半。
技巧2:返回体不要每次 new,用统一响应结构 + 工具类
很多系统会重复写:return new Result(200, “success”, data);
建议定义一个统一的响应模板类:
public class R<T> {
private Integer code;
private String msg;
private T data;
public static <T> R<T> ok(T data){
return new R<>(200, “success”, data);
}
public static <T> R<T> error(String msg){
return new R<>(500, msg, null);
}
}
Controller 中就能直接调用静态方法,简洁明了:
@GetMapping(“/list”)
public R<List<UserVO>> list() {
return R.ok(userService.list());
}
这不仅能减少重复代码,还能增强整个 API 响应格式的一致性。
技巧3:不在 Controller 写对象转换,用 MapStruct 或 BeanUtils 统一处理
Controller 里手动进行对象转换是最浪费时间的事情之一。
例如:
UserVO vo = new UserVO();
vo.setName(user.getName());
vo.setAge(user.getAge());
这么写会让 Controller 变成纯粹的“数据搬运工”。
正确方式是使用 MapStruct 等工具自动生成转换代码。只需定义一个转换器接口:
@Mapper(componentModel = “spring”)
public interface UserConverter {
UserVO toVO(User user);
}
在 Controller 中直接注入并调用,实现零样板代码:
@GetMapping(“/{id}”)
public R<UserVO> detail(@PathVariable Long id){
return R.ok(converter.toVO(userService.getById(id)));
}
技巧4:不在 Controller 塞业务逻辑,保持“单一职责”
这条原则听起来很基础,但 80% 的 Controller 都可能违反。
例如下面这种写法:
@GetMapping(“/pay”)
public R<?> pay(Long orderId) {
Order order = orderService.getById(orderId);
if (order.getStatus() != 1) {
return R.error(“订单状态错”);
}
boolean result = payService.pay(order);
return result ? R.ok(null) : R.error(“支付失败”);
}
这里至少存在三个问题:
- 订单状态判断属于业务逻辑。
- 支付调用和结果判断属于业务逻辑。
- 错误处理应该交由异常体系来完成,而非在 Controller 手动判断。
应将其重构为:
@GetMapping(“/pay”)
public R<?> pay(Long orderId) {
payService.pay(orderId);
return R.ok(null);
}
让 Controller 只负责接收参数、调用服务和返回响应,业务细节全部下沉到 Service 层。
技巧5:常见参数骚操作统一封装,保持接口整洁
例如分页参数,经常出现:
public R<?> list(@RequestParam int page, @RequestParam int size)
更好的方式是统一封装成一个对象:
public class PageRequest {
private Integer page = 1;
private Integer size = 10;
}
Controller 接口随之变得清晰:
@GetMapping(“/list”)
public R<?> list(@Valid PageRequest req) {
return R.ok(service.page(req));
}
这也避免了在业务层手动拼装分页对象的麻烦。
技巧6:复杂查询参数用对象接收,不要用一堆 @RequestParam
下面这种写法非常不友好:
@GetMapping(“/search”)
public R<?> search(@RequestParam(required = false) String name,
@RequestParam(required = false) Integer age,
@RequestParam(required = false) String city,
@RequestParam(required = false) Integer status)
建议将所有查询条件封装成一个 Query 对象:
public class UserQuery {
private String name;
private Integer age;
private String city;
private Integer status;
}
Controller 接口立刻变得整洁:
@GetMapping(“/search”)
public R<?> search(UserQuery query) {
return R.ok(service.search(query));
}
技巧7:使用全局切面处理通用逻辑,不要写在 Controller
很多 Controller 会混杂以下逻辑:
- 接口日志记录
- 方法耗时统计
- 权限校验
- 访问频率限制
- traceId 记录
这些都不应该写在 Controller,更不应该侵入业务代码。
正确方式是使用 AOP 切面统一处理。例如,定义一个日志切面:
@Aspect
@Component
public class LogAspect {
@Around(“execution(* com.demo.controller.*.*(..))”)
public Object log(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - start;
log.info(“{} cost {} ms”, pjp.getSignature(), cost);
return result;
}
}
Controller 因此可以保持干净清爽,专注于核心流程。
技巧8:尽量使用 RESTful 风格,让接口结构更统一
不规范的接口设计会直接增加团队的阅读和维护成本。
不推荐的写法:
/getUserInfo
/addUserInfo
/updateUserInfo
/deleteUserInfo
推荐遵循 RESTful 设计风格:
GET /users/{id}
POST /users
PUT /users
DELETE /users/{id}
这样,Controller 的编写就会变得简洁而自然,这也是现代 Web开发 的通用实践:
@GetMapping(“/{id}”)
public R<UserVO> detail(@PathVariable Long id){
return R.ok(service.getDetail(id));
}
@PostMapping
public R<?> create(@RequestBody @Valid UserCreateRequest req) {
service.create(req);
return R.ok(null);
}
统一的风格能让接口越写越快,团队协作效率也更高。
总结
Controller 层做得越少、越薄,项目就越好维护。
核心原则可以归纳为以下几点:
- 参数校验:交给 DTO 和
@Valid 注解。
- 返回封装:使用统一响应结构。
- 业务逻辑:坚决下沉到 Service 层。
- 对象转换:使用专用工具,不在 Controller 手动处理。
- 公共逻辑:利用 AOP 切面剥离。
- 参数接收:复杂参数对象化。
- 接口风格:遵循 RESTful 规范。
- 最终定位:Controller 只负责协调请求与响应。
遵循这些实践,你的接口开发效率将得到显著提升。