【问题标题】:Spring Boot (JAR) with multiple dispatcher servlets for different REST APIs with Spring Data RESTSpring Boot (JAR) 具有多个调度程序 servlet,用于具有 Spring Data REST 的不同 REST API
【发布时间】:2015-02-03 19:26:08
【问题描述】:

我有一个项目使用 Spring Boot 生成一个可执行 JAR,该 JAR 使用 Spring Data REST 公开一个 REST API。它还与 Spring Security OAuth 集成。这很好用。我的问题如下,

我想为 REST API 提供不同的模块,只有当具有 JPA 存储库的对应 JAR 位于类路径中(它已被定义为依赖项)时,我才想启用这些模块。

问题是我希望它们彼此独立。我希望能够在具有不同映射的不同调度程序 servlet 下为它们提供服务,这样我就可以为每个服务程序指定不同的 baseUri,并为资源发现使用不同的根 URL。

我会尽量说清楚:

  • API 模块 A:

    • 一个 JAR,例如包含资源 X 和 Y 的 XRespository 和 YRespository。
    • Dispatcher servlet A.
    • Servlet 映射:/api/moduleA/
    • Spring Data REST 的基本 URI:/api/moduleA/
    • 如果我检查 URL /api/moduleA/,我应该会发现资源 X 和 Y。
  • API 模块 B:

    • 一个 JAR,例如包含资源 P 和 Q 的 PRespository 和 QRespository。
    • Dispatcher servlet B.
    • Servlet 映射:/api/moduleB/
    • Spring Data REST 的基本 URI:/api/moduleB/
    • 如果我检查 URL /api/moduleB/,我应该会发现资源 P 和 Q。
  • 更多模块...

除此之外,我可以拥有另一个调度程序 servlet,其中我持有 /oauth/* 端点以及其他自定义控制器,并且安全配置必须对所有 (/*) 正常工作

我知道我可以通过 ServletRegistrationBean 定义更多调度程序 servlet,但我不知道如何附加到每个不同的 spring 数据休息配置。

我也一直在尝试使用 SpringApplicationBuilder 对分层应用程序上下文执行此操作,方法是在每个子上下文中具有定义每个调度程序 servlet、每个 RepositoryRestMvcConfiguration 的配置,并让每个 @EnableJpaRepositories 注释定义要扫描的不同包。无论如何,我什至无法加载上下文,因为它们不是作为 WebApplicationContext 创建的,因此因为没有可用的 ServletContext 而失败。

有什么帮助/建议吗?提前致谢。

【问题讨论】:

  • 既然你在使用 Spring Boot,为什么不将每个 Spring Data Rest 应用部署在不同的 tomcat 和上下文中(完全独立)?也许您需要设置 CORS 以防止跨域。我看不出这种方法有什么问题。
  • 我会对此感兴趣。如果您找到了解决方案,请分享

标签: java spring spring-boot spring-data-jpa spring-data-rest


【解决方案1】:

我不久前找到了解决方案,但我忘了在这里分享,所以感谢 Jan 提醒我。

我通过使用具有不同配置 (RepositoryRestMvcConfiguration) 的新 Web 应用程序上下文和作为 Spring Boot 应用程序的根应用程序上下文的公共父级创建和注册几个调度程序 servlet 来解决这个问题。为了根据类路径中包含的不同 jar 自动启用 API 模块,我模拟了 Spring Boot 或多或少的功能。

项目分为几个 gradle 模块。像这样的:

  • 项目服务器
  • project-api-autoconfigure
  • project-module-a-api
  • project-module-b-api
  • ...
  • project-module-n-api

模块 project-server 是主要模块。它声明了对project-api-autoconfigure的依赖,同时排除了project-api-autoconfigureproject-module-?的传递依赖。 -api 模块

project-server.gradle里面:

dependencies {
    compile (project(':project-api-autoconfigure')) {
        exclude module: 'project-module-a-api'
        exclude module: 'project-module-b-api'
        ...
    }
    ...
}

project-api-autoconfigure 依赖于所有 API 模块,所以在 project-api-autoconfigure.gradle 上的依赖关系如下所示:

dependencies {
    compile project(':project-module-a-api')
    compile project(':project-module-b-api')
    ...
}

project-api-autoconfigure 是我为每个 API 模块创建具有自己的 Web 应用程序上下文的调度程序 servlet bean 的地方,但是这种配置取决于每个 API 模块的配置类每个 API 模块 jar。

我创建并抽象了每个自动配置类都继承自的类:

public abstract class AbstractApiModuleAutoConfiguration<T> {

    @Autowired
    protected ApplicationContext applicationContext;

    @Autowired
    protected ServerProperties server;

    @Autowired(required = false)
    protected MultipartConfigElement multipartConfig;

    @Value("${project.rest.base-api-path}")
    protected String baseApiPath;

    protected DispatcherServlet createApiModuleDispatcherServlet() {
        AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
        webContext.setParent(applicationContext);
        webContext.register(getApiModuleConfigurationClass());
        return new DispatcherServlet(webContext);
    }

    protected ServletRegistrationBean createApiModuleDispatcherServletRegistration(DispatcherServlet apiModuleDispatcherServlet) {
        ServletRegistrationBean registration = new ServletRegistrationBean(
                apiModuleDispatcherServlet,
                this.server.getServletMapping() + baseApiPath + "/" + getApiModulePath() + "/*");

        registration.setName(getApiModuleDispatcherServletBeanName());
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }
        return registration;
    }

    protected abstract String getApiModuleDispatcherServletBeanName();

    protected abstract String getApiModulePath();

    protected abstract Class<T> getApiModuleConfigurationClass();

}

所以现在,模块 A 的自动配置类将如下所示:

@Configuration
@ConditionalOnClass(ApiModuleAConfiguration.class)
@ConditionalOnProperty(prefix = "project.moduleA.", value = "enabled")
public class ApiModuleAAutoConfiguration extends AbstractApiModuleAutoConfiguration<ApiModuleAConfiguration> {

    public static final String API_MODULE_A_DISPATCHER_SERVLET_BEAN_NAME = "apiModuleADispatcherServlet";
    public static final String API_MODULE_A_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "apiModuleADispatcherServletRegistration";

    @Value("${project.moduleA.path}")
    private String apiModuleAPath;

    @Bean(name = API_MODULE_A_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet apiModuleADispatcherServlet() {
        return createApiModuleDispatcherServlet();
    }

    @Bean(name = API_MODULE_A_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    public ServletRegistrationBean apiModuleADispatcherServletRegistration() {
        return createApiModuleDispatcherServletRegistration(apiModuleADispatcherServlet());
    }

    @Override
    protected String getApiModuleDispatcherServletBeanName() {
        return API_MODULE_A_DISPATCHER_SERVLET_BEAN_NAME;
    }

    @Override
    protected String getApiModulePath() {
        return apiModuleAPath;
    }

    @Override
    protected Class<ApiModuleAConfiguration> getApiModuleConfigurationClass() {
        return ApiModuleAConfiguration.class;
    }

}

现在,您的 ApiModuleAConfigurationApiModuleBConfiguration... 配置类将在每个 api 模块 project-module-a-api 上, project-module-b-api...

它们可以是 RepositoryRestMvcConfiguration 或者它们可以从它扩展,或者它们可以是导入 Spring Data REST 配置的任何其他配置类。

最后但并非最不重要的一点是,我在主模块 project-server 中创建了不同的 gradle 脚本,以根据传递给 gradle 的属性来加载以模拟 Maven 配置文件。每个脚本都将需要包含的 api 模块声明为依赖项。它看起来像这样:

- project-server
    /profiles/
        profile-X.gradle
        profile-Y.gradle
        profile-Z.gradle

例如,profile-X 启用 API 模块 A 和 B:

dependencies {
    compile project(':project-module-a-api')
    compile project(':project-module-b-api')
}

processResources {
    from 'src/main/resources/profiles/profile-X'
    include 'profile-x.properties'
    into 'build/resources/main'
}

其他配置文件可以启用不同的 API 模块。

配置文件以这种方式从 project-server.gradle 加载:

loadProfile()

processResources {
    include '**/*'
    exclude 'profiles'
}

