ConstraintValidator類如何實(shí)現(xiàn)自定義注解校驗(yàn)前端傳參
前言
今天項(xiàng)目碰到這么一個(gè)問題,前端傳遞的json格式到我的微服務(wù)后端轉(zhuǎn)換為vo類,其中有一個(gè)Integer的字段后端希望它在固定的幾個(gè)數(shù)里面取值,例如只能取值1、2、4。
一般咱們的思路是啥呢,找一些spring為我們提供的類似@Length、@NotBlank這些注解加在參數(shù)上面。
像下面這樣
不過我這個(gè)校驗(yàn)一時(shí)間想不起來用哪個(gè)注解了,咋整呢?行吧,咱不求人,自己實(shí)現(xiàn)一個(gè)。
補(bǔ)充一句話,千萬別直接拿著實(shí)體類往后傳遞到service層校驗(yàn)哈,太low了這樣子。
一、利用@Constraint定義注解
import java.lang.annotation.Documented; 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; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint( validatedBy = {IllegalNumberValidator.class} ) public @interface IllegalNumber { /** * 允許前端取的幾個(gè)值 * */ int[] acceptValues(); /** * 標(biāo)識(shí)此字段是否為必選項(xiàng) * */ boolean required() default true; /** * 標(biāo)識(shí)此字段是否為必選項(xiàng) * */ String message() default "數(shù)字不合法,不在要求的取值范圍之內(nèi)"; /** * 標(biāo)識(shí)要校驗(yàn)的值所屬組,這個(gè)后面詳細(xì)解釋 * */ Class<?>[] groups() default {}; /** * 這個(gè)字段一般不需要我們關(guān)注 * */ Class<? extends Payload>[] payload() default {}; }
二、增強(qiáng)注解
1.編寫增強(qiáng)類
注意到剛才注解中的@Constraint注解了嗎
validatedBy屬性標(biāo)識(shí)這個(gè)注解要被哪個(gè)類所增強(qiáng),我們把增強(qiáng)類IllegalNumberValidator定義出來
import com.google.common.collect.Lists; import org.springframework.util.StringUtils; import java.util.List; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class IllegalNumberValidator implements ConstraintValidator<IllegalNumber, Integer> { private final List<Integer> valueList = Lists.newArrayList(); private boolean require = false; @Override public void initialize(IllegalNumber constraintAnnotation) { require = constraintAnnotation.required(); int[] ints = constraintAnnotation.acceptValues(); for (int anInt : ints) { valueList.add(anInt); } } @Override public boolean isValid(Integer number, ConstraintValidatorContext constraintValidatorContext) { // 如果是必選的話,假設(shè)為我們傳遞的參數(shù)為空那肯定不行 if (require) { if (number == null) { return false; } return valueList.contains(number); } else { // 如果不為必選參數(shù),為空返回true,不為空還是得校驗(yàn) if (StringUtils.isEmpty(number)) { return true; } else { return valueList.contains(number); } } } }
增強(qiáng)類繼承ConstraintValidator類,實(shí)現(xiàn)的initialize()方法是初始化方法,啥意思呢,啥目的呢?在你真正執(zhí)行校驗(yàn)之前,可以做一些準(zhǔn)備性工作,發(fā)生在要校驗(yàn)的值上面的注解的IllegalNumber 已經(jīng)給咱們傳進(jìn)來了。我做的初始化工作就是load一下Integer類型的可選值,方便一會(huì)執(zhí)行真正的校驗(yàn)。
然后在isValid()方法中你可以做真正的校驗(yàn)了,很簡(jiǎn)單,我看下傳遞的Integer類型的值是不是acceptValues里面的可選值就行了。
定義一個(gè)前端傳遞的類,方便調(diào)試注解
import lombok.Data; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.NotNull; @Data public class TestVO { @NotNull @IllegalNumber(acceptValues = {0, 1,2},required = true,message = "請(qǐng)正確取值") private Integer number; @NotNull @Length(min = 1) private String password; }
定義接口,用來接收前端傳遞的json數(shù)據(jù)并parse為TestVO類
/** * 測(cè)試自定義注解 * * @param vo json將會(huì)映射的實(shí)體 * @return 默認(rèn)信息 */ @PostMapping(value = "/v1.0/test2", name = "測(cè)試自定義注解") public String test2(@Valid @RequestBody TestVO vo) { log.info("get vo success , detail message is:{}", vo); return RETURN_MESSAGE; }
注意,如果說前端傳遞數(shù)據(jù)不符合注解的校驗(yàn),其實(shí)是會(huì)拋出異常的來自@Constraint注解實(shí)現(xiàn)的注解都有此特點(diǎn),例如@Length、@Max等。咱們需要在異常拋出的時(shí)候給出攔截 這里咱們做一個(gè)通用攔截:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; 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 org.springframework.web.bind.annotation.ResponseStatus; import java.util.Objects; import javax.validation.ConstraintViolationException; @ControllerAdvice public class RestResponseEntityExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class); @Autowired private ApplicationContext applicationContext; @ExceptionHandler({ConstraintViolationException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public String handleConstraintViolationException(ConstraintViolationException e) { LOG.info("ConstraintViolationException intercept success:{}", e.getMessage()); return e.getMessage(); } @ExceptionHandler({MethodArgumentNotValidException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public String handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { LOG.info("MethodArgumentNotValidException intercept success:{}", e.getMessage()); return Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage(); } }
2.測(cè)試效果
下面測(cè)試一下。打開postman。直接干!取值的限定是0、1、2。咱們先試下錯(cuò)誤的
ok,再試下正確的
3.注解中的groups參數(shù)詳解
groups參數(shù),代表所屬組的意思。演示下怎么用,大家也就知道這個(gè)參數(shù)啥意思了。 建立Group1接口
public interface Group1 { }
建立Group2接口
public interface Group2 { }
給TestVO增加一個(gè)參數(shù),方便一會(huì)進(jìn)行比較
@Data public class TestVO { @NotNull @IllegalNumber(acceptValues = {0, 1,2},required = true,message = "請(qǐng)正確取值",groups = Group1.class) private Integer number; @NotNull @IllegalNumber(acceptValues = {0, 1,2},required = true,message = "請(qǐng)正確取值ha",groups = Group2.class) private Integer number2; @NotNull @Length(min = 1) private String password; }
使用注解的時(shí)候標(biāo)明所屬組:
接口處也進(jìn)行標(biāo)識(shí):
現(xiàn)在咱們分別測(cè)試下兩個(gè)接口,看groups參數(shù)是否能生效
test2接口
test3接口
ok,相信大家對(duì)此參數(shù)已經(jīng)掌握了,這里不再多余贅述。
總結(jié)
本篇介紹了自定義注解的另外一種手法,其實(shí)還有許許多多的手法,例如利用反射實(shí)現(xiàn)、利用攔截器實(shí)現(xiàn)等等。遇見的時(shí)候咱們?cè)俳榻B。 以上僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家
相關(guān)文章
springboot整合logback實(shí)現(xiàn)日志管理操作
本章節(jié)是記錄logback在springboot項(xiàng)目中的簡(jiǎn)單使用,本文將會(huì)演示如何通過logback將日志記錄到日志文件或輸出到控制臺(tái)等管理操作,感興趣的朋友跟隨小編一起看看吧2024-02-02spring profile 多環(huán)境配置管理詳解
這篇文章主要介紹了 spring profile 多環(huán)境配置管理詳解的相關(guān)資料,需要的朋友可以參考下2017-01-01詳解Java如何實(shí)現(xiàn)基于Redis的分布式鎖
在不同進(jìn)程需要互斥地訪問共享資源時(shí),分布式鎖是一種非常有用的技術(shù)手段。這篇文章運(yùn)用圖文和實(shí)例代碼介紹了Java如何實(shí)現(xiàn)基于Redis的分布式鎖,文章介紹的很詳細(xì),對(duì)Java和Redis剛興趣的朋友們可以參考借鑒,下面來一起看看。2016-08-08netflix.discovery.shared.transport.TransportException:Cannot
這篇文章主要介紹了netflix.discovery.shared.transport.TransportException:Cannot execute request on any known server報(bào)錯(cuò)問題及解決方法,感興趣的朋友一起看看吧2023-09-09java使用mybatis調(diào)用存儲(chǔ)過程返回一個(gè)游標(biāo)結(jié)果集方式
這篇文章主要介紹了java使用mybatis調(diào)用存儲(chǔ)過程返回一個(gè)游標(biāo)結(jié)果集方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Mybatis-Plus處理Mysql?Json類型字段的詳細(xì)教程
這篇文章主要給大家介紹了關(guān)于Mybatis-Plus處理Mysql?Json類型字段的詳細(xì)教程,Mybatis-Plus可以很方便地處理JSON字段,在實(shí)體類中可以使用@JSONField注解來標(biāo)記JSON字段,同時(shí)在mapper.xml中使用json函數(shù)來操作JSON字段,需要的朋友可以參考下2024-01-01JavaScript中HTML元素操作的實(shí)現(xiàn)
本文主要介紹了JavaScript中HTML元素操作的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06