欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

@Valid和@Validated注解校驗(yàn)以及異常處理方式

 更新時(shí)間:2024年11月04日 14:40:52   作者:YD_1989  
在Javaweb開(kāi)發(fā)中,防止數(shù)據(jù)庫(kù)惡意攻擊是至關(guān)重要的,盡管前端校驗(yàn)可以起到一定的篩選作用,但通過(guò)工具如postman直接對(duì)后端發(fā)起請(qǐng)求的情況仍然需要后端進(jìn)行嚴(yán)格的數(shù)據(jù)校驗(yàn),Java生態(tài)下,@Valid注解配合SpringBoot提供了一個(gè)便捷高效的后端數(shù)據(jù)校驗(yàn)方案

前言

在Javaweb的開(kāi)發(fā)中,為了防止懂技術(shù)的人對(duì)數(shù)據(jù)庫(kù)的惡意攻擊,我們通常使用參數(shù)校驗(yàn)對(duì)無(wú)效數(shù)據(jù)進(jìn)行篩選,Java生態(tài)下的@valid注解配置SpringBoot的使用可以方便快速的完成對(duì)數(shù)據(jù)校驗(yàn)的各種場(chǎng)景。

同時(shí)數(shù)據(jù)校驗(yàn)分為前端校驗(yàn)后端校驗(yàn)。

可為何前端做完校驗(yàn)之后,還要在后端進(jìn)行校驗(yàn)?

如果有人拿到了url地址,使用第三方測(cè)試工具比如postman就可以跳過(guò)前端頁(yè)面的參數(shù)檢驗(yàn),所以為了數(shù)據(jù)庫(kù)數(shù)據(jù)正確性,我們十分有必要對(duì)傳來(lái)的數(shù)據(jù)在后端進(jìn)行第二次校驗(yàn)

一、@Valid注解

1、源碼解析

通過(guò)源碼可以看出:

@Valid注解可以作用于:方法、屬性(包括枚舉中的常量)、構(gòu)造函數(shù)、方法的形參上。

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}

關(guān)于注解源碼解析,可以參考如下鏈接:

Java如何自定義注解

2、所屬的包

import javax.validation.Valid;

3、參數(shù)校驗(yàn)使用注解

(1)空校驗(yàn)

注解應(yīng)用
@Null用于基本類型上,限制只能為null
@NotNull用在基本類型上;不能為null,但可以為empty,沒(méi)有Size的約束
@NotEmpty用在集合類上面;不能為null,而且長(zhǎng)度必須大于0
@NotBlank只能作用在String上,不能為null,而且調(diào)用trim()后,長(zhǎng)度必須大于0

插播一條小內(nèi)容?。。ull和empty有何區(qū)別?

String a = new String
String b = ""
String c = null
  1. 此時(shí)a是分配了內(nèi)存空間,但值為空,是絕對(duì)的空,是一種有值(值存在為空而已)
  2. 此時(shí)b是分配了內(nèi)存空間,值為空字符串,是相對(duì)的空,是一種有值(值存在為空字串)
  3. 此時(shí)c是未分配內(nèi)存空間,無(wú)值,是一種無(wú)值(值不存在)

(2)Boolean校驗(yàn)

注解應(yīng)用
@AssertFalse限制必須為false
@AssertTrue限制必須為true

(3)長(zhǎng)度校驗(yàn)

注解應(yīng)用
@Size(max,min)驗(yàn)證對(duì)象(Array,Collection,Map,String)長(zhǎng)度是否在給定的范圍之內(nèi)
@Length(min=, max=)驗(yàn)證字符串長(zhǎng)度是否在給定的范圍之內(nèi)

(4)日期校驗(yàn)

注解應(yīng)用
@Past限制必須是一個(gè)過(guò)去的日期,并且類型為java.util.Date
@Future限制必須是一個(gè)將來(lái)的日期,并且類型為java.util.Date
@Pattern(value)限制必須符合指定的正則表達(dá)式

(5)數(shù)值校驗(yàn)

