SpringBoot-如何参数校验、统一异常、统一响应以及自定义注解

一、参数校验

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

image-20220403125202883

image-20220403125218207

引入依赖

        <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异常

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

image-20220403132704144

二、统一响应

1.普通的响应

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

image-20220403133041494

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

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

image-20220403133757050

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

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

image-20220403135451070

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

image-20220403140521531

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());
    }
}
  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白羽uou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值