在自己的摸索下对HibernateValidator有了初步的认识,可以使用已有的约束条件对字段做出限制,减少不要代码的出现,使代码更简洁。
但在最近的实际使用中,出现了一些无法使用框架处理的问题,例如,在第三方请求我的接口时,根据status字段区分不同的业务逻辑;status=1进行A逻辑处理,status=2进行B逻辑处理;在网络了检索了相关信息后,做出如下总结。
使用通用Mapper插件生成实体类和mapper,这里为了聚焦对HibernateValidator的使用,把关注点放在实体类Brand中,在实体类中,对status字段添加自定义约束@StatusConstraint
package com.codeup.mybatisjoin.model; import com.codeup.mybatisjoin.validation.StatusConstraint; import lombok.Data; import javax.persistence.Column; import javax.persistence.Id; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; @Data public class Brand { @Id @Column(name = "brand_ID") private Long brandId; /** * 使用`@NotNull`添加约束 */ @Column(name = "vendor_ID") @NotNull(message = "[vendorId]不可为空") private Long vendorId; /** * 使用`@NotBlank`添加约束 */ @Column(name = "brand_name") @NotBlank(message = "[brandName]字段不可为空") private String brandName; private String description; /** * 自定义约束 */ @StatusConstraint(message = "[status]为1或者2") private String status; }
@StatusConstraint
实现步骤和自定义注解相似
package com.codeup.mybatisjoin.validation; 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; /** * @ProjectName: mybatisjoin * @Package: com.codeup.mybatisjoin.validation * @ClassName: StatusConstraint * @Author: lhc * @Description: 状态约束 * @Date: 2019/8/22 下午 3:36 */ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) /** * 指出当前约束是通过`StatusConstraintValidator.class`来实现的 */ @Constraint(validatedBy = StatusConstraintValidator.class) public @interface StatusConstraint { /** * 配置message信息 * @return */ String message() default "违规参数"; /** * 分组 * @return */ Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
通过查看源代码里面的文档得知要要实现 ConstraintValidator
接口
@Documented @Target({ ANNOTATION_TYPE }) @Retention(RUNTIME) public @interface Constraint { /** * {@link ConstraintValidator} classes implementing the constraint. The given classes * must reference distinct target types for a given {@link ValidationTarget}. If two * {@code ConstraintValidator}s refer to the same type, an exception will occur. * <p> * At most one {@code ConstraintValidator} targeting the array of parameters of * methods or constructors (aka cross-parameter) is accepted. If two or more * are present, an exception will occur. * * @return array of {@code ConstraintValidator} classes implementing the constraint */ Class<? extends ConstraintValidator<?, ?>>[] validatedBy(); }
实现 ConstraintValidator
接口,重写 isValid()
方法,实现自己的判断逻辑
package com.codeup.mybatisjoin.validation; import lombok.extern.slf4j.Slf4j; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; /** * @ProjectName: mybatisjoin * @Package: com.codeup.mybatisjoin.validation * @ClassName: StatusConstraintValidator * @Author: lhc * @Description: TODO * @Date: 2019/8/22 下午 3:38 */ @Slf4j public class StatusConstraintValidator implements ConstraintValidator<StatusConstraint,Object> { @Override public boolean isValid(Object value, ConstraintValidatorContext context) { String statCode = (String) value; // 等于1或2返回true,反之 if ("1".equals(statCode) || "2".equals(statCode)) { return true; } return false; } }
至此,对字段添加约束条件已完成。还存在一个重要的操作是对约束的校验,这里通过工具类实现
package com.codeup.mybatisjoin.validation; import org.hibernate.validator.HibernateValidator; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import java.util.Set; /** * @ProjectName: mybatisjoin * @Package: com.codeup.mybatisjoin.validation * @ClassName: ValidatorConfig * @Author: lhc * @Description: TODO * @Date: 2019/8/22 下午 4:31 */ public class ValidatorUtil { /** * 配置hibernate_validator和快速失败模式 */ private static Validator validator = Validation.byProvider(HibernateValidator.class) .configure() .failFast(true) .buildValidatorFactory() .getValidator(); /** * 参数校验,若未匹配约束,则通过已将将之前定义的`message`抛出 * @param object 参数 * @param groups 属于组 */ public static void result(Object object, Class<?>... groups) { Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); if (constraintViolations.size() > 0) { String message = constraintViolations.iterator().next().getMessage(); throw new MissingParameterException(message); } } }
将message信息抛出之后,需要以更统一的方式返回给第三方,使用统一异常处理机制解决
自定义异常类MissingParameterException
package com.codeup.mybatisjoin.validation; public class MissingParameterException extends RuntimeException { public MissingParameterException(String message) { super(message); } }
统一异常处理
package com.codeup.mybatisjoin.validation; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MissingParameterException.class) public Map<String, Object> invalidParameter(MissingParameterException e) { Map<String, Object> map = new HashMap<>(); map.put("code", 500); map.put("message", e.getMessage()); return map; } }
通过请求查看返回结果
package com.codeup.mybatisjoin.controller; import com.codeup.mybatisjoin.model.Brand; import com.codeup.mybatisjoin.validation.ValidatorUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.LinkedHashMap; import java.util.Map; @Slf4j @RestController @RequestMapping(value = "/brandConroller") public class BrandConroller { @PostMapping(value = "/go") public Map<String, Object> go(@RequestBody Brand brand) { ValidatorUtil.result(brand); Map<String, Object> map = new LinkedHashMap<>(); log.info("brand:{}", brand); return map; } }
请求
// request { "brandId": 1, "vendorId": 1, "brandName": "demoData", "description": "demoData", "status": "3" } // response { "code": 500, "message": "[status]为1或者2" }