建議使用在Stirng,Integer類型,不建議使用在int類型上,因?yàn)楸韱沃禐?ldquo;”時(shí)無(wú)法轉(zhuǎn)換為int,但可以轉(zhuǎn)換為Stirng為"",Integer為null。

注解應(yīng)用
@Min(value)驗(yàn)證 Number 和 String 對(duì)象必須為一個(gè)不小于指定值的數(shù)字
@Max(value)驗(yàn)證 Number 和 String 對(duì)象必須為一個(gè)不大于指定值的數(shù)字
@DecimalMax(value)限制必須為一個(gè)不大于指定值的數(shù)字,小數(shù)存在精度
@DecimalMin(value)限制必須為一個(gè)不小于指定值的數(shù)字,小數(shù)存在精度
@Digits(integer,fraction)限制必須為一個(gè)小數(shù),且整數(shù)部分的位數(shù)不能超過(guò)integer,小數(shù)部分的位數(shù)不能超過(guò)fraction
@Digits驗(yàn)證 Number 和 String 的構(gòu)成是否合法
@Range(max =3 , min =1 , message = " ")Checks whether the annotated value lies between (inclusive) the specified minimum and maximum

Max和Min是對(duì)你填的“數(shù)字”是否大于或小于指定值,這個(gè)“數(shù)字”可以是number或者string類型。長(zhǎng)度限制用length。

(6)其他校驗(yàn)

注解應(yīng)用
@Email驗(yàn)證注解的元素值是Email,也可以通過(guò)正則表達(dá)式和flag指定自定義的email

4、具體使用

使用 @Valid 進(jìn)行參數(shù)效驗(yàn)步驟:

  1. 實(shí)體類中添加 @Valid 相關(guān)注解
  2. 接口類中添加 @Valid 注解
  3. 全局異常處理類中處理 @Valid 拋出的異常

運(yùn)行流程:

整個(gè)過(guò)程如下圖所示,用戶訪問(wèn)接口,然后進(jìn)行參數(shù)效驗(yàn),因?yàn)?@Valid 不支持平面的參數(shù)效驗(yàn)(直接寫(xiě)在參數(shù)中字段的效驗(yàn))所以基于 GET 請(qǐng)求的參數(shù)還是按照原先方式進(jìn)行效驗(yàn),而 POST 則可以以實(shí)體對(duì)象為參數(shù),可以使用 @Valid 方式進(jìn)行效驗(yàn)。

如果效驗(yàn)通過(guò),則進(jìn)入業(yè)務(wù)邏輯,否則拋出異常,交由全局異常處理器進(jìn)行處理。

代碼實(shí)踐:

(1)添加maven依賴(三種方式添加依賴)

			<!--第一種:valid依賴-->
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>版本號(hào)</version>
		</dependency>

		<!--		第二種:集成于web依賴中(注意版本號(hào))-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>2.0.5.RELEASE</version>
		</dependency>

		<!--		第三種:springboot的validation-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>

(2)創(chuàng)建request實(shí)體類

import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;

@Data
@NoArgsConstructor
public class TestRequest {

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

    @Length(max = 3,message = "address最大長(zhǎng)度是3")
    private String address;

    @Max(value = 5,message = "reqNo最大值是5")
    private String reqNo;

}

(3)創(chuàng)建controller

@RestController
public class ValidTestController {

