Skip to content
Go back

springdoc-v2中在不配置@ParameterObject的情况下实现Pageable以及Sort对象到API参数的转换

Edit page

springdoc-v2中在不配置@ParameterObject的情况下实现Pageable以及Sort对象到API参数的转换

springdoc是一个可以快速生成API文档的第三方公共库, 并提供了UI页面以供访问.
同时它也提供了spring-webmvc中的handler中的参数对象到API参数的转换.
对于spring-data-commons中的PageableSort, springdoc提供了开箱即用的功能, 需要在配置文件中启用以及在参数中声明@ParameterObject

springdoc:
  model-converters:
    pageable-converter:
      enabled: true

对于已经存在的项目, 当刚引入springdoc时, 需要配置大量的@ParameterObject注解以实现参数转换功能.
这个过程通常比较繁琐, 因为对于一个具有一定规模的项目而言, 其对外提供的API往往会非常多, 需要进行大量的修改功能才能实现该功能
那么此时我们需要一种解决方案, 即如何在不配置@ParameterObject的情况下实现Pageable以及Sort对象到API参数的转换

思路

@ParameterObject

springdoc中, @ParameterObject注解的获取是来自于spring-webmvc中的HandlerMethod对象

HandlerMethod

这个对象是我们在Controller中声明的Handler的抽象, 它主要保存了我们声明的Handler的一些元信息.
它的MethodParameter[] parameters属性包含了该方法的所有参数的信息.

MethodParameter

MethodParameter对象的Annotation[] parameterAnnotations属性包含了该参数的所有注解.
我们可以尝试修改Annotation[] parameterAnnotations属性以实现动态添加@ParameterObject注解的功能.

HandlerMapping

我们不能直接从spring容器中获取到MethodHandler, 因为其是保存在HandlerMapping中的, HandlerMapping 可以从容器中获取到.

BeanPostProcessor

spring中提供了BeanPostProcess机制, 主要实现了对Bean的创建进行拦截处理.
我们可以实现BeanPostProcessor来对HandlerMapping中的MethodHandler中的MethodParameterparameterAnnotations 进行修改以实现追加@ParameterObject注解

解决方案

新增AppendSpringdocAnnotationBeanPostProcessor用于实现增加@ParameterObject注解的能力

/**
 * We must add @ParameterObject annotation to Pageable and Sort parameter to ensure that springdoc can generate correct.
 * But declaring @ParameterObject in the controller method is not a good idea, because it will take some time to.
 * So we can use this class to avoid declaring @ParameterObject on method parameter which type is Pageable or Sort.
 *
 * @author Xiangcheng.Kuo
 * @see org.springdoc.core.annotations.ParameterObject
 * @see <a href="https://springdoc.org/v2/#how-can-i-map-pageable-spring-data-commons-object-to-correct-url-parameter-in-swagger-ui">13.22. How can I map Pageable (spring-data-commons) object to correct URL-Parameter in Swagger UI?</a>
 */
internal class AppendSpringdocAnnotationBeanPostProcessor : BeanPostProcessor {

	override fun postProcessAfterInitialization(bean: Any, beanName: String): Any {
		if (bean !is AbstractHandlerMethodMapping<*>) {
			return bean
		}
		bean.handlerMethods?.forEach { (_: Any, handlerMethod: HandlerMethod) ->
			handlerMethod
				.methodParameters
				.filter {
					Pageable::class.java.isAssignableFrom(it.parameterType) || Sort::class.java.isAssignableFrom(it.parameterType)
				}.forEach { methodParameter: MethodParameter ->
					addAnnotationForParameter(methodParameter)
				}
		}
		return bean
	}

	private fun addAnnotationForParameter(methodParameter: MethodParameter) {
		val parameterAnnotationsField =
			FieldUtils.getDeclaredField(MethodParameter::class.java, "parameterAnnotations", true)
		val annotations: MutableList<Annotation> =
			((parameterAnnotationsField[methodParameter] as Array<Annotation>?)
				?: emptyArray<Annotation>()).toMutableList()

		if (annotations.stream().anyMatch(ParameterObject::class::isInstance)) {
			return
		}

		annotations.add(FakeParameterObject.create())
		parameterAnnotationsField[methodParameter] = annotations.toTypedArray()
	}

}

新增@ParameterObject的实现类, 由于kotlin不支持继承annotation, 需要新建java类来继承annotation

/**
 * Avoid the following kotlin compile error
 * This type has a constructor, and thus must be initialized here
 * This type is final, so it cannot be inherited from
 *
 * @author Xiangcheng.Kuo
 * @see <a href="https://stackoverflow.com/questions/51608924/implement-inherit-extend-annotation-in-kotlin">Implement (/inherit/~extend) annotation in Kotlin</a>
 */
public class FakeParameterObject implements ParameterObject {

	@Override
	public Class<? extends Annotation> annotationType() {
		return FakeParameterObject.class;
	}

	public static ParameterObject create() {
		return new FakeParameterObject();
	}

}

新增Configuration, 将AppendSpringdocAnnotationBeanPostProcessor添加到容器中

/**
 * @author Xiangcheng.Kuo
 */
@Configuration
internal class ApidocAutoConfiguration {

	@Bean
	@ConditionalOnProperty(Constants.SPRINGDOC_ENABLED, matchIfMissing = true)
	fun appendSpringdocAnnotationBeanPostProcessor(): BeanPostProcessor =
		AppendSpringdocAnnotationBeanPostProcessor()

}

备注

反射

该解决方法是基于反射的, 并且在springBeanPostProcessor中修改了MethodParameterparameterAnnotations属性. 该属性可能会在spring的其他地方被使用, 因此可能会引起一些不可预知的问题.

springdoc应该在生产环境中关闭

在生产环境中, 不应该开启springdoc, 因为这会暴露swaggerapi文档, 从而导致api文档泄露,

使用spring中提供的profile功能以实现在开发模式下开启springdoc, 生产模式下关闭springdoc

springdoc:
  api-docs:
    enabled: false
  model-converters:
    pageable-converter:
      enabled: true
springdoc:
  api-docs:
    enabled: true
  model-converters:
    pageable-converter:
      enabled: true

参考

springdoc-v2

spring

kotlin


Edit page
Share this post on:

Previous Post
常见的命名前缀
Next Post
通过环境变量注入数组到应用程序配置