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

时间:2014-12-05 15:17:14

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

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

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

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

我会试着让它更清楚:

  • API的模块A:

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

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

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

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

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

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

1 个答案:

答案 0 :(得分:7)

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

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

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

  • 项目服务器
  • 项目-API的自动配置
  • 项目模块-A-API
  • 项目模块-B-API
  • ...
  • 项目模块 - 正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模块的配置类为条件的。每个API模块jar。

我创建并抽象了每个autoconfiguration类继承的类:

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;
    }

}

现在,您的 ApiModuleAConfiguration ApiModuleBConfiguration ...配置类将位于每个api模块 project-module-a-api 上, 项目模块-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"
}

这或多或少都是。我希望它可以帮助你Jan。

干杯。