Controller层代码这么写,简洁又优雅( 二 )

  • supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
  • beforeBodyWrite:对 response 进行具体的处理
// 如果引入了swagger或knife4j的文档生成组件,这里需要仅扫描自己项目的包,否则文档无法正常生成@RestControllerAdvice(basePackages = "com.example.demo")public class ResponseAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 如果不需要进行封装的,可以添加一些校验手段,比如添加标记排除的注解return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 提供一定的灵活度,如果body已经被包装了,就不进行包装if (body instanceof Result) {return body;}return Result.success(body);}}经过这样改造,既能实现对 Controller 返回的数据进行统一包装,又不需要对原有代码进行大量的改动
参数校验JAVA API 的规范 JSR303 定义了校验的标准 validation-api ,其中一个比较出名的实现是 hibernate validation ,spring validation 是对其的二次封装,常用于 SpringMVC 的参数自动校验,参数校验的代码就不需要再与业务逻辑代码进行耦合了
@PathVariable 和 @RequestParam 参数校验Get 请求的参数接收一般依赖这两个注解,但是处于 url 有长度限制和代码的可维护性,超过 5 个参数尽量用实体来传参 对 @PathVariable 和 @RequestParam 参数进行校验需要在入参声明约束的注解
如果校验失败,会抛出
MethodArgumentNotValidException 异常
@RestController(value = https://www.isolves.com/it/cxkf/bk/2022-08-02/"prettyTestController")@RequestMapping("/pretty")@Validatedpublic class TestController {private TestService testService;@GetMapping("/{num}")public Integer detail(@PathVariable("num") @Min(1) @Max(20) Integer num) {return num * num;}@GetMapping("/getByEmail")public TestDTO getByAccount(@RequestParam @NotBlank @Email String email) {TestDTO testDTO = new TestDTO();testDTO.setEmail(email);return testDTO;}@Autowiredpublic void setTestService(TestService prettyTestService) {this.testService = prettyTestService;}}校验原理在 SpringMVC 中,有一个类是
RequestResponseBodyMethodProcessor ,这个类有两个作用(实际上可以从名字上得到一点启发)
  • 用于解析 @RequestBody 标注的参数
  • 处理 @ResponseBody 标注方法的返回值
解析 @RequestBoyd 标注参数的方法是 resolveArgument
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {/*** Throws MethodArgumentNotValidException if validation fails.* @throws HttpMessageNotReadableException if {@link RequestBody#required()}* is {@code true} and there is no body content or if there is no suitable* converter to read the content with.*/@Overridepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {parameter = parameter.nestedIfOptional();//把请求数据封装成标注的DTO对象Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());String name = Conventions.getVariableNameForParameter(parameter);if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);if (arg != null) {//执行数据校验validateIfApplicable(binder, parameter);//如果校验不通过,就抛出MethodArgumentNotValidException异常//如果我们不自己捕获,那么最终会由DefaultHandlerExceptionResolver捕获处理if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());}}if (mavContainer != null) {mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());}}return adaptArgumentIfNecessary(arg, parameter);}}public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {/*** Validate the binding target if applicable.* <p>The default implementation checks for {@code @javax.validation.Valid},* Spring's {@link org.springframework.validation.annotation.Validated},* and custom annotations whose name starts with "Valid".* @param binder the DataBinder to be used* @param parameter the method parameter descriptor* @since 4.1.5* @see #isBindExceptionRequired*/protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {//获取参数上的所有注解Annotation[] annotations = parameter.getParameterAnnotations();for (Annotation ann : annotations) {//如果注解中包含了@Valid、@Validated或者是名字以Valid开头的注解就进行参数校验Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);if (validationHints != null) {//实际校验逻辑,最终会调用Hibernate Validator执行真正的校验//所以Spring Validation是对Hibernate Validation的二次封装binder.validate(validationHints);break;}}}}


推荐阅读