Two ways SpringBoot processing validation logic, really very resourceful!

SpringBoot actual electricity supplier item mall (30k + star) Address: github.com/macrozheng/...

Summary

Usually, when the development interface, often need to check the parameters, verification processing logic provides two ways here. One is to use Hibernate Validator to handle, the other is to use global exceptions to handle, let's talk about the use of these two methods.

Hibernate Validator

Hibernate Validator is SpringBoot built-in validation framework, as long as the integrated SpringBoot automatically integrates it, we can complete the verification parameters by using annotations provided in the object on top of it.

Common Annotations

Let's learn about common notes, there is the impression that check function Hibernate Validator provides.

  • @Null: annotated property must be null;
  • @NotNull: annotated property can not be null;
  • @AssertTrue: annotated property must be true;
  • @AssertFalse: annotated property must be false;
  • @Min: annotated attribute whose value must be greater than or equal value;
  • @Max: annotated attribute whose value must be less than or equal value;
  • @Size: attribute must be annotated between min and max values;
  • @Pattern: annotated property must conform to the regular expression regexp its defined;
  • @NotBlank: annotated string can not be the empty string;
  • @NotEmpty: annotated properties can not be empty;
  • @Email: annotated property must conform to the mailbox format.

Use

Next we add the brand to check, for example in terms of interface parameters to use Hibernate Validator took off, which involves some knowledge of AOP, can refer friends do not understand "SpringBoot applications using AOP interface to access log records" .

  • First, we need to add the brand interface parameters PmsBrandParamadd annotations check for information to determine the properties of validation rules and check after the failure need to return;
/**
 * 品牌传递参数
 * Created by macro on 2018/4/26.
 */
public class PmsBrandParam {
    @ApiModelProperty(value = "品牌名称",required = true)
    @NotEmpty(message = "名称不能为空")
    private String name;
    @ApiModelProperty(value = "品牌首字母")
    private String firstLetter;
    @ApiModelProperty(value = "排序字段")
    @Min(value = 0, message = "排序最小为0")
    private Integer sort;
    @ApiModelProperty(value = "是否为厂家制造商")
    @FlagValidator(value = {"0","1"}, message = "厂家状态不正确")
    private Integer factoryStatus;
    @ApiModelProperty(value = "是否进行显示")
    @FlagValidator(value = {"0","1"}, message = "显示状态不正确")
    private Integer showStatus;
    @ApiModelProperty(value = "品牌logo",required = true)
    @NotEmpty(message = "品牌logo不能为空")
    private String logo;
    @ApiModelProperty(value = "品牌大图")
    private String bigPic;
    @ApiModelProperty(value = "品牌故事")
    private String brandStory;

   //省略若干Getter和Setter方法...
}
复制代码
  • Then add annotations to add brand @Validated interface, and injected a BindingResult parameters;
/**
 * 品牌功能Controller
 * Created by macro on 2018/4/26.
 */
@Controller
@Api(tags = "PmsBrandController", description = "商品品牌管理")
@RequestMapping("/brand")
public class PmsBrandController {
    @Autowired
    private PmsBrandService brandService;

    @ApiOperation(value = "添加品牌")
    @RequestMapping(value = "/create", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult create(@Validated @RequestBody PmsBrandParam pmsBrand, BindingResult result) {
        CommonResult commonResult;
        int count = brandService.createBrand(pmsBrand);
        if (count == 1) {
            commonResult = CommonResult.success(count);
        } else {
            commonResult = CommonResult.failed();
        }
        return commonResult;
    }
}
复制代码
  • Then create a cut in the whole Controller layer, which surrounds the notification acquires the object to BindingResult injected, determined by the method of checking whether by hasErrors, if an error message directly returns an error message, verification is released;
/**
 * HibernateValidator错误结果处理切面
 * Created by macro on 2018/4/26.
 */
@Aspect
@Component
@Order(2)
public class BindingResultAspect {
    @Pointcut("execution(public * com.macro.mall.controller.*.*(..))")
    public void BindingResult() {
    }

    @Around("BindingResult()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof BindingResult) {
                BindingResult result = (BindingResult) arg;
                if (result.hasErrors()) {
                    FieldError fieldError = result.getFieldError();
                    if(fieldError!=null){
                        return CommonResult.validateFailed(fieldError.getDefaultMessage());
                    }else{
                        return CommonResult.validateFailed();
                    }
                }
            }
        }
        return joinPoint.proceed();
    }
}
复制代码
  • At this point we add brand access interface is not the incoming namefield, it will return 名称不能为空an error message;

Custom annotation

