brief introduction
In REST-style development, avoid informing the front desk of success and status codes. Here we usually do a util wrapping when we return, such as: Result similar classes, which contain succ, code, msg, data and other fields.
The interface call returns something like this:
{ "succ": false, // Success "ts": 1566467628851, // time stamp "data": null, // data "code": "CLOUD800", // Error type "msg": "Business exception", // Wrong description "fail": true }
Of course, each interface returns the information to be encapsulated through Result's tool class, which leads to code coupling between business and technical classes.
Interface call processing is similar to the following:
@GetMapping("hello") public Result list(){ return Result.ofSuccess("hello"); }
Result:
{ "succ": ture, // Success "ts": 1566467628851, // time stamp "data": "hello", // data "code": null, // Error type "msg": null, // Wrong description "fail": true }
We extract these operations from a common starter package, each service dependency can be done, a unified layer of interception processing work, technical decoupling.
To configure
unified-dispose-springboot-starter
This module includes exception handling and global return encapsulation functions, below.
The complete directory structure is as follows:
├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── purgetiem │ │ │ └── starter │ │ │ └── dispose │ │ │ ├── GlobalDefaultConfiguration.java │ │ │ ├── GlobalDefaultProperties.java │ │ │ ├── Interceptors.java │ │ │ ├── Result.java │ │ │ ├── advice │ │ │ │ └── CommonResponseDataAdvice.java │ │ │ ├── annotation │ │ │ │ ├── EnableGlobalDispose.java │ │ │ │ └── IgnorReponseAdvice.java │ │ │ └── exception │ │ │ ├── GlobalDefaultExceptionHandler.java │ │ │ ├── category │ │ │ │ └── BusinessException.java │ │ │ └── error │ │ │ ├── CommonErrorCode.java │ │ │ └── details │ │ │ └── BusinessErrorCode.java │ │ └── resources │ │ ├── META-INF │ │ │ └── spring.factories │ │ └── dispose.properties │ └── test │ └── java
Unified Return Processing
Following the general pattern, we all need to create a toolkit class for wrapping and a return object.
Result (return class):
Create Result < T > T as the data type. This class contains fields commonly used in the front end, and some commonly used methods for static initialization of Result objects.
/** * Return Unified Data Structure * * @author purgeyao * @since 1.0 */ @Data @ToString @NoArgsConstructor @AllArgsConstructor public class Result<T> implements Serializable { /** * Success */ private Boolean succ; /** * Server current timestamp */ private Long ts = System.currentTimeMillis(); /** * Successful data */ private T data; /** * Error code */ private String code; /** * Wrong description */ private String msg; public static Result ofSuccess() { Result result = new Result(); result.succ = true; return result; } public static Result ofSuccess(Object data) { Result result = new Result(); result.succ = true; result.setData(data); return result; } public static Result ofFail(String code, String msg) { Result result = new Result(); result.succ = false; result.code = code; result.msg = msg; return result; } public static Result ofFail(String code, String msg, Object data) { Result result = new Result(); result.succ = false; result.code = code; result.msg = msg; result.setData(data); return result; } public static Result ofFail(CommonErrorCode resultEnum) { Result result = new Result(); result.succ = false; result.code = resultEnum.getCode(); result.msg = resultEnum.getMessage(); return result; } /** * Get json */ public String buildResultJson(){ JSONObject jsonObject = new JSONObject(); jsonObject.put("succ", this.succ); jsonObject.put("code", this.code); jsonObject.put("ts", this.ts); jsonObject.put("msg", this.msg); jsonObject.put("data", this.data); return JSON.toJSONString(jsonObject, SerializerFeature.DisableCircularReferenceDetect); } }
This meets the general requirements of return processing and can be used in the interface as follows:
@GetMapping("hello") public Result list(){ return Result.ofSuccess("hello"); }
Of course, this is the use of coupling, each time you need to call the wrapping method in Result.
ResponseBodyAdvice returns to Unified Interception Processing
A new interface added by ResponseBodyAdvice in spring 4.1 allows the @ResponseBody modifier in Controller or ResponseEntity to adjust the contents of the response, such as doing some return processing, before the message body is written by HttpMessageConverter.
There are two methods in the ResponseBodyAdvice interface
- Support: Does this component support the given controller method return type and the selected {@code HttpMessageConverter} type
- beforeBodyWrite: Called after selecting {@code HttpMessageConverter} and before calling its write method.
Then we can do something in these two ways.
- Support is used to determine whether processing is required.
- beforeBodyWrite is used for return processing.
The CommonResponseDataAdvice class implements two methods of ResponseBodyAdvice.
The filter(MethodParameter methodParameter) is used to determine whether to intercept the uniform return processing in the private method.
Such as:
- Add a custom annotation @IgnorReponseAdvice to ignore interception.
- Judge that some classes are not intercepted.
- Judge that all classes under certain packages are not intercepted. For example, the interface under the springfox.documentation package of swagger ignores interception and so on.
filter method:
It is judged that false does not require interception processing.
private Boolean filter(MethodParameter methodParameter) { Class<?> declaringClass = methodParameter.getDeclaringClass(); // Check the filter packet path long count = globalDefaultProperties.getAdviceFilterPackage().stream() .filter(l -> declaringClass.getName().contains(l)).count(); if (count > 0) { return false; } // Check the < Class > Fi lt er List if (globalDefaultProperties.getAdviceFilterClass().contains(declaringClass.getName())) { return false; } // Check for annotations if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnorReponseAdvice.class)) { return false; } if (methodParameter.getMethod().isAnnotationPresent(IgnorReponseAdvice.class)) { return false; } return true; }
CommonResponseDataAdvice class:
The core is in beforeBodyWrite method processing.
- Determine whether Object o is null, and construct Result object for null to return.
- To determine whether Object o is a Result subclass or itself, in this case, it may be that Result was created when the interface returned. To avoid encapsulating it again, it is judged that the Result subclass or itself returns to Object o itself.
- To determine whether Object o is for String or not, a special case of String is found in the course of testing. A judgment operation is made here, and JSON. to JSON (Result. of Success (o). to String () sequence number operation is performed for String.
- In other cases, Result.ofSuccess(o) is returned by default for packaging.
/** * {@link IgnorReponseAdvice} Processing parsing {@link ResponseBodyAdvice} unified return wrapper * * @author purgeyao * @since 1.0 */ @RestControllerAdvice public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> { private GlobalDefaultProperties globalDefaultProperties; public CommonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties) { this.globalDefaultProperties = globalDefaultProperties; } @Override @SuppressWarnings("all") public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return filter(methodParameter); } @Nullable @Override @SuppressWarnings("all") public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { // o is null -> return response if (o == null) { return Result.ofSuccess(); } // o is instanceof ConmmonResponse -> return o if (o instanceof Result) { return (Result<Object>) o; } // string Special Processing if (o instanceof String) { return JSON.toJSON(Result.ofSuccess(o)).toString(); } return Result.ofSuccess(o); } private Boolean filter(MethodParameter methodParameter) { ···slightly } }
This basically completes the core processing work. Of course, the @IgnorReponseAdvice annotation mentioned above is missing.
@IgnorReponseAdvice:
It's simpler, just as a sign.
/** * Uniform Return Packing Mark Annotation * * @author purgeyao * @since 1.0 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface IgnorReponseAdvice { }
Add spring container
Finally, the Global Default Exception Handler is injected into the spring container as a bean.
@Configuration @EnableConfigurationProperties(GlobalDefaultProperties.class) @PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8") public class GlobalDefaultConfiguration { ···slightly @Bean public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){ return new CommonResponseDataAdvice(globalDefaultProperties); } }
Load the GlobalDefault Configuration under the resources/META-INF/spring.factories file.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.purgetime.starter.dispose.GlobalDefaultConfiguration
But this time we use annotations to open it. After other projects rely on packages, you need to add @EnableGlobalDispose to turn on the feature of global interception.
Comment out the spring.factories you just created and create the EnableGlobalDispose annotation.
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(GlobalDefaultConfiguration.class) public @interface EnableGlobalDispose { }
Use @Import to import Global Default Configuration.
Use
Add dependency
<dependency> <groupId>io.deepblueai</groupId> <artifactId>unified-dispose-deepblueai-starter</artifactId> <version>0.1.0.RELEASE</version> </dependency>
The startup class opens the @EnableGlobalDispose annotation.
- Business use
Interface:
@GetMapping("test") public String test(){ return "test"; }
Return
{ "succ": true, // Success "ts": 1566386951005, // time stamp "data": "test", // data "code": null, // Error type "msg": null, // Wrong description "fail": false }
- Ignore encapsulation annotations: @IgnorReponseAdvice
@ IgnorReponseAdvice allows the scope of: class + method, identifying that the return of a method under this class on the class will ignore the return encapsulation.
Interface:
@IgnorReponseAdvice // Ignoring data wrapping can be added to classes and methods @GetMapping("test") public String test(){ return "test"; }
Return to test
summary
There are many duplicate code s in the project, we can simplify them in a certain way, in order to achieve a certain goal to reduce the development volume.
Sample code address: unified-dispose-springboot
Author Git Hub:
Purgeyao Welcome to your attention.