前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot中如何参数校验、统一异常、统一响应以及自定义注解

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

作者头像
才疏学浅的木子
发布2022-11-13 09:33:00
4090
发布2022-11-13 09:33:00
举报
文章被收录于专栏:CSDN文章

👨‍💻个人主页: 才疏学浅的木子 🙇‍♂️ 本人也在学习阶段如若发现问题,请告知非常感谢 🙇‍♂️ 📒 本文来自专栏: 常用工具类以及常见问题处理方法 🌈 每日一语越努力越幸运 🌈

本文目录

一、参数校验

1.普通做法

写多个if来判断条件

实体类

代码语言:javascript
复制
@Data
public class User {
    private String username;
    private String password;
    private String email;
}
代码语言:javascript
复制
    @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的依赖包

代码语言:javascript
复制
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

2.@Validated注解

实体类

代码语言:javascript
复制
@Data
public class User {
    @NotBlank(message = "用户名不能为空")
    private String username;
    @NotBlank(message = "密码不能为空")
    private String password;
    @Email(message = "邮箱格式错误")
    private String email;
}
代码语言:javascript
复制
    @PostMapping("/loginUser")
    public void loginUser(@Validated @RequestBody User user) throws Exception {
        System.out.println(user);
    }
image-20220403125202883
image-20220403125202883
image-20220403125218207
image-20220403125218207

引入依赖

代码语言:javascript
复制
        <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
image-20220403131533850

而MethodArgumentNotValidException继承了BindException

代码语言:javascript
复制
@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
image-20220403132704144

二、统一响应

1.普通的响应

代码语言:javascript
复制
    @PostMapping("/getUser")
    public User getUser(){
        return new User();
    }
image-20220403133041494
image-20220403133041494

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

2.第一次封装

代码语言:javascript
复制
@Data
@Accessors(chain = true)
public class R {
    private boolean flag;
    private String message;
    private Object data;
}
代码语言:javascript
复制
    @GetMapping("/getUser")
    public R getUser(){
        return new R().setFlag(true).setMessage("获取用户成功").setData(new User());
    }
image-20220403133757050
image-20220403133757050

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

3.封装改进

每次返回都要new R()并且设置flag很麻烦所以提供一个静态方法

代码语言:javascript
复制
@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);
    }
}
代码语言:javascript
复制
    @GetMapping("/getUser")
    public R getUser(){
        return R.ok().setMessage("获取用户成功").setData(new User());
    }

4.另一种封装的方式

AOP拦截所有Controller

代码语言:javascript
复制
@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);
    }
}
代码语言:javascript
复制
    @GetMapping("/getUser")
    public User getUser(){
        return new User();
    }
image-20220403135451070
image-20220403135451070

但是这个只是对数据,这种可以设置成功的案列因为flag和message如果成功可以设置为默认

5.不开启统一响应

假如有需求返回结果不要R类型需要String类型或者其他类型,那么第一种封装就可以很快直接返回就行而使用AOP不能,所以我们可以自定义一个注解

代码语言:javascript
复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {
}
代码语言:javascript
复制
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return !returnType.getParameterType().isAssignableFrom(R.class) 
                && returnType.hasMethodAnnotation(NotControllerResponseAdvice.class);
    }
image-20220403140521531
image-20220403140521531

6.自定义注解的元注解的介绍

1.@Target

说明注解修饰的对象范围,枚举规定了范围

代码语言:javascript
复制
// 用于描述类、接口等
TYPE,
//用于描述域
FIELD,
//用于描述方法
METHOD,
//用于描述参数
PARAMETER,
//用于描述构造器
CONSTRUCTOR,
//用于描述局部变量
LOCAL_VARIABLE,
//注解变量
ANNOTATION_TYPE,
//用于描述包
PACKAGE,
TYPE_PARAMETER,
TYPE_USE

2.@Retention

定义该注解被保留时间长短

代码语言:javascript
复制
//在源文件有效
SOURCE,
//在class文件中有效
CLASS,
//运行时
RUNTIME

3.@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

4.@Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

三、统一异常处理

首先继承异常类

代码语言:javascript
复制
@Data
public class MyException extends RuntimeException{
    private int code;
    private String msg;
}
代码语言:javascript
复制
@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());
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文目录
  • 一、参数校验
    • 1.普通做法
      • 2.@Validated注解
        • 3.优化异常处理
        • 二、统一响应
          • 1.普通的响应
            • 2.第一次封装
              • 3.封装改进
                • 4.另一种封装的方式
                  • 5.不开启统一响应
                    • 6.自定义注解的元注解的介绍
                      • 1.@Target
                      • 2.@Retention
                      • 3.@Inherited
                      • 4.@Documented
                  • 三、统一异常处理
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档