Batter articles: Solve the functional conflicts of swagger and custom parameter parsers


After reading the previous article, I saw the code written by my colleagues, I actually started silently imitating. . . The small partner should have a clear understanding of the unified test of the third-party interface to complete the use of the parameter parser.

We mentioned above, the parameter parser RequestResponseBodyMethodProcessor priority is higher than our custom parameter parser, so you need to disconnect @RequestBody to normal use. This will cause SWAGGER to identify the correct parameter type, identify the requested body as Query Params, and then expand Body.

It can be seen that all parameters are identified as the Modelattribute type (Query flag), and the correct format we expect should be like the following

Because this method can greatly improve the readability and reusability of code, so we must know how difficult, find out the problem, solve the problem!

Reason for problems

The root cause of this problem is that Spring MVC and SWAGGER have a separate judgment of @RequestBody annotations, which is functionally dependent on the annotation itself.

SpringMVC’s dependence on @RequestBody annotation

Take the currently custom parameter parser, if the request parameter is added to the @RequestBody annotation, the reverse sequence of parameters will be intercepted by the RequestResponseBodyMethodProcessor in advance, and the custom parameter parser will fail.

Specific source code location: https: // /

It can be seen that the parameter parser supports parsing with the parameters of the @reuqestbody annotation, then perform sequence-of-sequence operations. However, it is relatively high in the list of priorities in the parameter parser, and the custom parameter parser is added to its back, so if you add @RequestBody annotations, the custom parameter parser is invalid. .

So use the custom parameter parser must not use @RequestBody annotations

FIG next source position: https: // Java # l129

The custom parameter parser used in this case is HDXargumentResolver

Swagger’s dependence on @RequestBody

After calling stack tracking, it is finally found in two places to annotate @RequestBody annotations! (Interested can track it yourself ??)

Request type determination: That is, which is the type of POST request type, which determines whether it will be expanded as a requestParameter, which is the first picture in the article, and the entire Model is considered to be ModeLamtribute. The definition property is filled: This ensures that the incoming number of the @RequestBody annotation is normal, as shown in the second picture in the text.

Request type determination

Source code location: L151

Here is a separate judgment of the commonly used annotations such as REQUESTBODY to ensure that these annotations modified will not be expanded as RequestParam.

Definition attribute value fill

The DEFINITION properties are filled with parameter types such as income, outlet, and if there is no corresponding model definition, the swagger information will be incomplete, and the display in the browser page will be incomplete. The logic of filling the definition is also done on @RequestBody annotations. Source code location: L80

It can be seen that only the income ginseng annotated by the RequestBody annotation and RequestPart annotation will be received to enter the definition property.

Source code analysis of the above two graphs, you can see that the swagger function depends on @RequestBody annotation, and the SWAGGER function will be incomplete, this and the independent parameter parser in SpringMVC. The function is not allowed to use @RequestBody annotation.

Solve the problem

From the above analysis, it can be conclusively, the fundamental problem here is the independent parameter parser function and swagger function in SpringMVC. A requirement cannot be added with @RequestBody annotations, and a requirement must be added to @RequestBody annotation, so the solution can be Use two ways

Starting from SpringMVC, you want to improve the priority of the custom parameter parser, as long as the custom parameter parser priority is higher than RequestResponseBodyMethodProcessor, you can add @RequestBody annotations on custom parameters, and swagger features naturally. . Starting from Swagger, I want to solve the SPRINGMVC-related feature that does not modify the SpringMVC-related functionality without modifying the SpringMVC-related feature.

Considering that the modification of the SpringMVC function may have a big impact on future version upgrades, this decided to use the cut to modify the original Swagger’s behavior of the two places of @RequestBody, so that the swagger function is normal.

Request type of logical adjustment

First, define an annotation

