SpringBoot校驗(validation)+全局處理異常


SpringBoot校驗(validation)+全局處理異常

SpringBoot校驗(validation)

加入依賴hibernate-validator
springBoot中可以直接引用starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

這里面正是包含了我們真正需要的hibernate-validator依賴

<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.2.0.Final</version>
  <scope>compile</scope>
</dependency>

hibernate-validator中有很多非常簡單好用的校驗注解,例如NotNull,@NotEmpty,@Min,@Max,@Email,@PositiveOrZero等等。這些注解能解決我們大部分的數據校驗問題。如下所示:

package com.nobody.dto;

import lombok.Data;

import javax.validation.constraints.*;

@Data
public class UserDTO {

    @NotBlank(message = "姓名不能為空")
    private String name;

    @Min(value = 18, message = "年齡不能小於18")
    private int age;

    @NotEmpty(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;
}

控制層代碼入參處記得加上@Valid注解開啟校驗,不然不生效。

@RestController
@RequestMapping("user")
public class UserController {

    @PostMapping("add")
    public UserDTO add(@RequestBody @Valid UserDTO userDTO) {
        System.out.println(">>> 用戶開戶成功...");
        return userDTO;
    }

}

二 自定義參數校驗器

但是,hibernate-validator中的這些注解不一定能滿足我們全部的需求,我們想校驗的邏輯比這復雜,那么我們可以自定義自己的參數校驗注解。

像它提供的注解一樣我們可以自定義自己的注解。
在這里插入圖片描述

自定義注解類

package com.zry.seckill.validator;

import com.zry.seckill.vo.IsMobileValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author zry
 * @ClassName IsMobile.java
 * @Description TODO
 * @createTime 2021年10月20日
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})//指定我們的校驗類,這里面實現了我們需要的具體校驗方法
public @interface IsMobile {
   /**
     * 是否強制校驗
     * 
     * @return 是否強制校驗的boolean值
     */
    boolean required() default true;  
    
  /**
     * 校驗不通過時的報錯信息
     * 
     * @return 校驗不通過時的報錯信息
     */
    String message() default "手機號碼格式錯誤";
    
   /**
     * 將validator進行分類,不同的類group中會執行不同的validator操作
     * 
     * @return validator的分類類型
     */
    Class<?>[] groups() default {};
    
   /**
     * 主要是針對bean,很少使用
     * 
     * @return 負載
     */
    Class<? extends Payload>[] payload() default {};

}

接下來我們完成 定義校驗類,實現ConstraintValidator接口,接口使用了泛型,需要指定兩個參數,
第一個是自定義注解,第二個是需要校驗的數據類型。
重寫2個方法,initialize方法主要做一些初始化操作,它的參數是我們使用到的注解,可以獲取到運行時的注解信息。
isValid方法就是要實現的校驗邏輯,被注解的對象會傳入此方法中。

package com.zry.seckill.vo;

import com.zry.seckill.utils.ValidatorUtil;
import com.zry.seckill.validator.IsMobile;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**手機校驗規則
 * @author zry
 * @ClassName IsMobileValidator.java
 * @Description 手機校驗規則
 * @createTime 2021年10月20日
 */
public class IsMobileValidator implements ConstraintValidator<IsMobile,String> {

    private boolean required = false;// 是否強制校驗
    @Override
    public void initialize(IsMobile constraintAnnotation) {
        this.required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
		//我這里調用了自定義的工具類
        return ValidatorUtil.isMobile(value);  // 返回boolean型,true代表通過校驗
    }
}

最后別忘了在Controller層加上 @Valid 注解才能啟用注解校驗。
在這里插入圖片描述
當然還有另一種@Validated,和@Valid有所不同,這里不做詳述。

Spring Validation驗證框架對參數的驗證機制提供了@Validated(Spring’s
JSR-303規范,是標准JSR-303的一個變種),javax提供了@Valid(標准JSR-303規范),配合BindingResult可以直接提供參數驗證結果。

通過以上幾個步驟,我們自定義的校驗注解就完成了。

如果參數校驗不通過,會拋出MethodArgumentNotValidException異常,我們全局處理下然后返回給接口。

SpringBoot全局處理異常

為什么要處理異常?

在日常開發中,為了不拋出異常堆棧信息給前端頁面,每次編寫Controller層代碼都要盡可能的catch住所有service層、dao層等異常,代碼耦合性較高,且不美觀,不利於后期維護。
為解決該問題,我們將Controller層異常信息統一封裝處理,且能區分對待Controller層方法返回給前端的String、Map、JSONObject、ModelAndView等結果類型。

簡單介紹

使用注解:

  • @ControllerAdvice+@ResponseBody + @ExceptionHandler

對於@ControllerAdvice,我們比較熟知的用法是結合@ExceptionHandler用於全局異常的處理,但其作用不止於此。ControllerAdvice拆開來就是Controller
Advice,關於Advice,在Spring的AOP中,是用來封裝一個切面所有屬性的,包括切入點和需要織入的切面邏輯。這里ControllerAdvice也可以這么理解,其抽象級別應該是用於對Controller進行切面環繞的,而具體的業務織入方式則是通過結合其他的注解來實現的。@ControllerAdvice是在類上聲明的注解,其用法主要有三點:

1.結合方法型注解@ExceptionHandler,用於捕獲Controller中拋出的指定類型的異常,從而達到不同類型的異常區別處理的目的。

2.結合方法型注解@InitBinder,用於request中自定義參數解析方式進行注冊,從而達到自定義指定格式參數的目的。

