首页 > 其他分享 >统一异常处理@ControllerAdvice及参数校验@Validated

统一异常处理@ControllerAdvice及参数校验@Validated

时间:2024-02-28 14:36:27浏览次数:23  
标签:code ControllerAdvice String 校验 Response return message Validated public

一、异常处理

有异常就必须处理,通常会在方法后面throws异常,或者是在方法内部进行try catch处理。
直接throws Exception
直接throws Exception,抛的异常太过宽泛,最好能抛出准确的异常,比如throws IOException之类。

    User getUserById(Integer id) throws IOException,BusinessException,InterruptedException;

如果有多种异常,那么方法后面要throws IOException,InterruptedException又显得冗长。
而且,异常一直向上抛,上层的类还是得处理这些异常。
try catch捕获异常
阿里巴巴的java规范中有一条,"最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。"
也就是说在Controller层,最好不要又throws Exception继续往上抛了。
但是,如果在Controller层进行大量的捕获异常,可能会出现大量的非常多的try catch代码块。

/**
  * 以下这种代码写法很丑。
  */
@PostMapping("/id")
public ResultInfo  getUserById(HttpServletRequest request)  {
	String postData = null;
	try {
		postData = IOUtils.toString(request.getInputStream(), "UTF-8");
	} catch (IOException e) {
		logger.error("IO异常);
	}
	JSONObject postJson = JSONObject.parseObject(postData);
	logger.info("请求中获取的参数为:" + postJson);
	String id=postJson.getString("id");
	User user=new User();
	try{
		user=getUserById(id)
	}catch(BusinessException e){
		logger.error("根据id查找用户发生异常,id:"+{});
	}
	
	//...
	
}

这么多的try catch很难看,不建议这样写。

二、统一异常处理

@ControllerAdvice配合@ExceptionHandler,可以很方便地统一处理异常。
首先是自定义的业务异常类,如下所示:

/**
 *  自定义异常。
 * 这里的BusinessException继承于RuntimeException,而非Exception。
 * 如果继承的是Exception,那么在服务层还是得进行异常处理。
 */
public class BusinessException  extends RuntimeException  {
    private static final long serialVersionUID = 1L;
    private String code;
    private String message;


    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    public BusinessException(String message) {
        super(message);
        this.message = message;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

Response 响应类

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;


/**
 * jackson 注解,忽略掉不相关的属性.如果不是实际项目,只是示例,也可以不加上。
 *
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Response<T>  {
    private String code = Status.SUCCESS.getCode();
    private String message = Status.SUCCESS.getName();
    private T data;

    public Response() {
    }

    public Response(T data) {
        this.data = data;
    }

    public Response(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public Response(Status status, String message) {
        this.code = status.getCode();
        this.message = message;
    }

    public Response(T data, String code, String message) {
        this.data = data;
        this.code = code;
        this.message = message;
    }

    public static <T> Response<T> success() {
        return new Response<>(null);
    }

    public static <T> Response<T> success(T data) {
        return new Response<>(data);
    }

    public static <T> Response<T> failure() {
        return new Response<>(null, Status.FAILED.getCode(), Status.FAILED.getName());
    }

    public static <T> Response<T> failure(String message) {
        return new Response<>(null, Status.FAILED.getCode(), message);
    }

    public static <T> Response<T> failure(T data) {
        return new Response<>(data, Status.FAILED.getCode(), Status.FAILED.getName());
    }

    public static <T> Response<T> failure(String code, String message) {
        return failure(null, code, message);
    }

    public static <T> Response<T> failure(T data, String code, String message) {
        return new Response<>(data, code, message);
    }

    public static <T> Response<T> failureWithoutData(String errorMsg) {
        return new Response<>(null, Status.FAILED.getCode(), errorMsg);
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public boolean isSuccess() {
        return code.equals(Status.SUCCESS.getCode());
    }



    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
    }

    /**
     * 状态
     */
    public enum Status {
        SUCCESS("200", "成功"),
        FAILED("500", "失败"),

        ;

        private final String code;
        private final String name;

        Status(String code, String name) {
            this.code = code;
            this.name = name;
        }

        public String getCode() {
            return this.code;
        }

        public String getName() {
            return this.name;
        }
    }
}

@ControllerAdvice 和 @ExceptionHandler 统一异常处理

接着是重点,@ControllerAdvice进行统一异常处理。

通过 @ExceptionHandler指定对应的异常处理措施。
如下所示:

import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;


@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public Response<Object> handleBusinessException(BusinessException e) {
        log.error("GlobalExceptionHandler handleBusinessException:" + e.getMessage());
        return Response.failure(e.getMessage());
    }


    /**
     * 处理所有接口数据验证异常。对应的是@Validated注解。
     * 注意,MethodArgumentNotValidException 要引入的类是 org.springframework.web.bind.MethodArgumentNotValidException,
     * 不要import错了。
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Response<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("GlobalExceptionHandler handleMethodArgumentNotValidException e:" + e.getMessage(), e);
        List<String> allMessage = new ArrayList<>();
        for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
            allMessage.add(fieldError.getDefaultMessage());
        }
        return Response.failure(StringUtils.join(allMessage, ";"));
    }


    /**
     * 处理所有的异常。最好放到最后面,做为兜底。放在前面,就不会再进入具体的异常了。
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Response<Object> handleException(Exception e) {
        log.error("GlobalExceptionHandler handleException e:", e);
        return Response.failure(e.getMessage());
    }

}

服务层

有了自定义异常,就可以在服务层抛出,直接在方法内部 throw new BusinessException(); 如下示:

    @PostMapping("/exception")
    @ResponseBody
    public Response<List<Order>> biz(@RequestBody  Order order) {
        throw new BusinessException("业务异常测试");
    }

有了@ControllerAdvice统一异常处理,那么在控制层就无须再处理了。

三、参数校验@Validated

@ControllerAdvice除了进行统一异常,还能配合@Validated注解进行参数校验。
Controller层的参数通常都需要检验,经常会看到大量的判空,然后返回错误提示,比如"名字不能为空"之类的提示。
有些人可能会像下面这样写:

/**
  *  以下的参数校验实在是太繁杂了。不建议这样写。
  */
@PostMapping("user")
@ResponseBody
public BaseResult getUser()  {

	//名字为空就返回错误提示"名字不能为空"
	if(StringUtils.isEmpty(name)){
		return  new BaseResult( ErrorType.NAME_NOT_NULL );
	}
     //手机号码为空就返回错误提示"手机号码不能为空"
	if(StringUtils.isEmpty(phoneNumber)){
		return  new BaseResult( ErrorType.PHONENUMBER_IS_NULL  );
	}

    // ...
}

这些冗长的参数校验,可以通过@Validated注解简化。
如下所示,直接在bean对象上面添加注解:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @NotNull(message = "id不能为空")
    private Integer id;
    @NotBlank(message = "名字不能为空")
    private String userName;
    @Min(value = 18,message = "年龄不能小于18岁")
    private Integer age;
    @NotNull(message = "手机号码不能为空")
    private String phoneNumber;
}

其中的类上方注解@Data之类是Lombok注解,详情见:https://www.cnblogs.com/expiator/p/10854141.html
而@NotNull,@Min这些是Validation注解。常见的参数校验注解如下:

JSR提供的校验注解:         
@Null   被注释的元素必须为 null    
@NotNull    被注释的元素必须不为 null    
@NotBlank    被注释的元素必须不为 null,不为空格组成   
@AssertTrue     被注释的元素必须为 true    
@AssertFalse    被注释的元素必须为 false    
@Min(value)     被注释的元素必须是一个数字,其值必须大于等于指定的最小值    
@Max(value)     被注释的元素必须是一个数字,其值必须小于等于指定的最大值    
@DecimalMin(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值    
@DecimalMax(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值    
@Size(max=, min=)   被注释的元素的大小必须在指定的范围内    
@Digits (integer, fraction)     被注释的元素必须是一个数字,其值必须在可接受的范围内    
@Past   被注释的元素必须是一个过去的日期    
@Future     被注释的元素必须是一个将来的日期    
@Pattern(regex=,flag=)  被注释的元素必须符合指定的正则表达式    

参数校验统一处理

@Validated注解的参数校验同样可以进行统一异常处理。
异常类型为MethodArgumentNotValidException.class 。
在统一异常处理类 GlobalExceptionHandler 中已经有如下代码:

    /**
     * 处理所有接口数据验证异常。对应的是@Validated注解。
     * 注意,MethodArgumentNotValidException 要引入的类是 org.springframework.web.bind.MethodArgumentNotValidException,
     * 不要import错了。
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Response<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("GlobalExceptionHandler handleMethodArgumentNotValidException e:" + e.getMessage(), e);
        List<String> allMessage = new ArrayList<>();
        for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
            allMessage.add(fieldError.getDefaultMessage());
        }
        return Response.failure(StringUtils.join(allMessage, ";"));
    }

控制层

只需要在方法参数前面加上注解@Validated ,如下所示:

    @PostMapping("/valid")
    @ResponseBody
    public Response<List<Order>> valid(@RequestBody @Validated Order order) {
        Response<List<Order>> response = new Response<>();
        response.setData(orderService.getListByDto(order));
        return response;
    }


参考资料:

https://blog.csdn.net/kinginblue/article/details/70186586
https://blog.csdn.net/u013815546/article/details/77248003/

标签:code,ControllerAdvice,String,校验,Response,return,message,Validated,public
From: https://www.cnblogs.com/expiator/p/18040281

相关文章

  • 数据校验_TEST
    importpandasaspdimportpymysql#数据库连接信息source_db_config={'host':'source_db_host','user':'source_db_user','password':'source_db_password','database':......
  • 关于磁盘和镜像的哈希值校验
    在取证做题联系的时候经常遇到这样的题目:请计算源盘的hash值,这时我们需要先对镜像进行挂载,像ftkimager等等软件,再对挂载后的磁盘进行hash值的计算给出两个计算工具1、火眼放入检材后相当于自动挂载2、winhex(注意此时如果需要计算本地磁盘的hash值,需要以管理员的身份运行winhe......
  • 微信小程序weui库表单提交 rules校验用法
    在开发微信小程序时候,一定会遇到表单提交问题。表单提交会遇到各种校验问题。微信小程序官方文档上面form是不带有校验功能的。如果要用需要自己手动校验。但是在weui中是有表单校验功能的,今天就来记录一下表单校验如何使用微信开放文档: https://developers.weixin.qq.com/min......
  • @Valid和@Validated区别
    @Valid和@Validated都是用来在Java中进行数据校验的注解,但它们来自不同的框架并服务于不同的目的:@Valid:@Valid是JavaEE(现在是JakartaEE)规范的一部分,具体来说是JSR303/JSR349(BeanValidation)的标准注解。它用于验证对象属性,当使用在方法参数上时,会在方法调用前自动触发......
  • 用路由方式写一个通用的微信小程序校验文件验证
    微信小程序加业务域名时,为了安全,通常需要在所在业务域名的根目录下加小程序校验文件,这个校验文件时txt格式,如果接入的小程序过多,需要多次上传。观察校验文件里的内容和校验文件名称是有一定关系的,我们可以通过路由方式,不管今后有多少小程序接入,都不需要传校验文件。下面是ThinkPHP......
  • 家庭小账本开发(7)登录校验功能
    在登陆时,前端与后端的交互过程如下①前端vue中将输入框中的username和password传给后端springboot②后端对传过来的(username和password)与后端数据库内容进行对比,如果用户存在--------利用jwt令牌生成token传给前端③vue前端将后端传过来的token值储存起来(一般储存到localStorag......
  • pytest简易教程(33):pytest常用插件 - 多重校验(pytest-assume)
     pytest简易教程汇总,详见:https://www.cnblogs.com/uncleyong/p/17982846应用场景对同一用例,要执行多个断言,查看断言是否都成功哪怕某个断言失败,后面断言依然能执行(assert实现不了) 插件安装pipinstall pytest-assume 使用方式pytest.assume(表达式)如果使用assert......
  • Modbus RTU通过从站地址获取校验码的代码
    主要方法拆分高低位计算校验码完整通过从站地址获取校验码的代码usingSystem;classProgram{staticvoidMain(){Console.Write("请输入从站地址(十六进制):");stringslaveAddressInput=Console.ReadLine();bytesl......
  • 多个form表单同时校验
    1.新建mulitipleFormValid.js/***多个表单同时校验*@param{*}formRefs*@returns*/constvalidateForms=(formRefs)=>{letobjectList=[];letresults=formRefs.map(formRef=>newPromise((resolve,reject)=>{formRef.validate......
  • el-form表单使用pattern自定义校验规则
    //正则校验的正则表达式,这里注意正则表达式中的‘\’要使用‘\\’转义constpatterns={"name":"^[a-zA-Z_][0-9a-zA-Z_]{0,}$","tel":"^1[2-9]\\d{0,}$","email":"^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-......