@ Documented @ Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.PARAMETER}) public @ interfaceNoSwaggerExpand {/ *** defaultswaggerexpanddisable * @ seeOperationParameterReader # shouldExpand (springfox.documentation.service.ResolvedMethodParameter, com.fasterxml.classmate.ResolvedType) * / booleaneXpand () defaultfalse;}

Add it to incubation

@ApiOperation (value = “demo”, notes = “demo”) @ PostMapping (value = “/ test”) publicResult test (@ HdxDecrypt @ NoSwaggerExpand @ ApiParam (required = true) ReqDTOreqDTO) {try { (ObjectMapperFactory.getObjectmapper (). WriteValueAsstring (Reqdto));} catch (jsonprocessingexceptione) {log.error (“”, e);} returnnel;}

Then define the cut surface

@ Slf4j @ Aspect @ ComponentpublicclassSwaggerExpandAspect {privatefinalModelAttributeParameterExpanderexpander; privatefinalEnumTypeDeterminerenumTypeDeterminer; @AutowiredprivateDocumentationPluginsManagerpluginsManager; @AutowiredpublicSwaggerExpandAspect (ModelAttributeParameterExpanderexpander, EnumTypeDeterminerenumTypeDeterminer) {this.expander = expander; this.enumTypeDeterminer = enumTypeDeterminer;} @ Around ( “execution (* springfox.documentation.spring.web. readers.operation.OperationParameterReader.apply (..)) “) publicObjectpointCut (ProceedingJoinPointpoint) throwsThrowable {Object [] args = point.getArgs (); OperationContextcontext = (OperationContext) args [0];. context.operationBuilder () parameters (context .getGlobalOperationParameters ()); context.operationBuilder () parameters (readParameters (context.)); returnnull;} privateList readParameters (finalOperationContextcontext) {List methodParameters = context.getParameters (); List parameters = NEWARRAYLIST (); for (ResolvedMethodparam etermethodParameter: methodParameters) {ResolvedTypealternate = context.alternateFor (methodParameter.getParameterType ()); if) {ParameterContextparameterContext = newParameterContext (methodParameter, newParameterBuilder (), context.getDocumentationContext () (shouldIgnore (methodParameter, alternate, context.getIgnorableParameterTypes ()!) , context.getGenericsNamingStrategy (), context); if (shouldExpand (methodParameter, alternate)) {parameters.addAll (expander.expand (newExpansionContext ( “”, alternate, context)));} else {parameters.add (pluginsManager.parameter (ParameterContext);}}} returnfluntiterable.from (parameters) .filter (NOT (Hiddenparams ())))))))). Tolist ();} privatepredicate Hiddenparams () {Returnnewpredicate

parameter> () {@ Overridepublicbooleanapply (Parameterinput) {returninput.isHidden ();}};} privatebooleanshouldIgnore (finalResolvedMethodParameterparameter, ResolvedTyperesolvedParameterType, finalSet ignorableParamTypes) {if (ignorableParamTypes.contains (resolvedParameterType.getErasedType ())) {returntrue; ..} returnFluentIterable.from (ignorableParamTypes) .filter (isAnnotation ()) filter (parameterIsAnnotatedWithIt (parameter)) size ()> 0;} privatePredicate parameterIsAnnotatedWithIt (finalResolvedMethodParameterparameter) {returnnewPredicate () {@ Overridepublicbooleanapply (classinput ) {returnparameter.hasParameterAnnotation (input);}};} privatePredicate isAnnotation () {returnnewPredicate () {@ Overridepublicbooleanapply (classinput) {returnAnnotation.class.isAssignableFrom (input);}};} privatebooleanshouldExpand (finalResolvedMethodParameterparameter , ResolvedTyperesolvedparamType) {Return! Parameter.HasparameteranNotation (RequestPart.Class) &&! Parameter.HasparameteranNotation (RequestPart.Class ) &&! Parameter.hasParameterAnnotation (RequestParam.class) &&! Parameter.hasParameterAnnotation (PathVariable.class) &&! IsBaseType (typeNameFor (resolvedParamType.getErasedType ())) &&! EnumTypeDeterminer.isEnum (resolvedParamType.getErasedType ()) &&! IsContainerType ( ! resolvedParamType) && isMapType (! resolvedParamType) && noExpandAnnotaion (parameter);} privatebooleannoExpandAnnotaion (ResolvedMethodParameterparameter) { ( “begin to decide whether to expand the question”);! if (parameter.hasParameterAnnotation (NoSwaggerExpand.class)) {returnfalse;} Noswaggerexpandnoswaggerexpand = (NoswaggeRexpand) parameter.GetanNotations (). Stream (). Filter (item->

IteminstanceOfnoswaggerexpand .findany (). Oralse (null); if (NoswaggeRexpand.expand ()) {ReturnFalse;} ReturnTrue;}} The most important thing is the modification here

Here, add-to-reference to custom annotations modified, enabling the cordialized annotation modified to be processed as swagger as @RequestBody.

DEFINITION attribute value fill logical adjustment

Cave a cut surface

@ Slf4j @ Aspect @ ComponentpublicclassSwaggerDefinitionAspect {privatestaticfinalLoggerLOG = LoggerFactory.getLogger (OperationModelsProvider.class); privatefinalTypeResolvertypeResolver; @AutowiredpublicSwaggerDefinitionAspect (TypeResolvertypeResolver) {this.typeResolver = typeResolver;} @ Around ( “execution (* springfox.documentation.spring.web.readers. operation.OperationModelsProvider.apply (..)) “) publicObjectpointCut (ProceedingJoinPointpoint) throwsThrowable {Object [] args = point.getArgs (); RequestMappingContextcontext = (RequestMappingContext) args [0]; collectFromReturnType (context); collectParameters (context); collectGlobalModels (context); returnnull;} privatevoidcollectGlobalModels (RequestMappingContextcontext) {for. (ResolvedTypeeach: context.getAdditionalModels ()) {context.operationModelsBuilder () addInputParam (each); context.operationModelsBuilder () addReturn (each);.}} privatevoidcollectFromReturnType (RequestMappingContextcontext ) {ResolvedtypeModeltype = context.getreturntype (); modelType = context.alternatefor (ModelTyP e); LOG.debug ( “Addingreturnparameteroftype {}”, resolvedTypeSignature (modelType) .or ( ““)); context.operationModelsBuilder () addReturn (modelType);.} privatevoidcollectParameters (RequestMappingContextcontext) {LOG.debug ( ” ReadingparametersmodelsforhandlerMethod | {} | “, context.getName ()); List parameterTypes = context.getParameters (); for (ResolvedMethodParameterparameterType: parameterTypes) {if (parameterType.hasParameterAnnotation (RequestBody.class) || parameterType.hasParameterAnnotation (RequestPart .class || ParameterType.hasparameteranNotation (Noswaggerexpand.class)) {resolvedtypemodeltype = context.AlternateFor (ParameterType.getParameterType ()); log.debug (“

);}} Only one code is changed here so that the cordialized annotations modified can be added to the definition attribute.

After doing two steps, you can fix the SpringMVC independent parameter parser function and the problem of swagger function conflicts.