Philip Lowney
Philip Lowney

Reputation: 41

Using Springdoc-openapi in Spring MVC Application (Not Spring Boot)

We have an existing Spring application which is packaged as a war file.

We recently migrated from Swagger 2 to Springdoc-openapi, but we are having trouble getting our application to start with a working Springdoc configuration.

Our configuration looks as follows:

@Configuration
@ComponentScan(basePackages = {"org.springdoc"})
@EnableWebMvc
@Import({SpringDocConfiguration.class,
        SpringDocWebMvcConfiguration.class,
        org.springdoc.webmvc.ui.SwaggerConfig.class,
        SwaggerUiConfigProperties.class,
        SwaggerUiOAuthProperties.class,
        JacksonAutoConfiguration.class})
public class OpenApiConfiguration implements WebMvcConfigurer {
    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI();
    }
}

We have the following dependencies, with spring.version=6.0.11:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.1.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot</artifactId>
    <version>3.1.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>3.1.2</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.1.2</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-framework-bom</artifactId>
    <version>${spring.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

When we start the application, we receive the following error:

2023-08-08 17:01:40,702 \[RMI TCP Connection(2)-127.0.0.1\] ERROR org.springframework.web.context.ContextLoader - \[\] Context initialization failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'openApiWebMvcResource' defined in URL \[jar:file:/Users/.../WEB-INF/lib/springdoc-openapi-starter-webmvc-api-2.1.0.jar!/org/springdoc/webmvc/api/OpenApiWebMvcResource.class\]: Unsatisfied dependency expressed through constructor parameter 5: Error creating bean with name 'springDocProviders' defined in org.springdoc.core.configuration.SpringDocConfiguration: Unsatisfied dependency expressed through method 'springDocProviders' parameter 3: Error creating bean with name 'springRepositoryRestResourceProvider' defined in org.springdoc.core.configuration.SpringDocDataRestConfiguration$SpringRepositoryRestResourceProviderConfiguration: Unsatisfied dependency expressed through method 'springRepositoryRestResourceProvider' parameter 0: No qualifying bean of type 'org.springframework.data.rest.core.mapping.ResourceMappings' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) \~\[spring-beans-6.0.11.jar:6.0.11\]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:245) \~\[spring-beans-6.0.11.jar:6.0.11\]

...

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springDocProviders' defined in org.springdoc.core.configuration.SpringDocConfiguration: Unsatisfied dependency expressed through method 'springDocProviders' parameter 3: Error creating bean with name 'springRepositoryRestResourceProvider' defined in org.springdoc.core.configuration.SpringDocDataRestConfiguration$SpringRepositoryRestResourceProviderConfiguration: Unsatisfied dependency expressed through method 'springRepositoryRestResourceProvider' parameter 0: No qualifying bean of type 'org.springframework.data.rest.core.mapping.ResourceMappings' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) \~\[spring-beans-6.0.11.jar:6.0.11\]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:550) \~\[spring-beans-6.0.11.jar:6.0.11\]
at 

...

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springRepositoryRestResourceProvider' defined in org.springdoc.core.configuration.SpringDocDataRestConfiguration$SpringRepositoryRestResourceProviderConfiguration: Unsatisfied dependency expressed through method 'springRepositoryRestResourceProvider' parameter 0: No qualifying bean of type 'org.springframework.data.rest.core.mapping.ResourceMappings' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) \~\[spring-beans-6.0.11.jar:6.0.11\]


We note that others on Stack Overflow have had a problem starting a non-boot application with Springdoc but they received a different error.

On Springdoc's GitHub there is another mention of how to configure a non-boot application but the solution recommended there, to add @ComponentScan(basePackages = {"org.springdoc"}) doesn't fix the issue.

Any help is appreciated!

Upvotes: 1

Views: 2379

Answers (1)

Philip Lowney
Philip Lowney

Reputation: 41

We have now resolved this issue, with thanks to the folks over on Springdoc's GitHub project.

We created a test project to demo the issue here.

As per this answer to our GitHub ticket, The fix was as follows:

  • Upgrade to use springdoc v2.2.0
  • As we weren't using Spring Data Rest, we needed to exclude the accompanying Springdoc configuration class.

In the case of our demo application, the exclusion of the relevant configuration class can be done as per the answer on GitHub, using @EnableAutoConfiguration(exclude=SpringDocDataRestConfiguration.class)

In the case of our actual application, we ended up using custom filters to exclude both SpringDocDataRestConfiguration and SpringDocHateoasConfiguration, as per this comment, i.e.

@Configuration
@ComponentScan(
        basePackages = {"org.springdoc"},
        excludeFilters = {@ComponentScan.Filter(
                type = FilterType.CUSTOM,
                classes = {
                        OpenApiConfiguration.SpringDocDataRestFilter.class,
                        OpenApiConfiguration.SpringDocHateoasFilter.class
                }
        )}
)
public class OpenApiConfiguration {

    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI();
    }

    public static class SpringDocDataRestFilter extends ClassFilter {
        @Override
        protected Class<?> getFilteredClass() {
            return SpringDocDataRestConfiguration.class;
        }
    }

    public static class SpringDocHateoasFilter extends ClassFilter {
        @Override
        protected Class<?> getFilteredClass() {
            return SpringDocHateoasConfiguration.class;
        }
    }

    private static abstract class ClassFilter implements TypeFilter {
        protected abstract Class<?> getFilteredClass();

        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            String className = metadataReader.getClassMetadata().getClassName();
            String enclosingClassName = metadataReader.getClassMetadata().getEnclosingClassName();
            return
                    className.equals(getFilteredClass().getCanonicalName())
                            || (enclosingClassName!=null && enclosingClassName.equals(getFilteredClass().getCanonicalName()));
        }
    }
}

Upvotes: 2

Related Questions