Sometimes annotation framework provides a check does not meet our needs, then we need custom validation annotation. For example, add brand or above, then there are arguments showStatus, we can only hope it is 0 or 1, can not be other numbers, then you can use custom annotations to implement this function.

  • First, a check annotation custom class FlagValidator, then add annotations @Constraint, using its validatedBy attribute specifies the implementation class checking logic;
/**
 * 用户验证状态是否在指定范围内的注解
 * Created by macro on 2018/4/26.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Constraint(validatedBy = FlagValidatorClass.class)
public @interface FlagValidator {
    String[] value() default {};

    String message() default "flag is not found";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
复制代码
  • Then create FlagValidatorClass implementation class as a check logic, implemented ConstraintValidator interfaces, there needs to specify two generic parameter, you first need to specify a custom validation annotation class, the second property to be verified is designated as you type, isValid method is a specific validation logic.
/**
 * 状态标记校验器
 * Created by macro on 2018/4/26.
 */
public class FlagValidatorClass implements ConstraintValidator<FlagValidator,Integer> {
    private String[] values;
    @Override
    public void initialize(FlagValidator flagValidator) {
        this.values = flagValidator.value();
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        boolean isValid = false;
        if(value==null){
            //当状态为空时使用默认值
            return true;
        }
        for(int i=0;i<values.length;i++){
            if(values[i].equals(String.valueOf(value))){
                isValid = true;
                break;
            }
        }
        return isValid;
    }
}
复制代码
  • Then we can use the annotation in mass participation object;
/**
 * 品牌传递参数
 * Created by macro on 2018/4/26.
 */
public class PmsBrandParam {

    @ApiModelProperty(value = "是否进行显示")
    @FlagValidator(value = {"0","1"}, message = "显示状态不正确")
    private Integer showStatus;

   //省略若干Getter和Setter方法...
}
复制代码
  • Finally, we tested this comment, the interface is an incoming call showStatus=3, it will return 显示状态不正确an error message.

Advantages and disadvantages

The advantage of this is that annotations may be used to implement the parameter verification, validation logic does not require some duplication, but also has some disadvantages, such as a need for additional injection BindingResult Controller object method, only support some simple checksum, check involved to query the database can not be satisfied.

Global exception handler

Use global exception handler to handle validation logic idea is very simple, first of all we need to @ControllerAdvice annotations define a global exception handler classes, and then customize a verification error when we check failure in the Controller, direct throw the exception, so that you can achieve the purpose of validation fails to return the error message.

Use the comment

@ControllerAdvice: @Component similar notes, you can specify a component, this component is mainly used to enhance @Controller modified functional class annotation, such conduct global exception handler.

@ExceptionHandler: A method for modifying global exception handler can specify the type of anomaly.

Use

  • First we need to define a custom exception class ApiExceptionThrown when we check failed:
/**
 * 自定义API异常
 * Created by macro on 2020/2/27.
 */
public class ApiException extends RuntimeException {
    private IErrorCode errorCode;

    public ApiException(IErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }

    public ApiException(String message) {
        super(message);
    }

    public ApiException(Throwable cause) {
        super(cause);
    }

    public ApiException(String message, Throwable cause) {
        super(message, cause);
    }

    public IErrorCode getErrorCode() {
        return errorCode;
    }
}
复制代码
  • Then create a class assertion treatment Assertsfor a variety of throws ApiException;
/**
 * 断言处理类,用于抛出各种API异常
 * Created by macro on 2020/2/27.
 */
public class Asserts {
    public static void fail(String message) {
        throw new ApiException(message);
    }

    public static void fail(IErrorCode errorCode) {
        throw new ApiException(errorCode);
    }
}
复制代码
  • We then create global exception handler class GlobalExceptionHandler, for global exception handling, and returns CommonResult encapsulated objects;
/**
 * 全局异常处理
 * Created by macro on 2020/2/27.
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = ApiException.class)
    public CommonResult handle(ApiException e) {
        if (e.getErrorCode() != null) {
            return CommonResult.failed(e.getErrorCode());
        }
        return CommonResult.failed(e.getMessage());
    }
}
复制代码
  • Here take the user to receive a coupon code as an example, let's compare the code before and after the improvements under the first look Controller layer code. The improved method as long as the successful implementation of Service says that to receive coupons success, because if unsuccessful receive a direct throw ApiException to return an error message;
/**
 * 用户优惠券管理Controller
 * Created by macro on 2018/8/29.
 */
@Controller
@Api(tags = "UmsMemberCouponController", description = "用户优惠券管理")
@RequestMapping("/member/coupon")
public class UmsMemberCouponController {
    @Autowired
    private UmsMemberCouponService memberCouponService;
    
