当前位置:网站首页>Practical part: solving the function conflict between swagger and user-defined parameter parser
Practical part: solving the function conflict between swagger and user-defined parameter parser
2022-06-29 04:17:00 【Ah Q said code】
Antecedents feed
Read the last article I read the code written by my colleagues , I started to imitate silently ... Little buddy , Should have been used for Parameter resolver To complete the unified signature verification of the third-party interface .
We mentioned above ,@RequestBody Parameter parser used RequestResponseBodyMethodProcessor The priority is higher than our custom parameter parser , So for normal use , Need to put @RequestBody Remove the comment . This will lead to swagger The correct parameter type is not recognized , Identify the request body as Query Params, And then body an .
You can see , All parameters are identified as ModelAttribute type (query sign ), The correct format we expect should look like this
Because this method can greatly improve the readability and reusability of the code , So we have to overcome difficulties , Identify problems , solve the problem !
The cause of the problem
The root cause of this problem is spring mvc and swagger All the @RequestBody Comments are judged separately , It is functionally dependent on the annotation itself .
springmvc Yes @RequestBody Annotation dependency
Take the current customized parameter parser for example , If you add... To the request parameters @RequestBody annotation , Deserialization of parameters will be done in advance RequestResponseBodyMethodProcessor Intercept , Custom parameter parsers will fail .
Specific source code location :https://github.com/spring-projects/spring-framework/blob/5.2.x/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java#L111
You can see , This parameter resolver pair adds @ReuqestBody Annotation parameters support parsing , Then do the serialization operation . However, it has a higher priority in the list of parameter parsers , The custom parameter parser will be listed after it is added to the parameter parser list , So if you add @RequestBody annotation , The custom parameter parser will fail .
So use a custom parameter parser It must not Use @RequestBody annotation
The following figure shows the source code location :https://github.com/spring-projects/spring-framework/blob/5.2.x/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java#L129
The custom parameter parser used in this case is
HdxArgumentResolver
swagger Yes @Requestbody Dependence
After the call stack trace , Finally, it is found that the functions in two places will be beneficial to @RequestBody Comments are judged separately !( Those who are interested can track by themselves )
- Request type determination : in other words
POSTWhat type of request is , This determines whether the input parameter will act asRequest ParameterExpanded parameters , That is, the first picture in the text , WholemodelAre consideredModelAttributeUnfolded . DefinitionProperty value padding : This ensures that@RequestBodyThe comment decorated entry is displayed normally , As shown in the second picture .
Request type determination
Source code location :https://github.com/springfox/springfox/blob/2.9.2/springfox-spring-web/src/main/java/springfox/documentation/spring/web/readers/operation/OperationParameterReader.java#L151
Here to RequestBody And other common annotations , Make sure that the input parameters modified by these annotations are not used as RequestParam an .
Definition Property value padding
Definition Property is filled with input parameters 、 Output parameter type , If there is no corresponding Model Definition , be swagger The information will be incomplete , The display in the browser page will also be incomplete . fill Definition The logic of also depends on @RequestBody annotation .
Source code location :https://github.com/springfox/springfox/blob/2.9.2/springfox-spring-web/src/main/java/springfox/documentation/spring/web/readers/operation/OperationModelsProvider.java#L80
You can see , Only by RequestBody Notes and RequestPart The input parameter modified by the annotation will be received Definition attribute .
The source code analysis of the above two figures , You can see ,swagger Function depends on @RequestBody annotation , If the input parameter is not modified by the annotation , be swagger The function will be incomplete , This and in springmvc The use of independent parameter parser functions in... Is not allowed @RequestBody Notes are contradictory .
solve the problem
The conclusion can be drawn from the above analysis , The fundamental problem here is springmvc Independent parameter parser functions and swagger Functional conflict , A request cannot be added @RequestBody annotation , A requirement must be added @RequestBody annotation , So there are two ways to solve this problem
- from
springmvcStarting with , Find ways to increase the priority of the custom parameter parser , As long as the custom parameter parser has priority overRequestResponseBodyMethodProcessorhigh , You can add... To the customized parameters@RequestBodyannotation ,swaggerThe function will be normal naturally . - from
swaggerStarting with , Try to solve the above two parts@RequestBodyIndependent determination of , Don't modifyspringmvcRelated functions can also makeswaggerFunction is normal .
Considering the changes springmvc The function may have a great impact on future version upgrades , It is decided to modify the original by using the section swagger Yes @RequestBody The behavior of two places , So that swagger Function is normal .
Request logical adjustment of type determination
First , Define an annotation
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface NoSwaggerExpand {
/**
* default swagger expand disable
* @see OperationParameterReader#shouldExpand(springfox.documentation.service.ResolvedMethodParameter, com.fasterxml.classmate.ResolvedType)
*/
boolean expand() default false;
}
Add it to the input parameter
@ApiOperation(value = "demo", notes = "demo")
@PostMapping(value = "/test")
public Result<boolean> test(@HdxDecrypt @NoSwaggerExpand @ApiParam(required = true) ReqDTO reqDTO) {
try {
log.info(ObjectMapperFactory.getObjectMapper().writeValueAsString(reqDTO));
} catch (JsonProcessingException e) {
log.error("", e);
}
return null;
}
Then define the facet
@Slf4j
@Aspect
@Component
public class SwaggerExpandAspect {
private final ModelAttributeParameterExpander expander;
private final EnumTypeDeterminer enumTypeDeterminer;
@Autowired
private DocumentationPluginsManager pluginsManager;
@Autowired
public SwaggerExpandAspect(
ModelAttributeParameterExpander expander,
EnumTypeDeterminer enumTypeDeterminer) {
this.expander = expander;
this.enumTypeDeterminer = enumTypeDeterminer;
}
@Around("execution(* springfox.documentation.spring.web.readers.operation.OperationParameterReader.apply(..))")
public Object pointCut(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
OperationContext context = (OperationContext) args[0];
context.operationBuilder().parameters(context.getGlobalOperationParameters());
context.operationBuilder().parameters(readParameters(context));
return null;
}
private List<parameter> readParameters(final OperationContext context) {
List<resolvedmethodparameter> methodParameters = context.getParameters();
List<parameter> parameters = newArrayList();
for (ResolvedMethodParameter methodParameter : methodParameters) {
ResolvedType alternate = context.alternateFor(methodParameter.getParameterType());
if (!shouldIgnore(methodParameter, alternate, context.getIgnorableParameterTypes())) {
ParameterContext parameterContext = new ParameterContext(methodParameter,
new ParameterBuilder(),
context.getDocumentationContext(),
context.getGenericsNamingStrategy(),
context);
if (shouldExpand(methodParameter, alternate)) {
parameters.addAll(
expander.expand(
new ExpansionContext("", alternate, context)));
} else {
parameters.add(pluginsManager.parameter(parameterContext));
}
}
}
return FluentIterable.from(parameters).filter(not(hiddenParams())).toList();
}
private Predicate<parameter> hiddenParams() {
return new Predicate<parameter>() {
@Override
public boolean apply(Parameter input) {
return input.isHidden();
}
};
}
private boolean shouldIgnore(
final ResolvedMethodParameter parameter,
ResolvedType resolvedParameterType,
final Set<class> ignorableParamTypes) {
if (ignorableParamTypes.contains(resolvedParameterType.getErasedType())) {
return true;
}
return FluentIterable.from(ignorableParamTypes)
.filter(isAnnotation())
.filter(parameterIsAnnotatedWithIt(parameter)).size() > 0;
}
private Predicate<class> parameterIsAnnotatedWithIt(final ResolvedMethodParameter parameter) {
return new Predicate<class>() {
@Override
public boolean apply(Class input) {
return parameter.hasParameterAnnotation(input);
}
};
}
private Predicate<class> isAnnotation() {
return new Predicate<class>() {
@Override
public boolean apply(Class input) {
return Annotation.class.isAssignableFrom(input);
}
};
}
private boolean shouldExpand(final ResolvedMethodParameter parameter, ResolvedType resolvedParamType) {
return !parameter.hasParameterAnnotation(RequestBody.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);
}
private boolean noExpandAnnotaion(ResolvedMethodParameter parameter) {
log.info(" Start deciding whether to expand the problem ");
if (!parameter.hasParameterAnnotation(NoSwaggerExpand.class)) {
return false;
}
NoSwaggerExpand noSwaggerExpand = (NoSwaggerExpand) parameter.getAnnotations().stream().filter(item -> item instanceof NoSwaggerExpand).findAny().orElse(null);
if (noSwaggerExpand.expand()) {
return false;
}
return true;
}
}
The most important thing is the modification here
Here, the input parameters modified by the user-defined annotation are judged , So that the input parameters modified by the custom annotation can be Swagger treat as @RequestBody Handle it the same way .
Definition Logical adjustment of attribute value filling
Define another section
@Slf4j
@Aspect
@Component
public class SwaggerDefinitionAspect {
private static final Logger LOG = LoggerFactory.getLogger(OperationModelsProvider.class);
private final TypeResolver typeResolver;
@Autowired
public SwaggerDefinitionAspect(TypeResolver typeResolver) {
this.typeResolver = typeResolver;
}
@Around("execution(* springfox.documentation.spring.web.readers.operation.OperationModelsProvider.apply(..))")
public Object pointCut(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
RequestMappingContext context = (RequestMappingContext) args[0];
collectFromReturnType(context);
collectParameters(context);
collectGlobalModels(context);
return null;
}
private void collectGlobalModels(RequestMappingContext context) {
for (ResolvedType each : context.getAdditionalModels()) {
context.operationModelsBuilder().addInputParam(each);
context.operationModelsBuilder().addReturn(each);
}
}
private void collectFromReturnType(RequestMappingContext context) {
ResolvedType modelType = context.getReturnType();
modelType = context.alternateFor(modelType);
LOG.debug("Adding return parameter of type {}", resolvedTypeSignature(modelType).or("<null>"));
context.operationModelsBuilder().addReturn(modelType);
}
private void collectParameters(RequestMappingContext context) {
LOG.debug("Reading parameters models for handlerMethod |{}|", context.getName());
List<resolvedmethodparameter> parameterTypes = context.getParameters();
for (ResolvedMethodParameter parameterType : parameterTypes) {
if (parameterType.hasParameterAnnotation(RequestBody.class)
|| parameterType.hasParameterAnnotation(RequestPart.class)
|| parameterType.hasParameterAnnotation(NoSwaggerExpand.class)
) {
ResolvedType modelType = context.alternateFor(parameterType.getParameterType());
LOG.debug("Adding input parameter of type {}", resolvedTypeSignature(modelType).or("<null>"));
context.operationModelsBuilder().addInputParam(modelType);
}
}
LOG.debug("Finished reading parameters models for handlerMethod |{}|", context.getName());
}
}
There is only one code change here , Enables input parameters modified by custom annotations to be added to Definition Attribute to .
Finish the above two steps , You can fix it springmvc Independent parameter parser functions and swagger The problem of function conflict .
That's all for today
边栏推荐
- PostgreSQL has a cross database references are not implemented bug
- Path and LD_ LIBRARY_ Example of path usage
- moudo网络库剖析
- Webassembly learning - dynamic linking
- Emotional changes need to be controlled
- webassembly学习-动态链接
- Multi machine LAN office artifact rustdesk use push!!!
- 树莓派用VNC Viewer方式远程连接
- 【C语言】 详解线程退出函数 pthread_exit
- Apifox : 不仅是Api调试工具,更是开发团队的协作神器
猜你喜欢

