今天我们来聊一聊如何一步步改造一套完美的 Controller 层代码,确保它既帅气又高效。
你知道,很多时候我们会遇到这样的情况:项目上线后,代码架构看似简单,随着业务不断扩展,Controller 层却成了“信息乱流”的起点。不同的接口返回格式不统一、异常处理凌乱不堪、参数校验不规范……这让人头大。
所以,今天咱们就从四个步骤出发,来聊聊如何优化 Controller 层,让它更加规范、灵活,也能提高代码的可维护性。
第一步:统一返回结构
说到统一的返回结构,大家可能会觉得:“这不就是接口返回一个 JSON 吗?没啥难度。”不过,想要真正的统一,不只是表面上的数据结构一致性,更要在成功与失败的响应格式上也保持一致,别再有“成功返回的是数据,失败返回的是字符串”这种情况了。为了做到这一点,我们可以定义一个通用的 Result
类,统一处理接口返回。
让我们先来看看怎么做:
public interface IResult {
Integer getCode();
String getMessage();
}
public enum ResultEnum implements IResult {
SUCCESS(2001, "接口调用成功"),
VALIDATE_FAILED(2002, "参数校验失败"),
COMMON_FAILED(2003, "接口调用失败"),
FORBIDDEN(2004, "没有权限访问资源");
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> 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);
}
}
在这个 Result
类里,我们通过定义一个 ResultEnum
枚举类来管理常见的返回码。这样,所有的接口无论是成功还是失败,返回格式都统一了。比如,成功时,我们就用 Result.success(data)
,失败时就用 Result.failed(message)
,格式清晰、统一、简单。
第二步:统一包装处理
假设你有很多接口方法,返回的数据类型各异,你是不是每次都要手动包装成 Result
类型?比如,有时你返回一个对象,有时返回一个列表。听起来是不是有点烦人?为了减少这种重复代码,我们可以利用 Spring 提供的 ResponseBodyAdvice
来进行统一包装。
来看这个例子:
@RestControllerAdvice(basePackages = "com.example.demo")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true; // 对所有返回类型都进行处理
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 如果返回值已经是 Result 类型,就不再做封装
if (body instanceof Result) {
return body;
}
// 否则包装成 Result 类型
return Result.success(body);
}
}
这个方法的核心是,拦截所有 Controller
层的返回结果,如果已经是 Result
类型了,就不再多做包装;如果不是,那就将其统一包装成 Result
格式。这样一来,你的 Controller 层就没有繁杂的返回包装逻辑,代码更简洁、易维护。
第三步:参数校验
参数校验这一块,大家应该都很清楚。请求参数不合法,直接让它炸了,谁也受不了!不过,手动校验参数有时非常繁琐,特别是每个接口都得写校验逻辑。我们可以利用 Spring 的 @Validated
注解来自动化这个过程,减少繁杂的代码。通过 JSR-303 和 JSR-380 标准,我们可以在 DTO 类中直接定义校验规则,像是 @NotNull
、@NotBlank
、@Email
等。
我们先看看如何定义一个 DTO 类并进行参数校验:
@Data
public class TestDTO {
@NotBlank(message = "用户名不能为空")
private String userName;
@NotBlank(message = "密码不能为空")
@Length(min = 6, max = 20, message = "密码长度应在6到20之间")
private String password;
@NotNull(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
}
然后在 Controller 层就可以通过 @Validated
来启用自动校验:
@RestController
@RequestMapping("/pretty")
public class TestController {
private TestService testService;
@PostMapping("/test-validation")
public Result<?> testValidation(@RequestBody @Validated TestDTO testDTO) {
this.testService.save(testDTO);
return Result.success("保存成功");
}
@Autowired
public void setTestService(TestService testService) {
this.testService = testService;
}
}
这样一来,Spring 会自动校验 TestDTO
中的参数。如果参数不合法,它会抛出一个 MethodArgumentNotValidException
异常,你可以在全局异常处理器中捕获这个异常并返回一个统一的错误响应。
第四步:自定义异常与统一拦截异常
自定义异常处理是提升代码可读性和维护性的重要一步。遇到业务逻辑错误或者特殊情况时,我们往往需要抛出特定的异常。为了保持异常处理的一致性,我们可以通过自定义异常类,并使用 @ExceptionHandler
来进行统一的异常拦截。
我们来看看如何定义自定义异常类:
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}
然后在全局异常处理器中捕获这些异常:
@RestControllerAdvice(basePackages = "com.example.demo")
public class ExceptionAdvice {
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException ex) {
return Result.failed(ex.getMessage());
}
@ExceptionHandler(ForbiddenException.class)
public Result<?> handleForbiddenException(ForbiddenException ex) {
return Result.failed("没有权限访问: " + ex.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception ex) {
return Result.failed("系统异常: " + ex.getMessage());
}
}
通过这种方式,我们可以针对不同类型的异常返回特定的错误信息,而且错误的返回格式也是统一的。这种方式让我们的异常处理更加规范,也大大提高了代码的可读性和可维护性。
小结
好了,通过这四个步骤,我们就能将一个凌乱的 Controller
层改造成一个整洁、规范、高效的代码架构:
统一返回结构,让接口的返回格式一致,便于前后端对接。 统一包装处理,减少重复代码,简化返回逻辑。 参数校验,确保请求数据的合法性,避免不必要的错误。 自定义异常与统一拦截异常,提高异常处理的灵活性与清晰度。
通过这些改造,Controller 层不仅变得更加规范和灵活,代码的可维护性和可读性也得到了大幅提升。不得不说,这样的改造真的很帅!🚀
对编程、职场感兴趣的同学,可以链接我,微信:coder301 拉你进入“程序员交流群”。