Spring 引导:请求参数中的自定义验证
Spring Boot : Custom Validation in Request Params
我想验证我的控制器中的请求参数之一。请求参数应来自给定值列表之一,否则将抛出错误。在下面的代码中,我希望请求参数 orderBy 来自@ValuesAllowed 中存在的值列表。
@RestController
@RequestMapping("/api/opportunity")
@Api(value = "Opportunity APIs")
@ValuesAllowed(propName = "orderBy", values = { "OpportunityCount", "OpportunityPublishedCount", "ApplicationCount",
"ApplicationsApprovedCount" })
public class OpportunityController {
@GetMapping("/vendors/list")
@ApiOperation(value = "Get all vendors")
public ResultWrapperDTO getVendorpage(@RequestParam(required = false) String term,
@RequestParam(required = false) Integer page, @RequestParam(required = false) Integer size,
@RequestParam(required = false) String orderBy, @RequestParam(required = false) String sortDir) {
我已经编写了一个自定义 bean 验证器,但不知何故这不起作用。即使我为查询参数传递任何随机值,它也不会验证并引发错误。
@Repeatable(ValuesAllowedMultiple.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ValuesAllowedValidator.class})
public @interface ValuesAllowed {
String message() default "Field value should be from list of ";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String propName();
String[] values();
}
public class ValuesAllowedValidator implements ConstraintValidator<ValuesAllowed, Object> {
private String propName;
private String message;
private String[] values;
@Override
public void initialize(ValuesAllowed requiredIfChecked) {
propName = requiredIfChecked.propName();
message = requiredIfChecked.message();
values = requiredIfChecked.values();
}
@Override
public boolean isValid(Object object, ConstraintValidatorContext context) {
Boolean valid = true;
try {
Object checkedValue = BeanUtils.getProperty(object, propName);
if (checkedValue != null) {
valid = Arrays.asList(values).contains(checkedValue.toString().toLowerCase());
}
if (!valid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message.concat(Arrays.toString(values)))
.addPropertyNode(propName).addConstraintViolation();
}
} catch (IllegalAccessException e) {
log.error("Accessor method is not available for class : {}, exception : {}", object.getClass().getName(), e);
return false;
} catch (NoSuchMethodException e) {
log.error("Field or method is not present on class : {}, exception : {}", object.getClass().getName(), e);
return false;
} catch (InvocationTargetException e) {
log.error("An exception occurred while accessing class : {}, exception : {}", object.getClass().getName(), e);
return false;
}
return valid;
}
}
情况一:如果注解ValuesAllowed根本没有被触发,可能是因为没有给控制器注解@Validated。
@Validated
@ValuesAllowed(propName = "orderBy", values = { "OpportunityCount", "OpportunityPublishedCount", "ApplicationCount", "ApplicationsApprovedCount" })
public class OpportunityController {
@GetMapping("/vendors/list")
public String getVendorpage(@RequestParam(required = false) String term,..{
}
情况2:如果被触发并抛出错误,可能是因为BeanUtils.getProperty
没有解析属性而抛出异常。
如果上述解决方案不起作用,您可以尝试将注释移动到方法级别并更新验证器以使用 OrderBy
参数的有效值列表。这对我有用。下面是示例代码。
@RestController
@RequestMapping("/api/opportunity")
@Validated
public class OpportunityController {
@GetMapping("/vendors/list")
public String getVendorpage(@RequestParam(required = false) String term,
@RequestParam(required = false) Integer page, @RequestParam(required = false) Integer size,
@ValuesAllowed(propName = "orderBy", values = { "OpportunityCount", "OpportunityPublishedCount", "ApplicationCount",
"ApplicationsApprovedCount" }) @RequestParam(required = false) String orderBy, @RequestParam(required = false) String sortDir) {
return "success";
}
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { ValuesAllowed.Validator.class })
public @interface ValuesAllowed {
String message() default "Field value should be from list of ";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String propName();
String[] values();
class Validator implements ConstraintValidator<ValuesAllowed, String> {
private String propName;
private String message;
private List<String> allowable;
@Override
public void initialize(ValuesAllowed requiredIfChecked) {
this.propName = requiredIfChecked.propName();
this.message = requiredIfChecked.message();
this.allowable = Arrays.asList(requiredIfChecked.values());
}
public boolean isValid(String value, ConstraintValidatorContext context) {
Boolean valid = value == null || this.allowable.contains(value);
if (!valid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message.concat(this.allowable.toString()))
.addPropertyNode(this.propName).addConstraintViolation();
}
return valid;
}
}
}
您必须更改一些内容才能使此验证生效。
控制器应该用@Validated
注释,@ValuesAllowed
应该在方法中注释目标参数。
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Validated
@RestController
@RequestMapping("/api/opportunity")
public class OpportunityController {
@GetMapping("/vendors/list")
public String getVendorpage(
@RequestParam(required = false)
@ValuesAllowed(values = {
"OpportunityCount",
"OpportunityPublishedCount",
"ApplicationCount",
"ApplicationsApprovedCount"
}) String orderBy,
@RequestParam(required = false) String term,
@RequestParam(required = false) Integer page, @RequestParam(required = false) Integer size,
@RequestParam(required = false) String sortDir) {
return "OK";
}
}
@ValuesAllowed
应该以 ElementType.PARAMETER
为目标,在这种情况下,您不再需要 propName
属性 因为 Spring 将验证所需的参数。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ValuesAllowedValidator.class})
public @interface ValuesAllowed {
String message() default "Field value should be from list of ";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] values();
}
验证者:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
public class ValuesAllowedValidator implements ConstraintValidator<ValuesAllowed, String> {
private List<String> expectedValues;
private String returnMessage;
@Override
public void initialize(ValuesAllowed requiredIfChecked) {
expectedValues = Arrays.asList(requiredIfChecked.values());
returnMessage = requiredIfChecked.message().concat(expectedValues.toString());
}
@Override
public boolean isValid(String testValue, ConstraintValidatorContext context) {
boolean valid = expectedValues.contains(testValue);
if (!valid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(returnMessage)
.addConstraintViolation();
}
return valid;
}
}
但是上面的代码 returns HTTP 500 和丑陋的堆栈跟踪污染了日志。为避免这种情况,您可以将这样的 @ExceptionHandler
方法放在控制器主体中(因此它将仅限于此控制器)并且您可以控制 HTTP 状态:
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
String handleConstraintViolationException(ConstraintViolationException e) {
return "Validation error: " + e.getMessage();
}
... 或者您可以将此方法放在单独的 @ControllerAdvice
class 中,并更好地控制此验证,例如在所有控制器或仅在所需控制器上使用它。
我想验证我的控制器中的请求参数之一。请求参数应来自给定值列表之一,否则将抛出错误。在下面的代码中,我希望请求参数 orderBy 来自@ValuesAllowed 中存在的值列表。
@RestController
@RequestMapping("/api/opportunity")
@Api(value = "Opportunity APIs")
@ValuesAllowed(propName = "orderBy", values = { "OpportunityCount", "OpportunityPublishedCount", "ApplicationCount",
"ApplicationsApprovedCount" })
public class OpportunityController {
@GetMapping("/vendors/list")
@ApiOperation(value = "Get all vendors")
public ResultWrapperDTO getVendorpage(@RequestParam(required = false) String term,
@RequestParam(required = false) Integer page, @RequestParam(required = false) Integer size,
@RequestParam(required = false) String orderBy, @RequestParam(required = false) String sortDir) {
我已经编写了一个自定义 bean 验证器,但不知何故这不起作用。即使我为查询参数传递任何随机值,它也不会验证并引发错误。
@Repeatable(ValuesAllowedMultiple.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ValuesAllowedValidator.class})
public @interface ValuesAllowed {
String message() default "Field value should be from list of ";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String propName();
String[] values();
}
public class ValuesAllowedValidator implements ConstraintValidator<ValuesAllowed, Object> {
private String propName;
private String message;
private String[] values;
@Override
public void initialize(ValuesAllowed requiredIfChecked) {
propName = requiredIfChecked.propName();
message = requiredIfChecked.message();
values = requiredIfChecked.values();
}
@Override
public boolean isValid(Object object, ConstraintValidatorContext context) {
Boolean valid = true;
try {
Object checkedValue = BeanUtils.getProperty(object, propName);
if (checkedValue != null) {
valid = Arrays.asList(values).contains(checkedValue.toString().toLowerCase());
}
if (!valid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message.concat(Arrays.toString(values)))
.addPropertyNode(propName).addConstraintViolation();
}
} catch (IllegalAccessException e) {
log.error("Accessor method is not available for class : {}, exception : {}", object.getClass().getName(), e);
return false;
} catch (NoSuchMethodException e) {
log.error("Field or method is not present on class : {}, exception : {}", object.getClass().getName(), e);
return false;
} catch (InvocationTargetException e) {
log.error("An exception occurred while accessing class : {}, exception : {}", object.getClass().getName(), e);
return false;
}
return valid;
}
}
情况一:如果注解ValuesAllowed根本没有被触发,可能是因为没有给控制器注解@Validated。
@Validated
@ValuesAllowed(propName = "orderBy", values = { "OpportunityCount", "OpportunityPublishedCount", "ApplicationCount", "ApplicationsApprovedCount" })
public class OpportunityController {
@GetMapping("/vendors/list")
public String getVendorpage(@RequestParam(required = false) String term,..{
}
情况2:如果被触发并抛出错误,可能是因为BeanUtils.getProperty
没有解析属性而抛出异常。
如果上述解决方案不起作用,您可以尝试将注释移动到方法级别并更新验证器以使用 OrderBy
参数的有效值列表。这对我有用。下面是示例代码。
@RestController
@RequestMapping("/api/opportunity")
@Validated
public class OpportunityController {
@GetMapping("/vendors/list")
public String getVendorpage(@RequestParam(required = false) String term,
@RequestParam(required = false) Integer page, @RequestParam(required = false) Integer size,
@ValuesAllowed(propName = "orderBy", values = { "OpportunityCount", "OpportunityPublishedCount", "ApplicationCount",
"ApplicationsApprovedCount" }) @RequestParam(required = false) String orderBy, @RequestParam(required = false) String sortDir) {
return "success";
}
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { ValuesAllowed.Validator.class })
public @interface ValuesAllowed {
String message() default "Field value should be from list of ";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String propName();
String[] values();
class Validator implements ConstraintValidator<ValuesAllowed, String> {
private String propName;
private String message;
private List<String> allowable;
@Override
public void initialize(ValuesAllowed requiredIfChecked) {
this.propName = requiredIfChecked.propName();
this.message = requiredIfChecked.message();
this.allowable = Arrays.asList(requiredIfChecked.values());
}
public boolean isValid(String value, ConstraintValidatorContext context) {
Boolean valid = value == null || this.allowable.contains(value);
if (!valid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message.concat(this.allowable.toString()))
.addPropertyNode(this.propName).addConstraintViolation();
}
return valid;
}
}
}
您必须更改一些内容才能使此验证生效。
控制器应该用@Validated
注释,@ValuesAllowed
应该在方法中注释目标参数。
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Validated
@RestController
@RequestMapping("/api/opportunity")
public class OpportunityController {
@GetMapping("/vendors/list")
public String getVendorpage(
@RequestParam(required = false)
@ValuesAllowed(values = {
"OpportunityCount",
"OpportunityPublishedCount",
"ApplicationCount",
"ApplicationsApprovedCount"
}) String orderBy,
@RequestParam(required = false) String term,
@RequestParam(required = false) Integer page, @RequestParam(required = false) Integer size,
@RequestParam(required = false) String sortDir) {
return "OK";
}
}
@ValuesAllowed
应该以 ElementType.PARAMETER
为目标,在这种情况下,您不再需要 propName
属性 因为 Spring 将验证所需的参数。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ValuesAllowedValidator.class})
public @interface ValuesAllowed {
String message() default "Field value should be from list of ";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] values();
}
验证者:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
public class ValuesAllowedValidator implements ConstraintValidator<ValuesAllowed, String> {
private List<String> expectedValues;
private String returnMessage;
@Override
public void initialize(ValuesAllowed requiredIfChecked) {
expectedValues = Arrays.asList(requiredIfChecked.values());
returnMessage = requiredIfChecked.message().concat(expectedValues.toString());
}
@Override
public boolean isValid(String testValue, ConstraintValidatorContext context) {
boolean valid = expectedValues.contains(testValue);
if (!valid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(returnMessage)
.addConstraintViolation();
}
return valid;
}
}
但是上面的代码 returns HTTP 500 和丑陋的堆栈跟踪污染了日志。为避免这种情况,您可以将这样的 @ExceptionHandler
方法放在控制器主体中(因此它将仅限于此控制器)并且您可以控制 HTTP 状态:
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
String handleConstraintViolationException(ConstraintViolationException e) {
return "Validation error: " + e.getMessage();
}
... 或者您可以将此方法放在单独的 @ControllerAdvice
class 中,并更好地控制此验证,例如在所有控制器或仅在所需控制器上使用它。