    @RequestMapping("/valid/test")
    public void test(@Valid @RequestBody TestRequest request){
        System.out.println(request);
    }

(4)postman測(cè)試

postman返回結(jié)果:

{
    "timestamp": "2022-11-12T09:54:24.202+00:00",
    "status": 400,
    "error": "Bad Request",
    "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.controller.ValidTestController.test(com.example.domain.TestRequest) with 3 errors: [Field error in object 'testRequest' on field 'name': rejected value []; codes [NotBlank.testRequest.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.name,name]; arguments []; default message [name]]; default message [name不為空]] [Field error in object 'testRequest' on field 'reqNo': rejected value [8]; codes [Max.testRequest.reqNo,Max.reqNo,Max.java.lang.String,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.reqNo,reqNo]; arguments []; default message [reqNo],5]; default message [reqNo最大值是5]] [Field error in object 'testRequest' on field 'address': rejected value [gtyjh]; codes [Length.testRequest.address,Length.address,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.address,address]; arguments []; default message [address],3,0]; default message [address最大長(zhǎng)度是3]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:681)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
    "message": "Validation failed for object='testRequest'. Error count: 3",
    "errors": [
        {
            "codes": [
                "NotBlank.testRequest.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "testRequest.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "name不為空",
            "objectName": "testRequest",
            "field": "name",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        },
        {
            "codes": [
                "Max.testRequest.reqNo",
                "Max.reqNo",
                "Max.java.lang.String",
                "Max"
            ],
            "arguments": [
                {
                    "codes": [
                        "testRequest.reqNo",
                        "reqNo"
                    ],
                    "arguments": null,
                    "defaultMessage": "reqNo",
                    "code": "reqNo"
                },
                5
            ],
            "defaultMessage": "reqNo最大值是5",
            "objectName": "testRequest",
            "field": "reqNo",
            "rejectedValue": "8",
            "bindingFailure": false,
            "code": "Max"
        },
        {
            "codes": [
                "Length.testRequest.address",
                "Length.address",
                "Length.java.lang.String",
                "Length"
            ],
            "arguments": [
                {
                    "codes": [
                        "testRequest.address",
                        "address"
                    ],
                    "arguments": null,
                    "defaultMessage": "address",
                    "code": "address"
                },
                3,
                0
            ],
            "defaultMessage": "address最大長(zhǎng)度是3",
            "objectName": "testRequest",
            "field": "address",
            "rejectedValue": "gtyjh",
            "bindingFailure": false,
            "code": "Length"
        }
    ],
    "path": "/valid/test"
}

從后端返回給postman的結(jié)果可以看出,三個(gè)字段的校驗(yàn)都已經(jīng)實(shí)現(xiàn)。但是,特別情況,RequestBody可能是嵌套的實(shí)體,這個(gè)時(shí)候,對(duì)于嵌套的實(shí)體類來(lái)說(shuō),嵌套必須加 @Valid,如果只在字段上添加校驗(yàn)注解嵌套中的驗(yàn)證不生效。

如果只在嵌套類字段上加上校驗(yàn)注解,如下:

import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;

@Data
@NoArgsConstructor
public class TestRequest {

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

    @Length(max = 3, message = "address最大長(zhǎng)度是3")
    private String address;

    @Max(value = 5, message = "reqNo最大值是5")
    private String reqNo;

    private TestRequestInner inner;

    @Data
    @NoArgsConstructor
    public static class TestRequestInner {
        @Length(max = 3, message = "最大長(zhǎng)度是3")
        private String sonName;

        private Integer sonAge;

        private String schoolNo;
    }
}

postman測(cè)試:

控制臺(tái)打?。?/p>

可以看出,嵌套類中sonName的長(zhǎng)度校驗(yàn)并沒(méi)有起到作用。

在嵌套類的外層加上@Valid注解,如下:

@Data
@NoArgsConstructor
public class TestRequest {

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

    @Length(max = 3, message = "address最大長(zhǎng)度是3")
    private String address;

    @Max(value = 5, message = "reqNo最大值是5")
    private String reqNo;

    @Valid
    private TestRequestInner inner;
    
//   即使放在list集合里面仍然是需要加上 @Valid 注解
//    @Valid
//    private List<TestRequestInner> inner;

    @Data
    @NoArgsConstructor
    public static class TestRequestInner {
        @Length(max = 3, message = "最大長(zhǎng)度是3")
        private String sonName;

        private Integer sonAge;

        private String schoolNo;
    }
}

postman測(cè)試:

校驗(yàn)成功。

5、異常處理

剛才的測(cè)試我們看到,校驗(yàn)注解全部生效,但是所有的異常全部拋出給postman,從控制臺(tái)可以看出:

程序拋出了MethodArgumentNotValidException異常信息,在實(shí)際業(yè)務(wù)中,有時(shí)候需要處理這個(gè)異常,這個(gè)時(shí)候就需要一個(gè)全局異常處理類中處理 @Valid 拋出的異常。

拋出的異常結(jié)構(gòu):

代碼如下:

實(shí)體類如上不變,controller接口方法改為返回string:

 @RequestMapping("/valid/test")
    public String test(@Valid @RequestBody TestRequest request){
        System.out.println(request);
        return "success";
    }

異常處理類:

我們可以根據(jù)上圖拋出的異常結(jié)構(gòu),去get我們想要獲得的內(nèi)容。

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 自定義驗(yàn)證異常
     * MethodArgumentNotValidException 方法參數(shù)無(wú)效異常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST) //設(shè)置狀態(tài)碼為 400
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public String paramExceptionHandler(MethodArgumentNotValidException e) {
        BindingResult exceptions = e.getBindingResult();
// 判斷異常中是否有錯(cuò)誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息
        if (exceptions.hasErrors()) {
            List errors = exceptions.getAllErrors();
            if (!errors.isEmpty()) {
// 這里列出了全部錯(cuò)誤參數(shù),按正常邏輯,只需要第一條錯(cuò)誤即可
                FieldError fieldError = (FieldError) errors.get(0);
                return fieldError.getDefaultMessage();
            }
        }
        return "請(qǐng)求參數(shù)錯(cuò)誤";
    }
}

