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

简介: 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);
    }

f252aeea0b87eb19a98f7d4ace78f18a.png

引入依赖

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

219bf2da736c5b9e27f7f4070dbcad22.png

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

72e7ca3188d17148c3e011afffdd614a.png


二、统一响应


1.普通的响应


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

fe2774a3d0818af7a1ba0e7692d3077b.png

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

01f34606ab40ccd5f7bd5615d41eef37.png

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


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

58000dece17439875baf4880dea2fedf.png

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

b4cc8bb780b1212d758bc6f68a91f3e2.png

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


相关文章
|
2月前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
57 0
|
11天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
137 73
|
6天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
40 21
|
11天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
11天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
19天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
69 14
|
2月前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
48 4
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
144 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
59 2
|
2月前
|
消息中间件 Java 数据库
解密Spring Boot:深入理解条件装配与条件注解
Spring Boot中的条件装配与条件注解提供了强大的工具,使得应用程序可以根据不同的条件动态装配Bean,从而实现灵活的配置和管理。通过合理使用这些条件注解,开发者可以根据实际需求动态调整应用的行为,提升代码的可维护性和可扩展性。希望本文能够帮助你深入理解Spring Boot中的条件装配与条件注解,在实际开发中更好地应用这些功能。
43 2