访问数据库时出现错误

Seattention channel attention mechanism

Libuv library overview and comparison of libevent, libev and libuv (Reprint)

如何创建 robots.txt 文件?

Apifox : 不仅是Api调试工具,更是开发团队的协作神器

基于可变参模板实现的线程池

1018 hammer scissors cloth

sql两列变为多行过滤显示
![[FPGA mathematical formula] use FPGA to realize common mathematical formulas](/img/b9/e6f219738b106a96b0f5323ee61cca.png)
[FPGA mathematical formula] use FPGA to realize common mathematical formulas

Libuv库概述及libevent、libev、libuv对比(转载)
随机推荐
On June 27, 2022, I have the right to choose the journey of the summer vacation.
1015 theory of virtue and talent
Log in to the MySQL database and view the version number on the command line
moudo网络库剖析
Rapid development project -vscode plug-in
PostgreSQL has a cross database references are not implemented bug
【Laravel系列8】走出 Laravel 的世界
Analysis on the types of source code anti leakage technology
How sqlserver queries and removes results with null fields in the whole column
SQL database stored procedure writing method
1018 锤子剪刀布
MySQL复习资料(附加)case when
MySQL column to row conversion without Union
Airflow2.2.3 + efficiency + MySQL 8 build a robust distributed scheduling cluster
[FPGA mathematical formula] use FPGA to realize common mathematical formulas
043. (2.12) what will happen if you become enlightened?
Libuv library overview and comparison of libevent, libev and libuv (Reprint)
增额终身寿险下架了吗?现在还有哪些增额终身寿险产品?
sql数据库存储过程写法
Path and LD_ LIBRARY_ Example of path usage