postman測(cè)試:

6、springboot項(xiàng)目中的異常處理

上述的異常處理只是一個(gè)簡(jiǎn)單的string返回,但是在實(shí)際項(xiàng)目中,返回結(jié)構(gòu)是固定的,下面對(duì)于固定的返回結(jié)構(gòu),做異常處理。

(1)request實(shí)體類

import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import java.util.List;

@Data
@NoArgsConstructor
public class TestRequest {

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

    @Length(max = 3, message = "address最大長(zhǎng)度是3")
    private String address;

    @Max(value = 5, message = "reqNo最大值是5")
    private String reqNo;

    @Valid
    private List<TestRequestInner> inner;

    @Data
    @NoArgsConstructor
    public static class TestRequestInner {
        @Length(max = 3, message = "最大長(zhǎng)度是3")
        private String sonName;

        private Integer sonAge;

        @NotBlank(message = "schoolNo不空")
        private String schoolNo;
    }
}

(2)結(jié)果返回實(shí)體類

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;

@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class ResponseResult {

    private List<ProvideInfo> provideInfos;

    @Data
    @NoArgsConstructor
    @Builder
    @AllArgsConstructor
    public static class ProvideInfo {
        private String code;
        private String detail;
    }
}

(3)controller接口方法

import com.example.domain.ResponseResult;
import com.example.domain.TestRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class ValidTestController {

    @RequestMapping("/valid/test")
    public ResponseEntity<ResponseResult> test(@Valid @RequestBody TestRequest request) {
        System.out.println(request);
        return null;
    }
}

(4)postman測(cè)試

再插播一個(gè)小插曲!?。?!

剛開(kāi)始寫(xiě)的結(jié)果返回實(shí)體類中的provideInfo是這樣式兒滴:

然后運(yùn)行項(xiàng)目就出了這個(gè)錯(cuò):

英文版是這樣式兒滴:

上網(wǎng)查了一下,問(wèn)題出現(xiàn)在這:

原因:

本應(yīng)當(dāng)(只能)使用無(wú)參構(gòu)造器,但編譯器卻發(fā)現(xiàn)代碼中使用了有參(全參)構(gòu)造器,這個(gè)全參構(gòu)造器出現(xiàn)在Builder類的build()方法中,該方法試圖調(diào)用一個(gè)全參構(gòu)造器。

這個(gè)報(bào)錯(cuò)信息表明,@NoArgsConstructor抑制了@Builder生成全參構(gòu)造器,只生成了一個(gè)無(wú)參構(gòu)造器,使用lombok插件delombok @Builder@NoArgsConstructor兩個(gè)注解,可以證實(shí)這一抑制現(xiàn)象。