dependencies {
        compile (project(':project-api-autoconfigure')) {
            exclude module: 'project-module-a-api'
            exclude module: 'project-module-b-api'
            ...
        }
        ...
    }

...

def loadProfile() {
    def profile = hasProperty('profile') ? "${profile}" : "dev"
    println "Profile: " + profile
    apply from: "profiles/" + profile + ".gradle"
}

这或多或少。希望对你有所帮助。

干杯。

【讨论】:

  • 感谢分享丹尼尔......你有任何改变在 github 上有一个完整的工作示例吗?顺便说一句,您是否设法在同一服务器/进程上运行所有 API 模块?
  • @dimi,抱歉,我在公共回购中没有任何内容。如果我有时间,我可以做到,但基本上我之前解释过。除此之外,是的,所有 API 模块都在同一个嵌入式 servlet 容器上运行。
  • 没问题丹尼尔。仅供参考,我根据您的示例在github 上上传了一个 PoC(谢谢)。似乎工作正常,API 托管在两个基本 URL( localhost:8080/private/localhost:8080/public/ )下,但它也托管在根路径:localhost:8080/localhost/。如果您知道如何解决此问题,请大喊。 ta.
  • @dimi 尝试排除RepositoryRestMvcAutoConfiguration。由于您仍然拥有默认的 DispatcherServlet 并且您的 jpa 存储库位于根上下文中,因为您已在 App 类上声明了 @EnableJpaRepositories,因此 REST API 也将在根路径下公开。此外,如果您想在每个 API 下公开不同的资源,请在由不同调度程序 servlet 加载的每个配置上声明不同的 @EnableJpaRepositories 注释,而不是在根上下文中。
  • 谢谢丹尼尔;不包括@RepositoryRestMvcAutoConfiguration 确实成功了。 WRT 分开@EnableJpaRepositories,这听起来是个好主意,但不幸的是没有奏效。这可能是我的错,所以我会在周末再试一次。非常感谢!
猜你喜欢
  • 1970-01-01
  • 2017-03-11
  • 2019-07-04
  • 2014-05-02
  • 2016-11-29
  • 1970-01-01
  • 2018-10-25
  • 2020-03-15
  • 1970-01-01
相关资源
最近更新 更多