小编典典

Spring Boot(JAR)具有多个调度程序servlet,用于带有Spring Data REST的不同REST API

spring-boot

我有一个使用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。
    • 调度程序ServletA。
    • Servlet映射:/ api / moduleA /
    • Spring Data REST的基本URI:/ api / moduleA /
    • 如果我检查URL / api / moduleA /,则应该发现资源X和Y。
    • API模块B:

    • 一个JAR,其中包含资源P和Q的PRespository和QRespository。

    • 调度程序ServletB。
    • Servlet映射:/ api / moduleB /
    • Spring Data REST的基本URI:/ api / moduleB /
    • 如果检查URL / api / moduleB /,我应该发现资源P和Q。
    • 更多模块…

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

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

我还尝试通过在每个子上下文中使用定义每个调度程序servlet的配置,每个RepositoryRestMvcConfiguration以及每个@EnableJpaRepositories注释定义不同的要扫描的包,来通过SpringApplicationBuilder在分层应用程序上下文中实现此目的。无论如何,我什至无法加载上下文,因为它们不是作为WebApplicationContext创建的,因此失败了,因为没有ServletContext可用。

任何帮助/建议吗?提前致谢。


阅读 292

收藏
2020-05-30

共1个答案

小编典典

我前一段时间找到了解决方案,但忘了在这里分享,因此感谢Jan提醒我。

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

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

  • 项目服务器
  • 项目API自动配置
  • 项目模块API
  • 项目模块b-api
  • 项目模块n-api

模块 项目服务器 是主要的模块。它声明了对 project-api-autoconfigure 的依赖,同时排除了对 project-api-
autoconfigure
project-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模块jar中每个API模块的配置类。

我创建了一个抽象类,每个自动配置类都从该类继承:

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-apiproject-module-b-api

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

最后但并非最不重要的一点是,我基于传递给gradle的属性来模拟Maven概要文件,在要加载的主模块 项目服务器
内创建了不同的gradle脚本。每个脚本都将需要包含的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"
}

或多或少。希望对您有帮助。

干杯。

2020-05-30