    //改进前
    @ApiOperation("领取指定优惠券")
    @RequestMapping(value = "/add/{couponId}", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult add(@PathVariable Long couponId) {
        return memberCouponService.add(couponId);
    }
    
    //改进后
    @ApiOperation("领取指定优惠券")
    @RequestMapping(value = "/add/{couponId}", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult add(@PathVariable Long couponId) {
        memberCouponService.add(couponId);
        return CommonResult.success(null,"领取成功");
    }    
}
复制代码
  • Look at the code in the Service interface, except that the returned result, the improved returns void. In fact CommonResult role has always been to the Service in the captured data into a unified package returns the result, contrary to the practice before the improvements to this principle, the practice improved solution to this problem;
/**
 * 用户优惠券管理Service
 * Created by macro on 2018/8/29.
 */
public interface UmsMemberCouponService {
    /**
     * 会员添加优惠券(改进前)
     */
    @Transactional
    CommonResult add(Long couponId);

    /**
     * 会员添加优惠券(改进后)
     */
    @Transactional
    void add(Long couponId);
}
复制代码
  • Look at the code class Service implementation, validation logic can be seen in the original logic returns CommonResult the method call into a Asserts fail to achieve;
/**
 * 会员优惠券管理Service实现类
 * Created by macro on 2018/8/29.
 */
@Service
public class UmsMemberCouponServiceImpl implements UmsMemberCouponService {
    @Autowired
    private UmsMemberService memberService;
    @Autowired
    private SmsCouponMapper couponMapper;
    @Autowired
    private SmsCouponHistoryMapper couponHistoryMapper;
    @Autowired
    private SmsCouponHistoryDao couponHistoryDao;
    
    //改进前
    @Override
    public CommonResult add(Long couponId) {
        UmsMember currentMember = memberService.getCurrentMember();
        //获取优惠券信息,判断数量
        SmsCoupon coupon = couponMapper.selectByPrimaryKey(couponId);
        if(coupon==null){
            return CommonResult.failed("优惠券不存在");
        }
        if(coupon.getCount()<=0){
            return CommonResult.failed("优惠券已经领完了");
        }
        Date now = new Date();
        if(now.before(coupon.getEnableTime())){
            return CommonResult.failed("优惠券还没到领取时间");
        }
        //判断用户领取的优惠券数量是否超过限制
        SmsCouponHistoryExample couponHistoryExample = new SmsCouponHistoryExample();
        couponHistoryExample.createCriteria().andCouponIdEqualTo(couponId).andMemberIdEqualTo(currentMember.getId());
        long count = couponHistoryMapper.countByExample(couponHistoryExample);
        if(count>=coupon.getPerLimit()){
            return CommonResult.failed("您已经领取过该优惠券");
        }
        //省略领取优惠券逻辑...
        return CommonResult.success(null,"领取成功");
    }
    
    //改进后
     @Override
     public void add(Long couponId) {
         UmsMember currentMember = memberService.getCurrentMember();
         //获取优惠券信息,判断数量
         SmsCoupon coupon = couponMapper.selectByPrimaryKey(couponId);
         if(coupon==null){
             Asserts.fail("优惠券不存在");
         }
         if(coupon.getCount()<=0){
             Asserts.fail("优惠券已经领完了");
         }
         Date now = new Date();
         if(now.before(coupon.getEnableTime())){
             Asserts.fail("优惠券还没到领取时间");
         }
         //判断用户领取的优惠券数量是否超过限制
         SmsCouponHistoryExample couponHistoryExample = new SmsCouponHistoryExample();
         couponHistoryExample.createCriteria().andCouponIdEqualTo(couponId).andMemberIdEqualTo(currentMember.getId());
         long count = couponHistoryMapper.countByExample(couponHistoryExample);
         if(count>=coupon.getPerLimit()){
             Asserts.fail("您已经领取过该优惠券");
         }
         //省略领取优惠券逻辑...
     }
}
复制代码
  • Here we enter the coupon ID no to a test this function, it returns 优惠券不存在an error message.

Advantages and disadvantages

The advantage of using the global exception checking logic to handle is relatively flexible and can handle complex validation logic. The disadvantage is that we need to repeat the check to write the code, as long as not using Hibernate Validator annotations using it. But we can in the above Assertsto add some class utility methods to enhance its functionality, such as to determine whether the air and determine the length and so can achieve their own.

to sum up

We can combine the two approaches used together, such as a simple check of the parameters used to implement Hibernate Validator, and some related to database operations use complex parity to achieve global exception handler.

Project Source Address

github.com/macrozheng/…

the public

mall project a full tutorial serialized in public concern number the first time to obtain.

No public picture

Guess you like

Origin juejin.im/post/5e6636da6fb9a07cb24aaf00