开源了,优雅的Controller,应该这样写!
01 引言你的Controller控制层还在通过if-else校验参数还在通过try-catch捕捉异常么还在因为参数校验不通过给客户端响应错误信息么......我们看看优雅的控制层是怎么写的02 传统控制层的写法2.1 参数校验RequestMapping(/foo) public JsonResult login(String cellphoneString name) { String cellphone C2BLoginUtil.getLoginCellphone(); if (StringUtils.isBlank(cellphone)) { return new JsonResult(false,手机号不能为空); } if (StringUtils.isBlank(name)) { return new JsonResult(false,姓名不能为空); } // ...... return new JsonResult(true); }在控制层内实现参数的校验如果参数较多就会出现大量的校验代码就会显的很臃肿。如果多个方法复用参数每个方法都要去写一遍造成代码冗余。2.2 异常的捕捉RequestMapping(/foo) public JsonResult login(String cellphoneString name) { try { // ... } catch (Exception e) { log.error(服务调用异常, e); return new JsonResult(false, 服务调用异常); } // ...... return new JsonResult(true); }每一个方法都需要异常捕获这也是代码冗余的体现。03 优雅的写法统一参数校验 统一异常捕获。整合控制才能重复的逻辑减少代码冗余。3.1 统一参数处理假如有一个Book 类需要校验我们需要将参数的校验统一处理。然后服务端只需要一个注解就可轻松解决还可以复用。Data public class Book implements Serializable { private static final long serialVersionUID -4746928436252252305L; /** * ID */ private Integer bookId; /** * 名称 */ Length(min 5, max 20, message 书名的长度必须在5~20之间) private String bookName; /** * 定价 */ Min(value 15, message 书的价格不能低于15元) Max(value 100, message 书的价格不能超过100元) private BigDecimal bookPrice; /** * 发布日期 */ NotNull(message publishDate 不能为空) private Date publishDate; /** * 编写完成日期 */ NotNull(message finishDateTime 不能为空) private LocalDateTime finishDateTime; /** * script */ private String script; }服务端的处理只需要一个Valid注解就可完美的解决参数校验的问题。RequestMapping(/testFormData) public String testFormData(Valid Book book){ System.out.println(book); return success; }是不是少了好多代码量。3.2 统一异常捕获将所有的异常放在一起处理控制层就不需要处理了如果需要只需要抛出异常即可。RestControllerAdvice public class GlobalExceptionHandler public static String LOG_TEMPLATE 请求路径[url]{}[%s] 异常信息; public static String REQUEST_PARAMS_EX_TEMPLATE 参数[%s]%s; public static String REQUEST_PARAMS_MISS_TEMPLATE 请求参数缺失缺失的参数%s; Autowired(required false) private GlobalExceptionResponseResolver globalExceptionResponseResolver; /** * 校验参数绑定异常 * * author ws * date 2024/1/29 11:13 * param e */ ExceptionHandler(BindException.class) public Object bindExceptionHandler(HttpServletRequest request, BindException e) { log.error(String.format(LOG_TEMPLATE, bindExceptionHandler), request.getRequestURL(), e); return doGenerateResult(e, getErrorMsgDefault(e.getBindingResult().getFieldErrors(), e.getMessage())); } /** * 方法参数校验异常处理 * * author ws * date 2024/1/29 13:25 * param e */ ExceptionHandler(MethodArgumentNotValidException.class) public Object methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) { log.error(String.format(LOG_TEMPLATE, methodArgumentNotValidExceptionHandler), request.getRequestURL(), e); return doGenerateResult(e, getErrorMsgDefault(e.getBindingResult().getFieldErrors(), e.getMessage())); } /** * 参数校验配合Requestparam的异常 * * author ws * date 2024/3/7 16:29 */ ExceptionHandler(ConstraintViolationException.class) public Object constraintViolationExceptionExceptionHandler(HttpServletRequest request, ConstraintViolationException e) { log.error(String.format(LOG_TEMPLATE, constraintViolationExceptionExceptionHandler), request.getRequestURL(), e); return doGenerateResult(e, getConstraintViolationExceptionMsg(e)); } /** * 请求参数缺失异常 * * author ws * date 2024/1/29 13:33 * param e */ ExceptionHandler(MissingServletRequestParameterException.class) public Object missingServletRequestParameterExceptionHandler(HttpServletRequest request, MissingServletRequestParameterException e) { log.error(String.format(LOG_TEMPLATE, missingServletRequestParameterExceptionHandler), request.getRequestURL(), e); return doGenerateResult(e, String.format(REQUEST_PARAMS_MISS_TEMPLATE, e.getParameterName())); } /** * 重复的请求拦截异常 * * author ws * date 2024/1/30 16:51 * param request * param e */ ExceptionHandler(WebConfigException.class) public Object requestLimitingInterceptorExceptionHandler(HttpServletRequest request, WebConfigException e) { log.error(String.format(LOG_TEMPLATE, requestLimitingInterceptorExceptionHandler), request.getRequestURL(), e); return doGenerateResult(e, e.getMessage()); } /** * 兜底的异常处理 * * author ws * date 2024/1/29 13:35 * param e */ ExceptionHandler(Exception.class) public Object exceptionHandler(HttpServletRequest request, Exception e) { log.error(String.format(LOG_TEMPLATE, exceptionHandler), request.getRequestURL(), e); return doGenerateResult(e, e.getMessage()); } /** * 处理ConstraintViolationException的参数 * * author ws * date 2024/3/7 16:34 */ private String getConstraintViolationExceptionMsg(ConstraintViolationException e) { SetConstraintViolation? violations e.getConstraintViolations(); if (CollectionUtils.isEmpty(violations)) { return e.getMessage(); } return violations.stream().filter(Objects::nonNull) .map(cv - String.format(REQUEST_PARAMS_EX_TEMPLATE, cv.getPropertyPath(), cv.getMessage())) .collect(Collectors.joining(\n)); } /** * 处理公用的参数校验异常的信息 * * author ws * date 2024/1/31 10:45 */ private String getErrorMsgDefault(ListFieldError fieldErrors, String detaultMsg) { String errorMsg null; if (!CollectionUtils.isEmpty(fieldErrors)) { ListString msgs new ArrayList(fieldErrors.size()); fieldErrors.forEach(error - msgs.add(showErrorMsg(error))); errorMsg String.join(\n, msgs); } return Optional.ofNullable(errorMsg).orElse(detaultMsg); } private String showErrorMsg(FieldError fieldError) { return String.format(REQUEST_PARAMS_EX_TEMPLATE, fieldError.getField(), fieldError.getDefaultMessage()); } /** * 处理通用结果 * * author ws * date 2024/1/31 10:43 */ private Object doGenerateResult(Exception e, String errorMsg) { disposeExceptionTrace(errorMsg); if (globalExceptionResponseResolver ! null) { globalExceptionResponseResolver.pushExceptionNotice(e, errorMsg); return globalExceptionResponseResolver.resolveExceptionResponse(e, errorMsg); } return JsonResult.ofFail(errorMsg); } }服务端的处理理论上服务端已经不需要处理异常了。但是如果需要校验方法内部的异常只需要抛出异常即可。RequestMapping(/testEx) public String testEx() { Assert.notNull(obj, obj 不能为空); return success; }这里如果obj 如果为空就会抛异常异常会被统一处理。3.3 统一Web的日期处理Form表单的如果传递日期格式需要控制层处理。如果日期的格式都不相同处理起来就会麻烦。我们可以使用统一处理日期格式兼容所有的场景。ControllerAdvice public class GlobalWebHandler { /** * 处理web的data数据这里主要处理form表单的日期 * * author ws * date 2024/1/29 14:13 * param binder */ InitBinder public void globalWebDataBinder(WebDataBinder binder){ DateFormatter dateFormatter new DateFormatter(DateFormatConstant.STANDARD_DATE_TIME); dateFormatter.setFallbackPatterns(DateFormatConstant.FORMAT_PATTERNS); binder.addCustomFormatter(dateFormatter, Date.class); //兼容jdk8 LocalDateTime binder.addCustomFormatter(new LocalDateTimeFormatter(), LocalDateTime.class); } }该方法兼容常用的日志格式以及LocalDateTime 只要传递对应的字符串就可以正常解析。支持的格式04 开源项目控制层的优雅写法技术文章中多次被各个大佬讲过但是都只是教你如何处理却没有现成的工具封装。为了能够更好的使用优雅的写法我自己从总结了常用的类型、以及以及处理方案并开源欢迎大家使用。只要引入对应的依赖就可体验这种写法不需要繁琐的配置开箱即用。GitHub地址github.com/simonking-w…Gitee地址: gitee.com/simonkingws…该项目不仅仅处理了通用的参数还增加常用的拦截器、重复提交、链路追踪、在线运维等方法原文链接https://juejin.cn/post/7504491526364037158