Designing an Excellent Controller Layer in Java Spring
This article explains how to build a clean, maintainable Controller layer in Java Spring by separating responsibilities, using unified response structures, applying ResponseBodyAdvice for automatic wrapping, implementing parameter validation with JSR‑303, and handling custom exceptions through centralized advice, illustrated with comprehensive code examples.
一个优秀的 Controller 层逻辑
Controller provides external data interfaces and acts as an indispensable supporting role in both traditional three‑layer and COLA architectures. Its main duties are receiving requests, delegating to services, handling exceptions, and returning results.
从现状看问题
Typical responsibilities:
Receive request and parse parameters
Call Service to execute business logic (including validation)
Capture business exceptions and give feedback
Return successful response
Problems with naïve implementations include excessive validation coupling, duplicated exception handling, and inconsistent response formats.
改造 Controller 层逻辑
统一返回结构
Define a common result interface and enum, then a generic Result<T> class with static factory methods.
public interface IResult {
Integer getCode();
String getMessage();
}
public enum ResultEnum implements IResult {
SUCCESS(2001, "接口调用成功"),
VALIDATE_FAILED(2002, "参数校验失败"),
COMMON_FAILED(2003, "接口调用失败"),
FORBIDDEN(2004, "没有权限访问资源");
// fields, constructor, getters omitted
}
@Data @NoArgsConstructor @AllArgsConstructor
public class Result
{
private Integer code;
private String message;
private T data;
public static
Result
success(T data) { ... }
public static Result
failed() { ... }
// other factory methods omitted
}To avoid repetitive wrapping, use ResponseBodyAdvice :
@RestControllerAdvice(basePackages = "com.example.demo")
public class ResponseAdvice implements ResponseBodyAdvice
{
@Override
public boolean supports(MethodParameter returnType, Class
> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class
> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
}参数校验
Leverage JSR‑303 (Hibernate Validator) and Spring’s @Validated to decouple validation from business logic.
// DTO example
@Data
public class TestDTO {
@NotBlank
private String userName;
@NotBlank @Length(min = 6, max = 20)
private String password;
@NotNull @Email
private String email;
}
// Controller method
@PostMapping("/test-validation")
public void testValidation(@RequestBody @Validated TestDTO testDTO) {
testService.save(testDTO);
}Spring’s RequestResponseBodyMethodProcessor resolves arguments, performs validation, and throws MethodArgumentNotValidException or ConstraintViolationException on failure.
自定义校验规则
Create a custom annotation and validator, e.g., @Mobile to validate phone numbers.
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
public @interface Mobile {
boolean required() default true;
String message() default "不是一个手机号码格式";
Class
[] groups() default {};
Class
[] payload() default {};
}
public class MobileValidator implements ConstraintValidator
{
private boolean required;
private final Pattern pattern = Pattern.compile("^1[34578][0-9]{9}$");
@Override public void initialize(Mobile annotation) { required = annotation.required(); }
@Override public boolean isValid(CharSequence value, ConstraintValidatorContext ctx) {
if (!required && !StringUtils.hasText(value)) return true;
return pattern.matcher(value).matches();
}
}自定义异常与统一拦截
Define specific runtime exceptions and handle them centrally.
public class BusinessException extends RuntimeException { public BusinessException(String msg){ super(msg); } }
public class ForbiddenException extends RuntimeException { public ForbiddenException(String msg){ super(msg); } }
@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(ResultEnum.FORBIDDEN); }
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result
handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { /* build message */ }
@ExceptionHandler(ConstraintViolationException.class)
public Result
handleConstraintViolationException(ConstraintViolationException ex) { /* build message */ }
@ExceptionHandler(Exception.class)
public Result
handle(Exception ex) { return Result.failed(ex.getMessage()); }
}总结
After applying these refactorings, Controller code becomes concise, each parameter’s validation rules are explicit, responses are uniformly wrapped, and all exceptions are centrally managed, allowing developers to focus on core business logic.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow 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.