解決方法:

一種方法是同時(shí)使用@Builder、@NoArgsConstructor@AllArgConstructor,還有一種方法是顯式添加@Tolerate注解的無(wú)參構(gòu)造器。

然后在provideInfo上添加了@AllArgConstructor注解(如下圖),成功運(yùn)行!

(5)全局異常處理類各種形式

對(duì)于全局異常類的處理,涉及到攔截器相關(guān)內(nèi)容,這里不做多說(shuō)。全局異常類的處理方式有很多種:

方式一:

// Enum枚舉類
public enum CodeEnum {
// 根據(jù)自己的項(xiàng)目需求更改狀態(tài)碼,這里只是一個(gè)示范
    UNKNOW_EXCEPTION(10000,"系統(tǒng)未知錯(cuò)誤"),
    VALID_EXCETIPON(10001,"參數(shù)格式校驗(yàn)錯(cuò)誤");
    private int code;
    private String msg;
    CodeEnum(int code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}


//異常處理類
@Slf4j
@RestControllerAdvice("com.cbj.db_work.controller") //表明需要處理異常的范圍
public class GlobalExceptionHandler {

    @ExceptionHandler(value = MethodArgumentNotValidException.class) // 參數(shù)異常拋出的異常類型為MethodArgumentNotValidException,這里捕獲這個(gè)異常
    // R為統(tǒng)一返回的處理類
    public R validExceptionHandler(MethodArgumentNotValidException e){
        System.out.println("數(shù)據(jù)異常處理");
        log.error("數(shù)據(jù)校驗(yàn)出現(xiàn)問(wèn)題,異常類型:{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        Map<String,String> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach((item)->{
            String message = item.getDefaultMessage();
            // 獲取錯(cuò)誤的屬性字段名
            String field = item.getField();
            map.put(field,message);
        });
        return R.error().code(CodeEnum.VALID_EXCETIPON.getCode()).message(CodeEnum.VALID_EXCETIPON.getMsg()).data("errorData",map);
    }
    
}

方式二:

@ControllerAdvice
@RestControllerAdvice
@Slf4j
public class ValidExceptionHandler extends GlobalExceptionHandler {

    // GET請(qǐng)求參數(shù)異常處理
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Result<Object> constraintViolationExceptionHandler(ConstraintViolationException e) {
        StringBuilder msg = new StringBuilder();
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        for (ConstraintViolation<?> constraintViolation : constraintViolations) {
            String message = constraintViolation.getMessage();
            msg.append(message).append(";");
        }
        return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg.toString());
    }

    @ExceptionHandler(ArithmeticException.class)
    public Result<Object> arithmeticExceptionHandler(ArithmeticException e) {
        e.printStackTrace();
        return ResultResponse.getFailResult(ResultCode.NOT_FOUND.getResultCode(), "算術(shù)異常!"+e.getMessage());
    }

    // POST請(qǐng)求參數(shù)異常處理
    @ExceptionHandler(BindException.class)
    public Result<Object> bindExceptionHandler(BindException e) {
        FieldError fieldError = e.getBindingResult().getFieldError();
        String msg;
        if (Objects.isNull(fieldError)) {
            msg = "POST請(qǐng)求參數(shù)異常:" + JSON.toJSONString(e.getBindingResult());
            log.info(msg);
        } else {
            msg = fieldError.getDefaultMessage();
        }
        return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg);
    }

}

特別的,get請(qǐng)求校驗(yàn):

@RestController
@RequestMapping(value = "/test")
@Slf4j
//@ApiIgnore
@Validated
public class TestController {

    @GetMapping(value = "/test")
    public Result<Object> test(@NotNull(message = "name必傳")
                                   @NotBlank(message = "name格式錯(cuò)誤")String name) {
        return ResultResponse.getSuccessResult("hello: " + name);
    }
}

二、@Validated注解

