[elegant code] 10 - reject if/else data verification and conversion
Welcome to b station official account / public number [hexagon warrior Xia Ning], a man who wants to fill all the indicators. The article has been published in github directory Included.
The handsome and beautiful in front of the screen, if it helps you, please like it and add a collection, which is really important to me. Don't worry about where you go next time.
- Video Explanation
- Complete code that can be run directly
- Last idea breakpoint, plug-in and template collection
- Next stream featured / @ functional lazy loading example
1. Background
Avoid if and else, and only use annotations to complete verification and formatting
2. Annotation boundary value
2.1 official notes
- Add @ Validated annotation to the interface (it can use grouping better)
@PostMapping("testFront") public ResponseVO<FrontRepVO> testFront(@Validated @RequestBody FrontReqVO frontReqVO) { return new ResponseVO<>(); }
- Using javax validation. Annotation under constraints, which is in non controller
public class FrontReqVO { @NotNull private String name; @Size(min = 1) private String name2; // Cannot be empty after spaces are removed during verification @TrimNotNull(groups = {Default.class, Insert.class}) // Serialization, removing spaces @JsonSerialize(using = TrimNotNullJson.class) // Deserialization, removing spaces @JsonDeserialize(using = TrimNotNullDeJson.class) private String name3; @Pattern(regexp = "^[Y|N]$") private String yesOrNo; @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date date; private ErrorCodeEnum errorCodeEnum; private Boolean boo; }
- Let the mutual calls between methods also take effect
You need to add @ Validated to the method and @ Valid to the attribute
@Service @Validated public class FrontTestService { public ResponseVO<FrontRepVO> testFront3Valid(@Valid FrontReqVO frontReqVO) { return new ResponseVO<>(); } }
2.2 user defined annotation
Sometimes, the user-defined annotation cannot fully realize its own functions. In this case, the user-defined annotation is required, such as TrimNotNull. The annotation is used as this field and cannot be null. After trim, it cannot be empty
- Add an annotation and copy it from the official original annotation. The key change is to write the implementation class of the annotation in @ Constraint
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(TrimNotNull.List.class) @Documented @Constraint( validatedBy = {TrimNotNullImpl.class} ) public @interface TrimNotNull { String message() default "Cannot be null Or empty"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface List { TrimNotNull[] value(); } }
- Custom annotation implementation class
public class TrimNotNullImpl implements ConstraintValidator<TrimNotNull, String> { @Override public void initialize(TrimNotNull constraintAnnotation) { } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { return StringUtils.isNoneBlank(s); } }
3. Unified specification error
3.1 error code
Ali defines 00000 as success. Errors beginning with A are client errors, errors beginning with B are server errors, and errors beginning with C are remote call errors. The problem can be preliminarily determined according to the error code. If the source code is too long, it will not be posted. For this part, please refer to Ali's manual
public enum ErrorCodeEnum { SUCCESS("00000", "success", "Success"), CLIENT_ERROR("A0001", "Client error", "Client error"), USER_REGISTRATION_ERROR("A0100", "User registration error", "User registration error"); public static final Map<String, ErrorCodeEnum> MAPS = Stream.of(ErrorCodeEnum.values()).collect(Collectors.toMap(ErrorCodeEnum::getCode, s -> s)); }
3.2 global exceptions
In order to avoid returning various errors to users, it is necessary to encapsulate them uniformly
- Verification class encapsulation
public class FieldValidError { private String field; private String msg; }
- Global exception
@RestControllerAdvice @Controller public class GlobalExceptionHandler implements ErrorController { /** * 404 * * @author seal 876651109@qq.com * @date 2020/6/4 1:29 AM */ @RequestMapping(value = "/error") @ResponseBody public ResponseVO<String> error(HttpServletRequest request, HttpServletResponse response) { return ResponseVO.<String>builder().code(ErrorCodeEnum.ADDRESS_NOT_IN_SERVICE.getCode()).msg(ErrorCodeEnum.ADDRESS_NOT_IN_SERVICE.getZhCn()).build(); } /** * Other undefined errors * * @author seal 876651109@qq.com * @date 2020/6/4 12:57 AM */ @ExceptionHandler(Exception.class) public ResponseVO<String> handleException(Exception e) { log.warn("handleException:" + e.getMessage(), e); return ResponseVO.<String>builder().code(ErrorCodeEnum.SYSTEM_EXECUTION_ERROR.getCode()).msg(ErrorCodeEnum.SYSTEM_EXECUTION_ERROR.getZhCn()).build(); } /** * Exception in request mode, such as get/post * * @author seal 876651109@qq.com * @date 2020/6/4 12:58 AM */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseVO<String> HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { return ResponseVO.<String>builder().code(ErrorCodeEnum.USER_API_REQUEST_VERSION_MISMATCH.getCode()).msg(ErrorCodeEnum.USER_API_REQUEST_VERSION_MISMATCH.getZhCn()).build(); } /** * Rewrite the verification exception and return it in a unified format * * @param e * @return {@link ResponseVO< List< FieldValidError>>} * @author 876651109@qq.com * @date 2021/5/14 5:06 afternoon */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseVO<List<FieldValidError>> MethodArgumentNotValidException(MethodArgumentNotValidException e) { ResponseVO<List<FieldValidError>> vo = ResponseVO.<List<FieldValidError>>builder().build(); List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); List<FieldValidError> list = new ArrayList<>(fieldErrors.size()); vo.setData(list); for (FieldError error : fieldErrors) { FieldValidError build = FieldValidError.builder().field(error.getField()).msg(error.getDefaultMessage()).build(); switch (error.getCode()) { case "Size": build.setMsg(String.format("Must be%s reach%s between", error.getArguments()[2], error.getArguments()[1])); break; default: break; } list.add(build); } return vo; } @Override public String getErrorPath() { return null; } }
4. Extended conversion
In daily development, it is inevitable to encounter string to various strange formats, or preprocessing or post-processing, so it is very necessary to extend it
- json format
- Deserialization, external data to object
- Writing method I
/** * De space deserialization * * @author 876651109@qq.com * @date 2021/5/14 1:46 afternoon */ public class TrimNotNullDeJson extends JsonDeserializer<String> { @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { return StringUtils.trim(p.getText()); } }
- Writing method 2
/** * Under this annotation, this method of the class will be called during deserialization. Note that the method should be static * * @author 876651109@qq.com * @date 2021/5/14 1:46 afternoon */ @JsonCreator public static ErrorCodeEnum get(String value) { if (StringUtils.isBlank(value)) { return null; } ErrorCodeEnum errorCodeEnum = MAPS.get(value); if (errorCodeEnum == null) { return ErrorCodeEnum.valueOf(value); } else { return errorCodeEnum; } }
Serialization, when the object is converted to string
/** * Custom Serialization * * @author 876651109@qq.com * @date 2021/5/14 1:46 afternoon */ public class TrimNotNullJson extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(StringUtils.trim(value)); } }
- Form form/data format or get request format
- Implement Converter and convert code to enum
/** * Convert form/data format or get request * Convert stringCode directly to enum * * @author seal 876651109@qq.com * @date 2022/1/2 12:57 */ public class StringCodeToEnumConvert implements Converter<String, ErrorCodeEnum> { @Override public ErrorCodeEnum convert(String source) { return ErrorCodeEnum.MAPS.get(source); } }
- register
@Configuration public class SpringMvcConfig extends WebMvcConfigurationSupport { @Override protected void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToCodeConvert()); } /** * Not necessary, for open static resource interception * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations( "classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("doc.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations( "classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); } }
5.aop
This part of the function aims to reduce various nonstandard tryCath logs on the back end, which can be relatively standardized. At the same time, it provides printing in parameters, return values, method time-consuming, etc. The core function is to print in the parameter when the method is wrong, which is convenient to be set as an error.
- Create Note
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAnnotation { /** * Run time */ boolean totalConsume() default true; /** * Request parameters */ boolean parameter() default false; /** * Return value */ boolean result() default false; /** * Print request parameters in case of exception */ boolean exception() default true; }
- Add aop configuration
@Aspect @Component @Order(1000) @Slf4j public class LogInfoAspect { /** * Declare frequently used Pointcut expressions through the @ Pointcut annotation */ @Pointcut("execution(* com.example.demo..*.*(..))") public void AspectController() { } @Pointcut("execution(* com.example.demo..*.*(..))") public void AspectController2() { } /** * Execute first, exit first * * @author seal 876651109@qq.com * @date 2020/6/3 2:34 PM */ @Around("AspectController() || AspectController2()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { log.debug("Surround front"); MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); pjp.getTarget(); LogAnnotation logAnnotationClass = pjp.getTarget().getClass().getAnnotation(LogAnnotation.class); LogAnnotation logAnnotationMethod = methodSignature.getMethod().getAnnotation(LogAnnotation.class); if (logAnnotationClass == null && logAnnotationMethod == null) { return pjp.proceed(); } LogAnnotation logAnnotation = ObjectUtils.defaultIfNull(logAnnotationMethod, logAnnotationClass); StopWatch sw = new StopWatch(); String className = pjp.getTarget().getClass().getName(); String methodName = methodSignature.getName(); if (logAnnotation.parameter()) { log.info("{}:{}:parameter:{}", className, methodName, pjp.getArgs()); } sw.start(); Object result = null; try { result = pjp.proceed(); } catch (Throwable e) { if (logAnnotation.exception()) { log.warn(e.getMessage(), e); log.info("{}:{}:parameter:{}", className, methodName, pjp.getArgs()); } throw e; } if (logAnnotation.result()) { log.info("{}:{}:result:{}", className, methodName, result); } sw.stop(); if (logAnnotation.totalConsume()) { log.info("{}:{}:totalConsume:{}s", className, methodName, sw.getTotalTimeSeconds()); } log.debug("After surround"); return result; } }
6. Compress return value
Join in yml
server: compression: enabled: true mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
7. Docking documents
- swagger-ui
- Official website
This is a relatively perfect online api generator. When writing java, it can directly generate Chinese annotations through api annotations. Of course, if not, it will also generate a document of all interfaces. At the same time, it supports online debugging and has very powerful functions. However, it is obviously not friendly enough for persistence. On this basis, swagger ui layer complements this, supporting export and better looking ui. The content of this section is not too detailed. It is relatively simple and there are many online materials. - Import jar package
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-ui</artifactId> <version>3.0.2</version> </dependency>
- Add configuration
@Configuration @EnableSwagger2 public class SwaggerConfig { //http://localhost:8081/swagger-ui.html //http://localhost:8081/doc.html @Value("${swagger.enable:true}") private boolean enable; @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("Microservice interface call document") .enable(enable) .pathMapping("/") .select() .apis(RequestHandlerSelectors.basePackage("com.example.demo")) .paths(PathSelectors.any()) .build().apiInfo(new ApiInfoBuilder() .title("SpringBoot integration Swagger") .description("SpringBoot integration Swagger,detailed information......") .version("9.0") .license("The Apache License") .licenseUrl("http://www.baidu.com") .build()); } }
2. yapi
- github
Different from swagger, this has permission control, which is particularly important for the whole team to maintain a document. It supports the import of multiple formats, including swagger mentioned above.