SpringBoot actual e-business project mall (30K + STAR) address: github.com/macrozheng/…
Abstract
During interface development, it is often necessary to verify parameters. There are two ways to handle the verification logic. One is to use Hibernate Validator and the other is to use global exceptions. Here’s how to use both methods.
Hibernate Validator
Hibernate Validator is a built-in validation framework for SpringBoot, which is automatically integrated whenever SpringBoot is integrated. You can use annotations provided by Hibernate Validator on objects to validate parameters.
Commonly used annotations
Let’s take a look at common annotations to get an idea of the validation capabilities Hibernate Validator provides.
- @null: The annotated attribute must be Null;
- @notnull: Annotated properties cannot be NULL;
- AssertTrue: The annotated property must be true;
- @assertFalse: The annotated property must be false;
- @min: The annotated attribute must be greater than or equal to its value.
- @max: The annotated attribute must be less than or equal to its value;
- @size: The annotated attribute must be between its min and Max value;
- @pattern: Annotated attributes must match the regular expression defined by their regexp;
- NotBlank: annotated string cannot be an empty string;
- @notempty: Annotated attributes cannot be empty;
- @email: The annotated attribute must conform to the mailbox format.
use
Use Hibernate Validator to add a branded interface to a Validator. Hibernate Validator is a Hibernate Validator that allows you to add a branded interface to a Validator. Use AOP to log interface access in SpringBoot applications.
- First we need to add the parameters of the brand interface
PmsBrandParam
To add verification annotations, used to determine the verification rules of attributes and the information to be returned after verification failure;
/** * Created by macro on 2018/4/26. */
public class PmsBrandParam {
@ApiModelProperty(value = "Brand Name",required = true)
@NotEmpty(message = "Name cannot be empty")
private String name;
@ApiModelProperty(value = "Brand Initials")
private String firstLetter;
@ApiModelProperty(value = "Sort field")
@Min(value = 0, message = "Sort at least 0")
private Integer sort;
@ApiModelProperty(value = "Is it the manufacturer?")
@FlagValidator(value = {"0"."1"}, message = "Manufacturer status is incorrect")
private Integer factoryStatus;
@ApiModelProperty(value = "Display or not")
@FlagValidator(value = {"0"."1"}, message = "Display status is incorrect")
private Integer showStatus;
@ApiModelProperty(value = "Brand logo",required = true)
@NotEmpty(message = "Brand logo cannot be empty")
private String logo;
@ApiModelProperty(value = "Big Picture of the Brand")
private String bigPic;
@ApiModelProperty(value = "Brand Story")
private String brandStory;
// omit several getters and setters...
}
Copy the code
- Then add the @validated annotation and inject a BindingResult parameter into the interface for adding a brand;
Controller * Created by macro on 2018/4/26. */
@Controller
@Api(tags = "PmsBrandController", description = "Product Brand Management")
@RequestMapping("/brand")
public class PmsBrandController {
@Autowired
private PmsBrandService brandService;
@ApiOperation(value = "Add brand")
@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();
}
returncommonResult; }}Copy the code
- Then create a slice in the whole Controller layer, get the injected BindingResult object in its surrounding notification, and pass the hasErrors method to determine whether the verification is successful. If there is an error message, the verification is released.
/** * Created by macro on 2018/4/26. */
@Aspect
@Component
@Order(2)
public class BindingResultAspect {
@Pointcut("execution(public * com.macro.mall.controller.*.*(..) )")
public void BindingResult(a) {}@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{
returnCommonResult.validateFailed(); }}}}returnjoinPoint.proceed(); }}Copy the code
- At this point we access the interface to add the brand, without passing in
name
Field is returnedThe name cannot be empty
Error message;
Custom annotations
Sometimes the validation annotations provided by the framework do not meet our needs. In this case, we need to customize the validation annotations. For example, if we add the brand as above, we have the parameter showStatus. We want it to be 0 or 1, not any other number. We can use custom annotations to do this.
- Start by customizing a validation annotation class, FlagValidator, and then add the @Constraint annotation to specify the implementation class of the validation logic using its validatedBy attribute.
** 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(a) default "flag is not found"; Class<? >[] groups()default {};
Class<? extends Payload>[] payload() default {};
}
Copy the code
- Create the FlagValidatorClass as the implementation of the validation logic. Implement the ConstraintValidator interface by specifying two generic parameters: the first is your custom validation annotation class, and the second is the type of the attribute you want to validate. The isValid method is the actual 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) {// Use the default value when the status is null
return true;
}
for(int i=0; i<values.length; i++){if(values[i].equals(String.valueOf(value))){
isValid = true;
break; }}returnisValid; }}Copy the code
- We can then use the annotation in the pass object;
/** * Created by macro on 2018/4/26. */
public class PmsBrandParam {
@ApiModelProperty(value = "Display or not")
@FlagValidator(value = {"0"."1"}, message = "Display status is incorrect")
private Integer showStatus;
// omit several getters and setters...
}
Copy the code
- Finally, we test the annotation. The calling interface is passed in
showStatus=3
That will be returnedThe display status is incorrect
Error message.
The advantages and disadvantages
The advantage of this approach is that you can use annotations to validate parameters without requiring some repetitive validation logic, but it also has some disadvantages. For example, you need to inject an additional BindingResult object into the Controller’s methods. Only some simple validation is supported, and the validation involved in querying the database cannot be met.
Global exception handling
The idea of using global exception handling to handle validation logic is very simple. First we need to define a global exception handling class via the @ControllerAdvice annotation. Then we need to define a validation exception that will be thrown if we fail to validate in Controller. This can achieve the purpose of verification failure return error message.
The annotations used
@ControllerAdvice: Similar to the @Component annotation, you can specify a Component that enhances the functionality of the class modified by the @Controller annotation, such as global exception handling.
@ExceptionHandler: A method used to modify global exception handling by specifying the type of exception.
use
- First we need to customize an exception class
ApiException
Throw this exception if we fail to validate:
/** * 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(a) {
returnerrorCode; }}Copy the code
- Then create an assertion handling class
Asserts
Is used to throw variousApiException
;
/** * 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 newApiException(errorCode); }}Copy the code
- Then create our global exception handling class
GlobalExceptionHandler
To handle global exceptions and return the encapsulated CommonResult object;
/** * 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());
}
returnCommonResult.failed(e.getMessage()); }}Copy the code
- Here, take the code for users to receive coupons as an example. Let’s first compare the code before and after improvement, and first look at the code of the Controller layer. If the method in the Service executes successfully, the coupon will be successfully collected. ApiException will be thrown directly and an error message will be returned.
/** * Created by macro on 2018/8/29. */
@Controller
@Api(tags = "UmsMemberCouponController", description = "User coupon Management")
@RequestMapping("/member/coupon")
public class UmsMemberCouponController {
@Autowired
private UmsMemberCouponService memberCouponService;
Before / / improvement
@ApiOperation("Get designated coupon")
@RequestMapping(value = "/add/{couponId}", method = RequestMethod.POST)
@ResponseBody
public CommonResult add(@PathVariable Long couponId) {
return memberCouponService.add(couponId);
}
/ / the improved
@ApiOperation("Get designated coupon")
@RequestMapping(value = "/add/{couponId}", method = RequestMethod.POST)
@ResponseBody
public CommonResult add(@PathVariable Long couponId) {
memberCouponService.add(couponId);
return CommonResult.success(null."Claim success"); }}Copy the code
- Looking at the code in the Service interface, the difference is that the result is returned. The improved return is void. In fact, CommonResult is used to encapsulate the data obtained in the Service into a unified return result. This principle is violated before the improvement, and this problem is solved after the improvement.
/** * Service * Created by macro on 2018/8/29. */
public interface UmsMemberCouponService {
/** * Members add coupons (before improvement) */
@Transactional
CommonResult add(Long couponId);
/** * Members add coupons (after improvement) */
@Transactional
void add(Long couponId);
}
Copy the code
- Looking at the code in the Service implementation class, you can see that the logic returning CommonResult has been changed to call the FAIL method for scientific results.
/** * 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;
Before / / improvement
@Override
public CommonResult add(Long couponId) {
UmsMember currentMember = memberService.getCurrentMember();
// Get the coupon information and judge the quantity
SmsCoupon coupon = couponMapper.selectByPrimaryKey(couponId);
if(coupon==null) {return CommonResult.failed("Coupons don't exist.");
}
if(coupon.getCount()<=0) {return CommonResult.failed("I ran out of coupons.");
}
Date now = new Date();
if(now.before(coupon.getEnableTime())){
return CommonResult.failed("Coupons are not ready for collection.");
}
// Determine whether the number of coupons received by the user exceeds the limit
SmsCouponHistoryExample couponHistoryExample = new SmsCouponHistoryExample();
couponHistoryExample.createCriteria().andCouponIdEqualTo(couponId).andMemberIdEqualTo(currentMember.getId());
long count = couponHistoryMapper.countByExample(couponHistoryExample);
if(count>=coupon.getPerLimit()){
return CommonResult.failed("You have already received this coupon.");
}
// omit the coupon logic...
return CommonResult.success(null."Claim success");
}
/ / the improved
@Override
public void add(Long couponId) {
UmsMember currentMember = memberService.getCurrentMember();
// Get the coupon information and judge the quantity
SmsCoupon coupon = couponMapper.selectByPrimaryKey(couponId);
if(coupon==null){
Asserts.fail("Coupons don't exist.");
}
if(coupon.getCount()<=0){
Asserts.fail("I ran out of coupons.");
}
Date now = new Date();
if(now.before(coupon.getEnableTime())){
Asserts.fail("Coupons are not ready for collection.");
}
// Determine whether the number of coupons received by the user exceeds the limit
SmsCouponHistoryExample couponHistoryExample = new SmsCouponHistoryExample();
couponHistoryExample.createCriteria().andCouponIdEqualTo(couponId).andMemberIdEqualTo(currentMember.getId());
long count = couponHistoryMapper.countByExample(couponHistoryExample);
if(count>=coupon.getPerLimit()){
Asserts.fail("You have already received this coupon.");
}
// omit the coupon logic...}}Copy the code
- Here we enter a coupon ID that doesn’t exist to test the feature, and it returns
Coupons don't exist
Error message.
The advantages and disadvantages
The advantage of using global exceptions for validation logic is that it is flexible and can handle complex validation logic. The downside is that we need to write validation code repeatedly, instead of just using annotations like we do with Hibernate Validator. However, we can add some tool methods to the above results class to enhance its functionality, such as null and length determination, which can be realized by itself.
conclusion
We can use both methods together, such as using Hibernate Validator for simple parameter validation, and global exception handling for complex validation involving database operations.
Project source code address
Github.com/macrozheng/…
The public,
Mall project full set of learning tutorials serialized, attention to the public number the first time access.