参数校验、统一异常、统一响应在SpringBoot中的做法以及自定义注解

文章目录

  • 一、参数校验
    • 1.普通做法
    • 2.@Validated注解
    • 3.优化异常处理
  • 二、统一响应
    • 1.普通的响应
    • 2.第一次封装
    • 3.封装改进
    • 4.另一种封装的方式
    • 5.不开启统一响应
    • 6.自定义注解的元注解的介绍
      • 1.@Target
      • 2.@Retention
      • 3.@Inherited
      • 4.@Documented
  • 三、统一异常处理

一、参数校验

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.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.12.0version>
        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);
    }

参数校验、统一异常、统一响应在SpringBoot中的做法以及自定义注解_第1张图片

image-20220403125218207

引入依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-validationartifactId>
        dependency>

踩坑过后的建议

字符串建议用@NotBlank不要用@NotNull

@NotNull:用在基本类型上 不能为null但是可以为空字符串

@NotEmpty:用在集合类上 不能为空并且长度必须大于0

@NotBlank:只能作用在String上 不能为null并且调用trim()后长度必须大于0

3.优化异常处理

由上面可以看出抛出了MethodArgumentNotValidException异常

image-20220403131533850

而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);
    }
}

参数校验、统一异常、统一响应在SpringBoot中的做法以及自定义注解_第2张图片

二、统一响应

1.普通的响应

    @PostMapping("/getUser")
    public User getUser(){
        return new User();
    }

参数校验、统一异常、统一响应在SpringBoot中的做法以及自定义注解_第3张图片

但是要进行和前端的交互,为了和前端妹子打好关系所以我们通常需要对数据进行包装一下,增加一下状态码,状态信息

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());
    }

参数校验、统一异常、统一响应在SpringBoot中的做法以及自定义注解_第4张图片

这里面可以封装状态码信息等我只是简单封装

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();
    }

参数校验、统一异常、统一响应在SpringBoot中的做法以及自定义注解_第5张图片

但是这个只是对数据,这种可以设置成功的案列因为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);
    }

参数校验、统一异常、统一响应在SpringBoot中的做法以及自定义注解_第6张图片

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());
    }
}
  //获取到错误信息
    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());
}

}




你可能感兴趣的:(Spring等主流框架,spring,java)