校验及示例
通过注解约定字段如何进行校验,然后在controller层进行校验以及校验错误的输出。 示例代码:
(@NotBlank
表示字段不能为空且长度大于0)
@Data
public class ScreenInputVo implements IDataConvertInterface{
@NotBlank(message = "大屏名称不能为空")
private String compName;
}
@PostMapping("/addBaseScreen")
MVCResult add(@RequestBody @Valid ScreenInputVo model, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return MVCResult.fail(oper, ValidErrorUtils.toAssignedError(bindingResult));
}
}
ValidErrorUtils 是一个简单的工具类,用于统一输出错误信息
public class ValidErrorUtils {
public static String toError(BindingResult bindingResult) {
if (bindingResult != null) {
List<ObjectError> ls = bindingResult.getAllErrors();
if (ls.size() > 0) {
ObjectError objectError = ls.get(0);
if (objectError instanceof FieldError) {
return ((FieldError) objectError).getField() + ":" + objectError.getDefaultMessage();
}
return objectError.getDefaultMessage();
}
}
return null;
}
public static String toAssignedError(BindingResult bindingResult) {
if (bindingResult != null) {
List<ObjectError> ls = bindingResult.getAllErrors();
if (ls.size() > 0) {
ObjectError objectError = ls.get(0);
return objectError.getDefaultMessage();
}
}
return null;
}
}
解释和说明
这篇文章 总结的不错,下面是一些转载
必要性
车险项目,有计划做To B业务,作为接口服务提供方,就非常有必要在项目中加入参数校验模块,限制入口参数。例如:车型查询接口-110,可对车辆信息对象carInfo
中的车架号、车牌号、发动机号、品牌型号验参。因此,引入Spring Validation框架。
Spring 4.0支持Bean Validation 1.0(JSR-303)
和Bean Validation 1.1(JSR-349)
,提供@Validator
注解,并且能够通过BindingResult类在Controller层的处理方法中取得错误信息。
import java.util.HashMap;
import java.util.Map;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.journaldev.spring.form.model.Customer;
@Controller
public class CustomerController {
private static final Logger logger = LoggerFactory
.getLogger(CustomerController.class);
private Map<String, Customer> customers = null;
public CustomerController(){
customers = new HashMap<String, Customer>();
}
@RequestMapping(value = "/cust/save", method = RequestMethod.GET)
public String saveCustomerPage(Model model) {
logger.info("Returning custSave.jsp page");
model.addAttribute("customer", new Customer());
return "custSave";
}
@RequestMapping(value = "/cust/save.do", method = RequestMethod.POST)
public String saveCustomerAction(@Valid Customer customer, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
logger.info("Returning custSave.jsp page");
return "custSave";
}
logger.info("Returning custSaveSuccess.jsp page");
model.addAttribute("customer", customer);
customers.put(customer.getEmail(), customer);
return "custSaveSuccess";
}
}
如何校验
引入Maven依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
Model上加JSR-303注解类型
JSR-303定义的校验类型:
注解 | 描述 |
---|
空检查 | |
@Null | 验证对象是否为null |
@NotNull | 验证对象是否不为null, 无法查检长度为0的字符串 |
@NotBlank | 检查约束字符串是不是Null还有被trim的长度是否大于0,只对字符串,且会去掉前后空格 |
@NotEmpty | 检查约束元素是否为NULL或者是EMPTY |
Booelan检查 | |
@AssertTrue | 验证 Boolean 对象是否为 true |
@AssertFalse | 验证 Boolean 对象是否为 false |
长度检查 | |
@Size(min=, max=) | 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length(min=, max=) | Validates that the annotated string is between min and max included |
日期检查 | |
@Past | 验证 Date 和 Calendar 对象是否在当前时间之前 |
@Future | 验证 Date 和 Calendar 对象是否在当前时间之后 |
@Pattern | 验证 String 对象是否符合正则表达式的规则 |
数值检查 | 建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null |
@Min | 验证 Number 和 String 对象是否大等于指定的值 |
@Max | 验证 Number 和 String 对象是否小等于指定的值 |
@DecimalMax | 被标注的值必须不大于约束中指定的最大值.这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度 |
@DecimalMin | 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度 |
@Digits | 验证 Number 和 String 的构成是否合法 |
@Digits(integer=,fraction=) | 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度 |
@Range(min=, max=) | 检查数字是否介于min和max之间. |
@Valid | 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证) |
@CreditCardNumber | 信用卡验证 |
@Email | 验证是否是邮件地址,如果为null,不进行验证,算通过验证 |
@ScriptAssert(lang= ,script=, alias=) | |
@URL(protocol=,host=, port=,regexp=, flags=) | |
自定义注解,定义车架号的验证:
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { VinNoValidator.class })
public @interface VinNo {
String message() default "车架号不合法「不能包含I、O、Q,第十位不能是U、Z、数字0」";
String regexp() default "^[A-HJ-NP-PR-Z0-9]{9}[A-HJ-NP-PR-TV-Y1-9]{1}[A-HJ-NP-PR-Z0-9]{7}$";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
VinNo[] value();
}
}
public class VinNoValidator implements ConstraintValidator<VinNo, String> {
private String regexp;
@Override
public void initialize(VinNo vinNo) {
this.regexp = vinNo.regexp();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (s == null) {
return true;
}
if (s.matches(regexp)) {
return true;
}
return false;
}
}
Model上的注解
@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class VehicleInfo {
@NotNull(message = "{NotNull.cityCode}", groups = {Group110.class})
private Long cityCode;
private String comCode;
@LicenseNo(groups = {Group110.class})
private String licenseNo;
@ZeroOrOne(groups = {Group110.class})
@NotNull(message = "{NotNull.noLicenseFlag}", groups = {Group110.class} )
private Integer noLicenseFlag;
private Integer vehicleNewFlag;
@DecimalMin(value= "0", groups = {Group110.class})
private String vehiclePrice;
private Date buyInvoiceDate;
@DecimalMin(value= "0", groups = {Group110.class})
private Double vehiclePriceTax;
private String vehicleActualPrice;
@VinNo(groups = {GroupDefault.class, Group110.class})
private String vehicleFrameNo;
@EngineNo(groups = {GroupDefault.class, Group110.class})
private String engineNo;
private String carProperty;
private Date firstRegisterDate;
@ZeroOrOne(groups = {Group110.class})
@NotNull(message = "{NotNull.transferFlag}", groups = {Group110.class} )
private Integer transferFlag;
private Date transferDate;
private String vehicleModelName;
private String vehicleBrandName;
private String vehicleFamily;
private String vehicleDesc;
private String vehicleId;
private String tradeVehicleId;
private String noticeNo;
}
applicationContext.xml文件中加入如下配置:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
<property name="validationMessageSource" ref="messageSource" />
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:ValidationMessages"/>
<property name="useCodeAsDefaultMessage" value="true" />
<property name="fileEncodings" value="utf-8" />
</bean>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
配置ValidationMessages.properties文件,文件名一定要是ValidationMessages,使用其他名字不能读取到,也不知是何原因
NotNull.insureComCode=供应商代码不能为空
Size.transId=transId长度不能超过{max}
NotEmpty.transId=transId不允许空
NotNull.transId=transId不允许NULL
NotEmpty.partnerCode=partnerCode不允许空
NotNull.partnerCode=partnerCode不允许NULL
NotEmpty.operationCode=operationCode不允许空
NotNull.operationCode=operationCode不允许空
NotNull.cityCode=城市代码cityCode不允许空
NotNull.transferFlag=transferFlag不允许空
NotNull.provCode=provCode不允许空
NotNull.noLicenseFlag=noLicenseFlag不允许空
加入@Validated,就大工搞成了
@RestController
@RequestMapping("carInsure")
public class StandardCarController {
private static Logger logger = LoggerFactory.getLogger(StandardCarController.class);
@Autowired
protected MongoTemplate mongoTemplate;
@Autowired
protected InsuranceRecordService insuranceRecordService;
@Autowired
private InsuranceSvcBeanRoutingService routeService;
@RequestMapping(value = "autoQuote", method = RequestMethod.POST)
@ResponseBody
public RestResponse<CarMultiRespWrapper> autoQuote(@RequestParam String params, @RequestParam String partnerCode) throws Exception {
String prodCode = CarConstants.CAR_PROD_CODE;
CarRequestBean request = JSON.parseObject(JsonLibUtil.removeUndefinedKey(params), CarRequestBean.class);
Integer operationCode = request.getChannelInfo().getOperationCode();
String serialNo = SerialNoUtil.createInsuranceSerialNo(partnerCode);
CarMultiInsureService service = InsureServiceFactory.INSTANCE.getCarMultiInsureService(prodCode, routeService.getInsuranceSvcBeanGroupId(prodCode, operationCode), serialNo);
if (service == null) {
throw new InsureCustomException(CarConstants.RET_MSG_INVALID_PRODCODE);
}
ParameterValidateResult parameterValidateResult = service.verifyParams(request);
if (!parameterValidateResult.isValidate()) {
throw new InsureCustomException(parameterValidateResult.getResultMsg());
}
InsuranceContext insuranceContext = InsuranceContextFactory.getCarMultiPriceContext(partnerCode, serialNo, operationCode, prodCode, params, new Date());
CarMultiRespWrapper carMultiRespWrapper = service.doInsure(request, insuranceContext);
if (insuranceContext != null) {
insuranceContext.getInsuranceAnalyzer().setRequestEndTime(new Date());
this.insuranceRecordService.saveInsureDatagram(insuranceContext);
}
return RestFulUtil.toCarSuccessResponse(carMultiRespWrapper);
}
}
加入@Validated,就大工搞成了
@RestController
@RequestMapping("carInsure")
public class StandardCarController {
private static Logger logger = LoggerFactory.getLogger(StandardCarController.class);
@Autowired
protected MongoTemplate mongoTemplate;
@Autowired
protected InsuranceRecordService insuranceRecordService;
@Autowired
private InsuranceSvcBeanRoutingService routeService;
@RequestMapping(value = "autoQuote", method = RequestMethod.POST)
@ResponseBody
public RestResponse<CarMultiRespWrapper> autoQuote(@RequestParam String params, @RequestParam String partnerCode) throws Exception {
String prodCode = CarConstants.CAR_PROD_CODE;
CarRequestBean request = JSON.parseObject(JsonLibUtil.removeUndefinedKey(params), CarRequestBean.class);
Integer operationCode = request.getChannelInfo().getOperationCode();
String serialNo = SerialNoUtil.createInsuranceSerialNo(partnerCode);
CarMultiInsureService service = InsureServiceFactory.INSTANCE.getCarMultiInsureService(prodCode, routeService.getInsuranceSvcBeanGroupId(prodCode, operationCode), serialNo);
if (service == null) {
throw new InsureCustomException(CarConstants.RET_MSG_INVALID_PRODCODE);
}
ParameterValidateResult parameterValidateResult = service.verifyParams(request);
if (!parameterValidateResult.isValidate()) {
throw new InsureCustomException(parameterValidateResult.getResultMsg());
}
InsuranceContext insuranceContext = InsuranceContextFactory.getCarMultiPriceContext(partnerCode, serialNo, operationCode, prodCode, params, new Date());
CarMultiRespWrapper carMultiRespWrapper = service.doInsure(request, insuranceContext);
if (insuranceContext != null) {
insuranceContext.getInsuranceAnalyzer().setRequestEndTime(new Date());
this.insuranceRecordService.saveInsureDatagram(insuranceContext);
}
return RestFulUtil.toCarSuccessResponse(carMultiRespWrapper);
}
}
@Validated
public interface CarMultiInsureService {
ParameterValidateResult verifyParams(CarRequestBean requestBean);
@Validated({GroupDefault.class})
CarMultiRespWrapper doInsure(@Valid CarRequestBean requestBean, InsuranceContext insuranceContext);
}
方便处理校验不通过异常ConstraintViolation,写了个RestFulUtil类处理
public class RestFulUtil {
public static <T> RestResponse<T> toCarSuccessResponse(CarMultiRespWrapper carMultiRespWrapper) {
RestResponse.Builder builder = new RestResponse.Builder();
builder.setStatus(HttpStatus.OK.value());
builder.setCode(Constants.CAR_INSURE_SUCCEED.intValue());
builder.setMessage(CarConstants.CAR_MSG_INSURE_SUCCESS);
builder.setMessageDetail("");
if (carMultiRespWrapper != null) {
builder.setData(carMultiRespWrapper);
}
return builder.build();
}
public static String getConstraintViolationExMsg(ConstraintViolationException ex) {
List<FieldError> errors = FieldError.getErrors(ex.getConstraintViolations());
String errorMsg = "";
for (FieldError error : errors) {
errorMsg += error.getMessage() + ";";
}
return errorMsg;
}
}
public class FieldError {
private String field;
private String code;
private String message;
public static List<FieldError> getErrors(Set<ConstraintViolation<?>> constraintViolations) {
List<FieldError> list = new ArrayList<>();
for(ConstraintViolation<?> constraintViolation : constraintViolations) {
list.add(of(constraintViolation));
}
return list;
}
private static FieldError of(ConstraintViolation<?> constraintViolation) {
String field = StringUtils.substringAfter(constraintViolation.getPropertyPath().toString(), ".");
return new FieldError(field, constraintViolation.getMessageTemplate(), constraintViolation.getMessage());
}
}
至此,Spring Validation 搞定,增减前置参数校验 只需要在Model加减注解即可。
参考 :
(原文)http://daobin.wang/2017/06/Spring-Validation/
https://www.jianshu.com/p/cbaee928a2f7