1、@Validated 和 @Valid 區(qū)別

  1. @Validate 是對(duì)@Valid 的封裝
  2. @Validate 可以進(jìn)行分組驗(yàn)證 ,一個(gè)對(duì)象中都寫(xiě)了驗(yàn)證而你只需要驗(yàn)證該方法需要的驗(yàn)證時(shí)使用

2、為何要分組校驗(yàn)?

假設(shè)有這樣一種場(chǎng)景:

我們使用同一個(gè)VO(Request)類來(lái)傳遞save和update方法的數(shù)據(jù),但對(duì)于id來(lái)說(shuō),通常有框架幫我們生成id,我們不需要傳id此時(shí)需要使用注解@Null表名id數(shù)據(jù)必須為空。

但對(duì)于update方法,我們必須傳id才能進(jìn)行update操作,所以同一個(gè)字段面對(duì)不同的場(chǎng)景不同需求就可以使用分組校驗(yàn)。

3、代碼實(shí)操

(1)創(chuàng)建分組接口

這里并不需要實(shí)現(xiàn)編寫(xiě)什么代碼,標(biāo)明分類。

//分組接口 1
public interface InsertGroup {
}

//分組接口 2
public class UpdateGroup {
}

(2)Request實(shí)體類

@Data
@NoArgsConstructor
public class TestRequest {

    @Null(message = "無(wú)需傳id",groups = InsertGroup.class)
    @NotBlank(message = "必須傳入id",groups = UpdateGroup.class)
    private String id;
    
  }

(3)controller接口

	@RequestMapping("/valid/test")
    public ResponseEntity<ResponseResult> test(@Validated({UpdateGroup.class})@RequestBody TestRequest request) {
        System.out.println(request);
        return null;
    }

(4)postman測(cè)試

(5)注意事項(xiàng)?。?!

當(dāng)我們?cè)赾ontroller層指定分組后,屬性上沒(méi)有表名分組的校驗(yàn)還執(zhí)行么?

下面的Request實(shí)體中,id進(jìn)行了分組,address沒(méi)有進(jìn)行分組:

@Data
@NoArgsConstructor
public class TestRequest {

    @Null(message = "無(wú)需傳id",groups = InsertGroup.class)
    @NotBlank(message = "必須傳入id",groups = UpdateGroup.class)
    private String id;

	@Length(max = 3, message = "address最大長(zhǎng)度是3")
    private String address;
    
  }

postman測(cè)試:

如下圖,校驗(yàn)沒(méi)有成功?。。?!說(shuō)明一旦開(kāi)啟了分組校驗(yàn),就必須把所有的校驗(yàn)規(guī)則都指定組別,不然不生效。

三、自定義校驗(yàn)注解

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)字段比如showStatus只能由0和1兩個(gè)取值,我們可以使用正則,也可以自定義注解校驗(yàn),這里我們展示如何使用自定義校驗(yàn).

如下圖所示:

自定義注解實(shí)現(xiàn)過(guò)程

(1)編寫(xiě)自定義注解

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

// Target表示注解使用的范圍
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
// 獲取注解的時(shí)間,固定值
@Retention(RetentionPolicy.RUNTIME)
// 匹配的校驗(yàn)器,我們稍后編寫(xiě),關(guān)聯(lián)注解和校驗(yàn)器
@Constraint(validatedBy = {StatusValueValidator.class})
@Documented
public @interface StatusValue {

    // 錯(cuò)誤信息去哪找,通常我們使用配置文件,稍后編寫(xiě)
    String message() default "{com.cbj.db_work.valid.ListValue.message}";
    // 支持分組校驗(yàn)
    Class<?>[] groups() default {};
    // 自定義負(fù)載信息
    Class<? extends Payload>[] payload() default {};
    // 指定參數(shù),就是上圖中指定的可取值的范圍
    int []  vals() default {};
}

(2)自定義校驗(yàn)器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

// 實(shí)現(xiàn)ConstraintValidator接口,泛型值:<自定義注解類,被校驗(yàn)值的數(shù)據(jù)類型>
public class StatusValueValidator implements ConstraintValidator<StatusValue, Integer> {

