前言
在自己的摸索下对 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" }