一、参数校验
1.普通做法
写多个if来判断条件
实体类
@Data public class User { private String username; private String password; private String email; }
@PostMapping("/loginUser") public void loginUser(@RequestBody User user) throws Exception { if(StringUtils.isBlank(user.getUsername())){ throw new Exception("用户名不能为空"); } if (StringUtils.isBlank(user.getPassword())){ throw new Exception("密码不能为空"); } if (StringUtils.isBlank(user.getEmail())){ throw new Exception("邮箱不能为空"); } System.out.println(user); }
StringUtils的依赖包
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency>
2.@Validated注解
实体类
@Data public class User { @NotBlank(message = "用户名不能为空") private String username; @NotBlank(message = "密码不能为空") private String password; @Email(message = "邮箱格式错误") private String email; }
@PostMapping("/loginUser") public void loginUser(@Validated @RequestBody User user) throws Exception { System.out.println(user); }
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
踩坑过后的建议
字符串建议用@NotBlank不要用@NotNull
@NotNull:用在基本类型上 不能为null但是可以为空字符串
@NotEmpty:用在集合类上 不能为空并且长度必须大于0
@NotBlank:只能作用在String上 不能为null并且调用trim()后长度必须大于0
3.优化异常处理
由上面可以看出抛出了MethodArgumentNotValidException异常
而MethodArgumentNotValidException继承了BindException
@RestControllerAdvice public class ControllerAdvice { @ExceptionHandler(BindException.class) public R MethodArgumentNotValidExceptionHandler(BindException e){ //获取到错误信息 String objectError = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); return new R().setFlag(false).setMessage(objectError); } }
二、统一响应
1.普通的响应
@PostMapping("/getUser") public User getUser(){ return new User(); }
2.第一次封装
@Data @Accessors(chain = true) public class R { private boolean flag; private String message; private Object data; }
@GetMapping("/getUser") public R getUser(){ return new R().setFlag(true).setMessage("获取用户成功").setData(new User()); }
这里面可以封装状态码信息等我只是简单封装
3.封装改进
每次返回都要new R()并且设置flag很麻烦所以提供一个静态方法
@Data @Accessors(chain = true) public class R { private boolean flag; private String message; private Object data; public static R ok(){ return new R().setFlag(true); } public static R error(){ return new R().setFlag(false); } }
@GetMapping("/getUser") public R getUser(){ return R.ok().setMessage("获取用户成功").setData(new User()); }
4.另一种封装的方式
AOP拦截所有Controller
@RestControllerAdvice(basePackages = {"com.example.quickspringboot.controller"}) public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> { //判断这个类型是不是已经是 R 是了就不用封装,如果不是就会调用 beforeBodyWrite @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return !returnType.getParameterType().isAssignableFrom(R.class); } @SneakyThrows @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // String类型不能直接包装 if (returnType.getGenericParameterType().equals(String.class)) { ObjectMapper objectMapper = new ObjectMapper(); try { // 将数据包装在ResultVo里后转换为json串进行返回 return objectMapper.writeValueAsString(new R().setData(body)); } catch (JsonProcessingException e) { throw new Exception("ControllerResponseAdvice String 封装失败"); } } // 否则直接包装成ResultVo返回 return new R().setData(body); } }
@GetMapping("/getUser") public User getUser(){ return new User(); }
但是这个只是对数据,这种可以设置成功的案列因为flag和message如果成功可以设置为默认
5.不开启统一响应
假如有需求返回结果不要R类型需要String类型或者其他类型,那么第一种封装就可以很快直接返回就行而使用AOP不能,所以我们可以自定义一个注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NotControllerResponseAdvice { }
@Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return !returnType.getParameterType().isAssignableFrom(R.class) && returnType.hasMethodAnnotation(NotControllerResponseAdvice.class); }
6.自定义注解的元注解的介绍
1.@Target
说明注解修饰的对象范围,枚举规定了范围
// 用于描述类、接口等 TYPE, //用于描述域 FIELD, //用于描述方法 METHOD, //用于描述参数 PARAMETER, //用于描述构造器 CONSTRUCTOR, //用于描述局部变量 LOCAL_VARIABLE, //注解变量 ANNOTATION_TYPE, //用于描述包 PACKAGE, TYPE_PARAMETER, TYPE_USE
2.@Retention
定义该注解被保留时间长短
//在源文件有效 SOURCE, //在class文件中有效 CLASS, //运行时 RUNTIME
3.@Inherited
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
4.@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员
三、统一异常处理
首先继承异常类
@Data public class MyException extends RuntimeException{ private int code; private String msg; }
@RestControllerAdvice public class ControllerAdvice { @ExceptionHandler(BindException.class) public R MethodArgumentNotValidExceptionHandler(BindException e){ //获取到错误信息 String objectError = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); return new R().setFlag(false).setMessage(objectError); } @ExceptionHandler(MyException.class) public void test(Exception e){ System.out.println(e.getMessage()); } }