3.結合方法型注解@ModelAttribute,表示其注解的方法將會在目標Controller方法執行之前執行。

我們這次使用@RestControllerAdvice + @ExceptionHandler,用於捕獲Controller中拋出的指定類型的異常,從而達到不同類型的異常區別處理的目的。

方案一

這個是直接建一個異常處理類直接繼承 Exception
在這里插入圖片描述

異常處理類

package com.zry.simpleblog.handler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

/**
 * 使用 @ControllerAdvice 注釋來管理應用程序中的異常。
 * @ControllerAdvice是@Component 注解的一種特殊化,它允許在一個全局處理組件中處理整個應用程序中的異常。
 * 它可以被視為一個攔截器,攔截由注釋@RequestMapping和類似的方法拋出的異常。
 * @author zry
 * @ClassName ControllerExceptionHandler.java
 * @Description TODO
 * @createTime 2021年08月28日
 */
@RestControllerAdvice
public class ControllerExceptionHandler extends Exception {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @ExceptionHandler(value = {Exception.class})//指定捕獲異常的類型,這里是全部捕獲
    
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) throws Exception {
        ModelAndView mv = new ModelAndView();
        logger.error("Request URl : {}, Exception : {}",request.getRequestURI(),e);

//        如果定制了http狀態碼那么拋出異常
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }

        mv.addObject("url", request.getRequestURI());
        mv.addObject("exception",e);
        mv.setViewName("error/error");
        return mv;
    }
}

這樣一個簡單全局處理就做好了。

方案二

我們先建一個excepttion包來放我們的全局異常類。
在這里插入圖片描述

自定義異常類

package com.zry.seckill.exception;

import com.zry.seckill.vo.RespBeanEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author zry
 * @ClassName GlobalException.java
 * @Description TODO
 * @createTime 2022年01月01日
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GlobalException extends RuntimeException{
    private RespBeanEnum respBeanEnum;
}

自定義全局異常處理

package com.zry.seckill.exception;

import com.zry.seckill.vo.RespBean;
import com.zry.seckill.vo.RespBeanEnum;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/**
 * @author zry
 * @ClassName GlobalExceptionHandler.java
 * @Description TODO
 * @createTime 2022年01月01日
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    //調試日志
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @ExceptionHandler(Exception.class)//處理哪些異常
    public RespBean ExceptionHandler(Exception e,HttpServletRequest request){
    //打印日志
    logger.error("Requst URL : {},Exception : {}", request.getRequestURL(),e);
    
        if(e instanceof GlobalException){//如果是咱們之前自定義的異常
            GlobalException ex = (GlobalException) e;
            return RespBean.error(ex.getRespBeanEnum());
        }else if(e instanceof BindException){ //如果異常是綁定異常(比如沒有通過參數校驗注解拋出的異常)
            BindException be = (BindException) e;
            RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
            respBean.setMessage("參數校驗異常" + be.getBindingResult().getAllErrors().get(0).getDefaultMessage());
            return respBean;
        }
        return RespBean.error(RespBeanEnum.ERROR);
    
    }
	
}

這里我們就已經實現了對Controller層的全局異常處理。

下面是對上面方法中出現RespBeanEnum和RespBean的一些說明

GlobalExceptionHandler() 類中都是我們自己編寫的處理異常的方法。
我這里的ExceptionHandler()方法只是個例子。
其中RespBeanEnum是我自定義的枚舉類。它主要是為RespBean類服務。RespBean相當於統一返回實體類
用來返回結果信息。

返回類型枚舉

package com.zry.seckill.vo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

/**
 * @author zry
 * @ClassName RespBeanEnum.java
 * @Description 公共返回對象枚舉
 * @createTime 2021年10月15日
 */
@Getter
@ToString
@AllArgsConstructor
public enum RespBeanEnum {
    SUCCESS(200,"SUCCESS"),
    ERROR(500,"服務端異常"),
    LOGIN_ERROR(500210,"用戶名或密碼錯誤"),
    MOBILE_ERROR(500211,"手機號碼格式不正確"),
    BIND_ERROR(500212,"參數校驗異常");

    private final Integer code;
    private final String message;


}

返回結果類

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
    private long code;
    private String message;
    private Object obj;

    /**
     * 功能描述:返回成功結果
     * @param
     * @return
     */
    public static RespBean success(){
        return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(),null);
    }

    /**
     * 功能描述:返回成功結果
     * @param obj
     * @return
     */
    public static RespBean success(Object obj){
        return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(),obj);
    }
    /**
     * 功能描述:返回失敗結果
     * @param respBeanEnum
     * @return
     */
    public static RespBean error(RespBeanEnum respBeanEnum){
        return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),null);
    }
    /**
     * 功能描述:返回失敗結果
     * @param respBeanEnum,obj
     * @return
     */
    public static RespBean error(RespBeanEnum respBeanEnum,Object obj){
        return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),obj);
    }
}

@RestControllerAdvice與@ControllerAdvice的區別

@RestControllerAdvice與@ControllerAdvice的區別就和@RestController與@Controller的區別類似,@RestControllerAdvice注解包含了@ControllerAdvice注解和@ResponseBody注解。
(參考https://www.pianshen.com/article/6221932106/)
@RestControllerAdvice源碼中有@ControllerAdvice注解和@ResponseBody注解,當自定義類加@RestControllerAdvice注解時,方法自動返回json數據,每個方法無需再添加@ResponseBody注解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM