AdSense

網頁

2020/4/10

Spring Boot 自訂Controller請求參數Bean Validation

本篇介紹如何在Spring Boot使用自訂Java Bean Validation驗證器(custom Java Bean validation)對Controller的請求物件欄位(field)進行格式驗證。

範例環境:

  • Windows 64 Bit
  • Eclipse
  • Java 11
  • Spring Boot 2.2.4.RELEASE

首先建立Spring Boot專案

application.properties設定如下。

application.properties

#context path
server.servlet.context-path=/demo
#port
server.port=8080

所以本機路徑為http://localhost:8080/demo


建立EmailValidator做為email信箱格式驗證器。

此類別要實作ConstraintValidator<A extends Annotation,T>
第一個泛型AAnnotation,設為下面的@Email
第二個泛形T為被驗證欄位的型別。由於@Email是掛在DemoRequest.email前來驗證信箱格式,所以設為String

實作isValid(T value, ConstraintValidatorContext context)做為實際的驗證邏輯。返回true代表通過,false代表未通過驗證。

EmailValidator

package com.abc.demo.validation.validator;

import java.util.regex.Pattern;

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

import com.abc.demo.validation.annotation.Email;

public class EmailValidator implements ConstraintValidator<Email, String> {

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        // Email格式正則表示式
        String regex = "^\\w{1,63}@[a-zA-Z0-9]{2,63}\\.[a-zA-Z]{2,63}(\\.[a-zA-Z]{2,63})?$";
        boolean isValid = Pattern.compile(regex).matcher(email).find();
        return isValid;
    }

}


建立@Email annotation注釋。用來掛在請求參數欄位前則該欄位會被EmailValidator驗證。

@Email

package com.abc.demo.validation.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import com.abc.demo.validation.validator.EmailValidator;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { EmailValidator.class }) // 用EmailValidator驗證
public @interface Email {
    
    String message() default "信箱格式錯誤"; // 預設驗證錯誤訊息

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}


建立PasswordValidator做為password密碼格式驗證器。

PasswordValidator

package com.abc.demo.validation.validator;

import java.util.regex.Pattern;

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

import com.abc.demo.validation.annotation.Password;

public class PasswordValidator implements ConstraintValidator<Password, String>{

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        // 至少一個英文,一個數字,長度至少為8個字
        String regex = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$";
        boolean isValid = Pattern.compile(regex).matcher(password).find();
        return isValid;
    }

}

建立@Password annotation注釋。用來掛在請求參數欄位前則該欄位會被PasswordValidator驗證。

@Password

package com.abc.demo.validation.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import com.abc.demo.validation.validator.PasswordValidator;


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { PasswordValidator.class })
public @interface Password {
    
    String message() default "密碼格式錯誤"; // 預設驗證錯誤訊息
    
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

建立DemoRequest為Request請求參數物件。在email前加上@Email;在password前加上@Password

DemoRequest

package com.abc.demo.controller.request;

import com.abc.demo.validation.annotation.Email;
import com.abc.demo.validation.annotation.Password;

public class DemoRequest {
    
    @Email // 驗證email格式
    private String email;

    @Password // 驗證password格式
    private String password;
    
    // getters and setters...
    
}

建立DemoController用來接收Request請求。要被驗證的請求參數前必須加上@Valid才會進行驗證。

DemoController

package com.abc.demo.controller;

import javax.validation.Valid;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.abc.demo.controller.request.DemoRequest;

@RestController
public class DemoController {
    
    @PostMapping(value = "/send")
    public void send (@Valid @RequestBody DemoRequest request) {
        
        System.out.println("pass"); // 如果通過自訂的欄位驗證器才會印出
        
    }

}

完成以上後啟動專案,使用Postman送出下面符合驗證格式的請求。

POST | http://localhost:8080/demo/send
Body:raw:JSON

{
    "email": "test@abc.com",
    "password": "abcd1234"
}

送出的信箱及密碼格式皆符合驗證規則,所以會通過驗證並在console印出"pass"。


改用Postman送出不符合驗證格式的請求。

POST | http://localhost:8080/demo/send
Body:raw:JSON

{
    "email": "test@abc",
    "password": "1234"
}

由於兩個參數格式皆不符無法通過驗證,返回結果如下。

{
    "timestamp": "2020-01-29T12:37:54.445+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Password.demoRequest.password",
                "Password.password",
                "Password.java.lang.String",
                "Password"
            ],
            "arguments": [
                {
                    "codes": [
                        "demoRequest.password",
                        "password"
                    ],
                    "arguments": null,
                    "defaultMessage": "password",
                    "code": "password"
                }
            ],
            "defaultMessage": "密碼格式錯誤",
            "objectName": "demoRequest",
            "field": "password",
            "rejectedValue": "1234",
            "bindingFailure": false,
            "code": "Password"
        },
        {
            "codes": [
                "Email.demoRequest.email",
                "Email.email",
                "Email.java.lang.String",
                "Email"
            ],
            "arguments": [
                {
                    "codes": [
                        "demoRequest.email",
                        "email"
                    ],
                    "arguments": null,
                    "defaultMessage": "email",
                    "code": "email"
                }
            ],
            "defaultMessage": "信箱格式錯誤",
            "objectName": "demoRequest",
            "field": "email",
            "rejectedValue": "test@abc",
            "bindingFailure": false,
            "code": "Email"
        }
    ],
    "message": "Validation failed for object='demoRequest'. Error count: 2",
    "path": "/demo/send"
}

專案src/main的目錄結構如下。

demo/src/main
├─java
│  └─com
│      └─abc
│          └─demo
│              │  DemoApplication.java
│              │
│              ├─controller
│              │  │  DemoController.java
│              │  │
│              │  └─request
│              │          DemoRequest.java
│              │
│              └─validation
│                  ├─annotation
│                  │      Email.java
│                  │      Password.java
│                  │
│                  └─validator
│                          EmailValidator.java
│                          PasswordValidator.java
│
└─resources
    │  application.properties
    │
    ├─static
    └─templates

參考:

沒有留言:

AdSense