使用 Springfox 记录 Spring 应用程序中的 jax-rs 服务

Using Springfox to document jax-rs services in a Spring app

我想记录一个使用 Springfox 的现有应用程序的 API。我将这些依赖项添加到 pom.xml:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.2.2</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.2.2</version>
</dependency>

我已经将 swagger 包添加到 web.xml 中的 jersey 包中,以便选择 Swagger2Controller:

<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container, see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html#d4e194 -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-context.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>myapp</filter-name>
        <filter-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</filter-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>org.mystuff;springfox.documentation</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.config.property.JSPTemplatesBasePath</param-name>
            <param-value>/WEB-INF/views</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.config.property.WebPageContentRegex</param-name>
            <param-value>/(js|css|images|resources)/.*</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.config.feature.Redirect</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.config.feature.FilterForwardOn404</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>myapp</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

应用程序上下文指定服务等的注释:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:property-placeholder ignore-unresolvable="true" location="classpath*:default.properties, classpath*:${runtime.environment}.properties" />
    <context:annotation-config/>
    <context:component-scan base-package="org.myapp" use-default-filters="true" />

    <aop:aspectj-autoproxy/>
</beans>

配置 class 看起来像:

@Configuration
@EnableSwagger2
public class ApiDocumentationConfiguration {

    @Bean
    public Docket documentation() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.regex("/api/.*"))
                .build();
    }

    @Bean
    public UiConfiguration uiConfig() {
        return UiConfiguration.DEFAULT;
    }

    private ApiInfo metadata() {
        return new ApiInfoBuilder()
                .title("My awesome API")
                .description("Some description")
                .version("1.0")
                .contact("my-email@domain.org")
                .build();
    }
}

启动时有堆栈跟踪:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'documentationPluginsBootstrapper' defined in URL [jar:file:/home/don/.m2/repository/io/springfox/springfox-spring-web/2.2.2/springfox-spring-web-2.2.2.jar!/springfox/documentation/spring/web/plugins/DocumentationPluginsBootstrapper.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [springfox.documentation.spi.service.RequestHandlerProvider]: : Error creating bean with name 'webMvcRequestHandlerProvider' defined in URL [jar:file:/home/don/.m2/repository/io/springfox/springfox-spring-web/2.2.2/springfox-spring-web-2.2.2.jar!/springfox/documentation/spring/web/plugins/WebMvcRequestHandlerProvider.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [java.util.List]: : No qualifying bean of type [org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webMvcRequestHandlerProvider' defined in URL [jar:file:/home/don/.m2/repository/io/springfox/springfox-spring-web/2.2.2/springfox-spring-web-2.2.2.jar!/springfox/documentation/spring/web/plugins/WebMvcRequestHandlerProvider.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [java.util.List]: : No qualifying bean of type [org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:185)
...
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webMvcRequestHandlerProvider' defined in URL [jar:file:/home/don/.m2/repository/io/springfox/springfox-spring-web/2.2.2/springfox-spring-web-2.2.2.jar!/springfox/documentation/spring/web/plugins/WebMvcRequestHandlerProvider.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [java.util.List]: : No qualifying bean of type [org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:185)
...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:999)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
    ... 71 more

这是集成 Swagger 的最佳方法吗?如果是这样,丢失的 bean 的修复方法是什么?或者有更简单的方法吗?

编辑:

将此添加到 web.xml 可消除堆栈跟踪:

<bean id="requestMappingHandlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

但是对 http://localhost:9090/myapp/v2/api-docs 的查询会导致 404,即使启动控制台显示:

2015-09-28 21:54:16,689 INFO  [RequestMappingHandlerMapping] - Mapped "{[/v2/api-docs],methods=[GET]}" onto public org.springframework.http.ResponseEntity<springfox.documentation.spring.web.json.Json> springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation(java.lang.String)
2015-09-28 21:54:16,690 INFO  [RequestMappingHandlerMapping] - Mapped "{[/configuration/ui]}" onto org.springframework.http.ResponseEntity<springfox.documentation.swagger.web.UiConfiguration> springfox.documentation.swagger.web.ApiResourceController.uiConfiguration()
2015-09-28 21:54:16,690 INFO  [RequestMappingHandlerMapping] - Mapped "{[/configuration/security]}" onto org.springframework.http.ResponseEntity<springfox.documentation.swagger.web.SecurityConfiguration> springfox.documentation.swagger.web.ApiResourceController.securityConfiguration()
2015-09-28 21:54:16,690 INFO  [RequestMappingHandlerMapping] - Mapped "{[/swagger-resources]}" onto org.springframework.http.ResponseEntity<java.util.List<springfox.documentation.swagger.web.SwaggerResource>> springfox.documentation.swagger.web.ApiResourceController.swaggerResources()

编辑:

也可以使用 jax-rs 映射 SpringFox URI:

<context:component-scan base-package="springfox.documentation" use-default-filters="true" />

...

@Autowired
private Swagger2Controller swagger2Controller;

...

@GET
@Path("/v2/api-docs")
public ResponseEntity<Json> apiDocs() {
    return swagger2Controller.getDocumentation(null);
}

但这并没有填充 SpringFox 需要的完整上下文,所以:

SEVERE: The RuntimeException could not be mapped to a response, re-throwing to the HTTP container
java.lang.IllegalStateException: Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?
    at org.springframework.util.Assert.state(Assert.java:385)
    at org.springframework.hateoas.mvc.ControllerLinkBuilder.getCurrentRequest(ControllerLinkBuilder.java:242)
    at org.springframework.hateoas.mvc.ControllerLinkBuilder.getBuilder(ControllerLinkBuilder.java:189)
    at org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo(ControllerLinkBuilder.java:85)

我将 DispatcherServlet 添加到 web.xml 以处理 Springfox 请求:

<servlet>
    <servlet-name>springfox</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>springfox</servlet-name>
    <url-pattern>/v2/api-docs/*</url-pattern>
</servlet-mapping>

接下来我在WEB-INF中创建了一个占位符springfox-servlet.xml

然后将这些行添加到 application-context.xml:

<bean id="swagger2Config" class="org.myapp.api.ApiDocumentationConfiguration"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

现在 api-docs 请求由 DispatcherServlet 正确路由,应用程序 jax-rs 请求由 Jersey SpringServlet 路由。