Optimizing Controller Layer Logic in Spring MVC: Best Practices and Refactoring
This article explains the responsibilities of the MVC controller layer, shows a typical Spring Boot controller implementation, and then demonstrates a series of refactorings—including delegating to the service layer, using validation annotations, standardizing response objects, and centralizing exception handling—to produce cleaner, more maintainable backend code.
In an MVC architecture the web project is divided into three layers: DAO, Service, and Controller. The controller is responsible for handling external requests, invoking service methods, and returning responses without containing business logic.
The controller should perform five main functions: (1) receive and parse request parameters, (2) respond when business logic succeeds, (3) handle exceptions, (4) convert business objects, and (5) call Service interfaces.
Original implementation :
@RestController
public class TestController {
@Autowired
private UserService userService;
@PostMapping("/test")
public Result service(@Validated @RequestBody UserRequestBo requestBo) throws Exception {
Result result = new Result();
// 参数校验
if (StringUtils.isNotEmpty(requestBo.getId()) || StringUtils.isNotEmpty(requestBo.getType())
|| StringUtils.isNotEmpty(requestBo.getName()) || StringUtils.isNotEmpty(requestBo.getAge())) {
throw new Exception("必输项校验失败");
} else {
try {
int count = 0;
User user = userService.queryUser(requestBo.getId());
if (ObjectUtils.isEmpty(user)) {
result.setCode("11111111111");
result.setMessage("请求失败");
return result;
}
// 转换业务对象
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(requestBo, userDTO);
if ("02".equals(user.getType())) {
count = userService.updateUser(userDTO);
} else if ("03".equals(user.getType())) {
count = userService.addUser(userDTO);
}
// 组装返回对象
result.setData(count);
result.setCode("00000000");
result.setMessage("请求成功");
} catch (Exception ex) {
// 异常处理
result.setCode("111111111111");
result.setMessage("请求失败");
}
}
return result;
}
}Optimization ideas :
Delegate all business operations to the Service layer; the controller only orchestrates calls.
Use validation annotations such as @NotBlank , @NotNull , and @NotEmpty on request DTO fields to perform parameter checks automatically.
Introduce a unified response wrapper Result<T> with static factory methods for success and failure.
Centralize exception handling with @RestControllerAdvice (or @ControllerAdvice ) to avoid repetitive try‑catch blocks.
Encapsulate object conversion inside the request DTO (e.g., convertToUserDTO() ) so the controller does not need to know conversion details.
Parameter validation DTO :
@Data
public class UserRequestBo {
@NotBlank
private String id;
@NotBlank
private String type;
@NotBlank
private String name;
@NotBlank
private String age;
/** Convert to UserDTO */
public UserDTO convertToUserDTO() {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(this, userDTO);
userDTO.setType(this.getType());
return userDTO;
}
}Unified response wrapper :
@Data
public class Result
{
private String code;
private String message;
private T data;
public static
Result
success(T data) {
return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
}
public static Result
failed() {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
}
public static Result
failed(String message) {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
}
public static Result
failed(String code, String message) {
return new Result<>(code, message, null);
}
}Centralized exception handling :
@RestControllerAdvice(basePackageClasses = TestController.class)
public class TestControllerAdvice {
@Autowired
HttpServletRequest request;
private void logErrorRequest(Exception e) {
String logInfo = String.format("报错API URL: %s, error = %s", request.getRequestURI(), e.getMessage());
System.out.println(logInfo);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
logErrorRequest(ex);
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), sb.toString());
}
@ExceptionHandler(RuntimeException.class)
protected Result serviceException(RuntimeException ex) {
logErrorRequest(ex);
return Result.failed(ex.getMessage());
}
@ExceptionHandler({HttpClientErrorException.class, IOException.class, Exception.class})
protected Result serviceException(Exception ex) {
logErrorRequest(ex);
return Result.failed(ex.getMessage());
}
}Final optimized controller :
@RestController
public class TestController {
@Autowired
private UserService userService;
@PostMapping("/test")
public Result service(@Validated @RequestBody UserRequestBo requestBo) throws Exception {
// Convert request to DTO and delegate to service
int count = userService.updateUser(requestBo.convertToUserDTO());
return Result.success(count);
}
}The article concludes that after applying these refactorings the controller becomes concise, focused on request orchestration, and benefits from standardized validation, response handling, and error management.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.