    // 整體思路,使用set在initialize獲得參數(shù)信息,在isValid方法中校驗(yàn),成功true,失敗false
    Set<Integer> set = new HashSet<>();

    // 初始化方法,可以得到詳細(xì)信息
    @Override
    public void initialize(StatusValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

/**
     * 校驗(yàn)是否匹配
     * @param value 就是需要校驗(yàn)的值
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(integer);
    }
}

(3)編寫(xiě)配置文件

com.cbj.db_work.valid.ListValue.message=必須提交指定的值

在配置文件中可以指定匹配錯(cuò)誤時(shí)顯示的信息,也可以message指定。

(4)postman測(cè)試

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 利用數(shù)組實(shí)現(xiàn)棧(Java實(shí)現(xiàn))

    利用數(shù)組實(shí)現(xiàn)棧(Java實(shí)現(xiàn))

    這篇文章主要為大家詳細(xì)介紹了利用數(shù)組實(shí)現(xiàn)棧,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 解決Spring JPA 使用@transaction注解時(shí)產(chǎn)生CGLIB代理沖突問(wèn)題

    解決Spring JPA 使用@transaction注解時(shí)產(chǎn)生CGLIB代理沖突問(wèn)題

    這篇文章主要介紹了解決Spring JPA 使用@transaction注解時(shí)產(chǎn)生CGLIB代理沖突問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解

    Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解

    這篇文章主要介紹了Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • SpringMVC中文件的上傳與下載詳細(xì)解析

    SpringMVC中文件的上傳與下載詳細(xì)解析

    這篇文章主要介紹了SpringMVC中文件的上傳與下載詳細(xì)解析,在開(kāi)發(fā)中有遇到文件上傳下載的功能需求,今天就來(lái)說(shuō)一下前后端的實(shí)現(xiàn)和要注意的地方,需要的朋友可以參考下
    2024-01-01
  • idea 默認(rèn)路徑修改從C盤更改到D盤

    idea 默認(rèn)路徑修改從C盤更改到D盤

    本文主要介紹了idea 默認(rèn)路徑修改從C盤更改到D盤,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • SpringBoot整合MongoDB的示例

    SpringBoot整合MongoDB的示例

    這篇文章主要介紹了SpringBoot整合MongoDB的示例,幫助大家更好的理解和學(xué)習(xí)springboot框架,感興趣的朋友可以了解下
    2020-10-10
  • MyBatis ResultMap 的基本用法示例詳解

    MyBatis ResultMap 的基本用法示例詳解

    在MyBatis 中,resultMap 用于定義數(shù)據(jù)庫(kù)查詢結(jié)果到Java對(duì)象屬性的映射關(guān)系,本文給大家介紹MyBatis ResultMap的基本用法,感興趣的朋友一起看看吧
    2025-06-06
  • java使用定時(shí)器實(shí)現(xiàn)監(jiān)聽(tīng)數(shù)據(jù)變化

    java使用定時(shí)器實(shí)現(xiàn)監(jiān)聽(tīng)數(shù)據(jù)變化

    這篇文章主要為大家詳細(xì)介紹了Java如何使用定時(shí)器監(jiān)聽(tīng)數(shù)據(jù)變化,當(dāng)滿足某個(gè)條件時(shí)(例如沒(méi)有數(shù)據(jù)更新)自動(dòng)執(zhí)行某項(xiàng)任務(wù),有興趣的可以了解下
    2023-11-11
  • 如何開(kāi)啟控制臺(tái)輸出mybatis執(zhí)行的sql日志問(wèn)題

    如何開(kāi)啟控制臺(tái)輸出mybatis執(zhí)行的sql日志問(wèn)題

    這篇文章主要介紹了如何開(kāi)啟控制臺(tái)輸出mybatis執(zhí)行的sql日志問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • IDEA中的.iml文件和.idea文件夾使用方式

    IDEA中的.iml文件和.idea文件夾使用方式

    這篇文章主要介紹了IDEA中的.iml文件和.idea文件夾使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08

最新評(píng)論