Spring Boot (JAR) with multiple dispatcher servlets for different REST APIs with Spring Data REST

懵懂的女人 提交于 2019-11-30 09:42:20

I found the solution a while ago but I forgot to share it here, so thanks Jan for reminding me that.

I solved it by creating and registering several dispatchers servlets with new web application contexts with different configurations (RepositoryRestMvcConfiguration) and a common parent which is the root application context of the Spring Boot application. To enable the API modules automatically depending on the different jars included on the classpath I emulated what Spring Boot does more or less.

The project is divided in several gradle modules. Something like this:

  • project-server
  • project-api-autoconfigure
  • project-module-a-api
  • project-module-b-api
  • ...
  • project-module-n-api

The module project-server is the main one. It declares a dependency on project-api-autoconfigure, and at the same time it excludes the transitive dependencies of project-api-autoconfigure on project-module-?-api module(s).

Inside project-server.gradle:

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

project-api-autoconfigure depends on all the API modules, so the dependencies will look like this on project-api-autoconfigure.gradle:

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

project-api-autoconfigure is where I create the dispatcher servlet beans with their own web application context for every API module, but this configurations are conditional on the configuration classes of every API module which live inside each API module jar.

I created and abstract class from which every autoconfiguration class inherit:

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();

}

So now, the autoconfiguration class for module A will look something like this:

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

}

And now, your ApiModuleAConfiguration, ApiModuleBConfiguration... configuration classes will be on each api module project-module-a-api, project-module-b-api...

They can be RepositoryRestMvcConfiguration or they can extend from it or they can be any other configuration class that imports the Spring Data REST configuration.

And last but not least, I created different gradle scripts inside the main module project-server to be loaded based on a property passed to gradle to emulate Maven profiles. Each script declares as dependencies the api modules that need to be included. It looks something like this:

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

and for example, profile-X enables API modules A and 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'
}

Other profiles could enable different API modules.

Profiles are loaded this way from the 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"
}

And that's all more or less. I hope it helps you Jan.

Cheers.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!