如何在模块化 Spring Boot 应用程序中定义隔离的 Web 上下文?

Posted

技术标签:

【中文标题】如何在模块化 Spring Boot 应用程序中定义隔离的 Web 上下文?【英文标题】:How to define isolated web contexts in a modular Spring Boot application? 【发布时间】:2016-10-06 04:33:29 【问题描述】:

假设一个 Spring Boot 应用程序由一个引导模块和两个或多个隔离的业务模块组成——每个模块都公开特定于业务域的 REST API,并且每个模块都使用独立的、隔离的文档存储来进行数据持久性,如何我要不要配置这样的应用程序,这样:

引导模块定义了一个父上下文(非 Web),它为底层模块(全局配置、对象映射器等)提供某些共享资源 每个业务模块都在同一端口上公开其 REST 控制器,但使用不同的上下文路径。理想情况下,我希望能够为每个模块(例如 /api/catalog、/api/orders)定义一个基本路径,并在每个控制器中单独定义子路径。 每个业务模块定义自己的存储库配置(例如,每个模块的不同 MongoDB 设置)

为了隔离各个业务模块的上下文(这允许我管理每个模块中的独立存储库配置),我尝试使用 SpringApplicationBuilder 中可用的上下文层次结构来隔离每个单独的业务模块的上下文:

public class Application 

    @Configuration
    protected static class ParentContext 
    

    public static void main(String[] args) throws Exception 
        new SpringApplicationBuilder(ParentContext.class)
            .child(products.config.ModuleConfiguration.class)
                .web(true)
            .sibling(orders.config.ModuleConfiguration.class)
                .web(true)
            .run(args);
    

但是,由于每个模块都包含一个使用 @EnableAutoConfiguration 注释的配置类,这会导致 Spring Boot 尝试启动两个独立的嵌入式 servlet 容器,每个容器都尝试绑定到同一个端口:

@Configuration
@EnableAutoConfiguration
public class WebApplicationConfiguration 

    @Value("$api.basePath:/api")
    protected String apiBasePath;

    @Bean
    public DispatcherServlet dispatcherServlet() 
        return new DispatcherServlet();
    

    @Bean
    public ServletRegistrationBean dispatcherServletRegistration() 
        ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(),
            apiBasePath + "/products/*");
        registration.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);

        return registration;
    

有关上下文层次结构的 Spring Boot 文档指出父上下文不能是 Web 上下文,所以我对如何在隔离的子上下文之间共享嵌入式 servlet 容器有点迷茫。

我创建了一个最小的GitHub 项目来说明这一点:

【问题讨论】:

【参考方案1】:

为业务模块child1的上下文创建配置类

package com.child1;
@Configuration
@ComponentScan(basePackages = "com.child1a", "com.child1b")
public class Child1Configuration 

为业务模块child2的上下文创建配置类

package com.child2;
@Configuration
@ComponentScan(basePackages = "com.child2a", "com.child2b")
public class Child2Configuration 


为引导模块父上下文创建一个配置类。指定组件扫描要由子上下文共享的 bean

package com.parent;
@Configuration
@ComponentScan(basePackages = "com.parent1", "com.root")
public class ParentConfiguration 


使用两个调度程序 servlet bean 创建 SpringBootApplication 类,每个业务模块一个。为每个 servlet 创建应用程序上下文,并将引导应用程序创建的上下文设置为 root。基本上spring会将上下文注入@Bean方法的ApplicationContext参数中。

package com.parent;
@SpringBootApplication
public class Application 

public static void main(String[] args) 
  ApplicationContext context = SpringApplication.run(Application.class, args);


     @Bean
     public ServletRegistrationBean child1(ApplicationContext parentContext) 

     DispatcherServlet dispatcherServlet = new DispatcherServlet();
     dispatcherServlet.setDetectAllHandlerMappings(false);

     AnnotationConfigWebApplicationContext applicationContext = new 
      AnnotationConfigWebApplicationContext();
     applicationContext.setParent(parentContext);
     applicationContext.register(Child1Configuration.class);

      applicationContext.refresh();

     dispatcherServlet.setApplicationContext(applicationContext);

    ServletRegistrationBean servletRegistrationBean = new 
     ServletRegistrationBean(dispatcherServlet, true, "/child1/*");

   servletRegistrationBean.setName("child1");

   servletRegistrationBean.setLoadOnStartup(1);

    return servletRegistrationBean;
    

    @Bean
    public ServletRegistrationBean child2(ApplicationContext parentContext) 



      DispatcherServlet dispatcherServlet = new DispatcherServlet();
      dispatcherServlet.setDetectAllHandlerMappings(false);

      AnnotationConfigWebApplicationContext applicationContext = new 
      AnnotationConfigWebApplicationContext();
      applicationContext.setParent(parentContext);

      applicationContext.register(Child2Configuration.class);

      applicationContext.refresh();

       dispatcherServlet.setApplicationContext(applicationContext);

     ServletRegistrationBean servletRegistrationBean = new 
      ServletRegistrationBean(dispatcherServlet, true, "/child2/*");

     servletRegistrationBean.setName("child2");

     servletRegistrationBean.setLoadOnStartup(1);

     return servletRegistrationBean;


【讨论】:

以上是关于如何在模块化 Spring Boot 应用程序中定义隔离的 Web 上下文?的主要内容,如果未能解决你的问题,请参考以下文章

如何在多模块spring boot应用程序gradle中只访问不同模块的一个类?

如何在我的 Maven Spring Boot 多模块项目的测试中引用兄弟模块?

如何在spring-boot中添加多个application.properties文件?

如何使用 CommandLineJobRunner 调用嵌入在 Spring Boot 应用程序中的 Spring Batch 作业

Spring Boot - 多模块多环境配置,大厂必备技能

如何在 application.properties 文件中覆盖 Spring Boot Starter 的默认属性?