1. 概述

Spring Boot 支持创建多个独立的 ApplicationContext 并组织成父子层级结构。这种机制在模块化设计、资源共享和配置隔离等场景中非常实用。

本文重点介绍如何使用 Spring Boot 提供的 Fluent Builder API(即 SpringApplicationBuilder)来构建多层级的应用上下文。我们不会讲解 Spring Boot 基础搭建流程,这部分内容可参考官方文档或相关入门教程。

✅ 核心目标:掌握通过 fluent 风格 API 构建复杂上下文拓扑的能力
⚠️ 注意:本文面向已有 Spring Boot 使用经验的开发者,基础概念不再赘述


2. 应用上下文层级结构(Application Context Hierarchy)

Spring 容器支持上下文之间的父子关系,形成一个树状结构:

  • ✅ 子上下文可以访问父上下文中的 Bean
  • ❌ 父上下文无法访问子上下文中的 Bean
  • ✅ 子上下文可覆盖父上下文中同名 Bean 的定义
  • ✅ 一个父上下文可以有多个子上下文,但一个子上下文只能有一个直接父上下文
  • ✅ 利用层级隔离,可实现模块间松耦合,避免 Bean 冲突

典型应用场景包括:

  • 多租户系统
  • 后台管理与前台服务共享核心服务但独立 Web 配置
  • 微服务中聚合多个子系统到同一 JVM

3. 使用 SpringApplicationBuilder API

SpringApplicationBuilder 提供了链式调用风格的 API,便于构建上下文层级:

方法 作用说明
parent() 设置父上下文
child() 添加子上下文
sibling() 添加兄弟上下文(共用同一个父上下文)

我们将构建如下结构:

Parent Context (非 Web)
   ├── Child Context 1 (Web, port=8074)
   └── Sibling Context 2 (Web, port=8075)

两个 Web 子应用运行在同一 JVM,共享父上下文中的服务 Bean,但各自拥有独立的接口和配置。


3.1 父上下文:共享服务定义

父上下文用于存放多个子应用共享的 Bean,比如通用业务服务、数据源、安全组件等。

我们先定义一个基础服务接口:

public interface IHomeService {
    String getGreeting();
}

实现类注册为 Spring Bean:

@Service
public class HomeService implements IHomeService {

    public String getGreeting() {
        return "Welcome User";
    }
}

配置类启用组件扫描,确保 @Service 被加载:

@Configuration
@ComponentScan("com.example.parent")
public class ParentConfig {}

⚠️ 包路径隔离:为避免组件扫描冲突,每个上下文使用独立包名


3.2 子上下文 1:覆盖父级 Bean

第一个子应用运行在端口 8074,其配置如下:

配置文件 ctx1.properties

server.port=8074
server.servlet.context-path=/ctx1

spring.application.admin.enabled=false
spring.application.admin.jmx-name=org.springframework.boot:type=Ctx1Rest,name=Ctx1Application

✅ 注意设置了独立的 JMX 名称,防止 MBean 冲突

子上下文配置类

@Configuration
@ComponentScan("com.example.ctx1")
@EnableAutoConfiguration
@PropertySource("classpath:ctx1.properties")
public class Ctx1Config {
    
    @Bean
    public IHomeService homeService() {
        return new GreetingService();
    }
}

这里我们显式定义了一个 homeService Bean,它会 覆盖 父上下文中的同名 Bean。

自定义服务实现

@Service
public class GreetingService implements IHomeService {

    public String getGreeting() {
        return "Greetings for the day";
    }
}

控制器接口

@RestController
public class Ctx1Controller {

    @Autowired
    private IHomeService homeService;

    @GetMapping("/home")
    public String greeting() {
        return homeService.getGreeting(); // 返回 "Greetings for the day"
    }
}

访问 http://localhost:8074/ctx1/home 将返回子上下文提供的问候语。


3.3 兄弟上下文:继承父级 Bean

第二个子应用运行在端口 8075,不重新定义 homeService,而是直接使用父上下文的实现。

配置文件 ctx2.properties

server.port=8075
server.servlet.context-path=/ctx2

spring.application.admin.enabled=false
spring.application.admin.jmx-name=org.springframework.boot:type=WebAdmin,name=SpringWebApplication

配置类

@Configuration
@ComponentScan("com.example.ctx2")
@EnableAutoConfiguration
@PropertySource("classpath:ctx2.properties")
public class Ctx2Config {}

控制器

@RestController
public class Ctx2Controller {

    @Autowired
    private IHomeService homeService;

    @GetMapping("/greeting")
    public String getGreeting() {
        return homeService.getGreeting(); // 返回 "Welcome User"
    }
}

✅ 这里注入的 homeService 来自父上下文
❌ 若父上下文未提供该 Bean,则启动报错

访问 http://localhost:8075/ctx2/greeting 将返回父上下文的默认问候语。


3.4 构建上下文层级

最后,使用 SpringApplicationBuilder 组装整个结构:

public class App {
    public static void main(String[] args) {
        new SpringApplicationBuilder()
          .parent(ParentConfig.class).web(WebApplicationType.NONE)
          .child(Ctx1Config.class).web(WebApplicationType.SERVLET)
          .sibling(Ctx2Config.class).web(WebApplicationType.SERVLET)
          .run(args);
    }
}

关键点解析:

链式调用 说明
.parent(ParentConfig.class) 指定父上下文配置类
.web(WebApplicationType.NONE) 父上下文为非 Web 类型
.child(Ctx1Config.class) 添加第一个子上下文
.sibling(Ctx2Config.class) 添加兄弟上下文(也是子上下文)
.web(WebApplicationType.SERVLET) 明确指定为 Servlet Web 应用

启动后验证:

  • GET http://localhost:8074/ctx1/home"Greetings for the day"
  • GET http://localhost:8075/ctx2/greeting"Welcome User"

4. 总结

通过本文的示例,我们掌握了使用 SpringApplicationBuilder 构建复杂上下文层级的核心技巧:

父子继承:子上下文可复用父上下文的 Bean,减少重复配置
配置覆盖:子上下文可通过重新定义 Bean 实现定制化逻辑
兄弟共享:多个子上下文可共用父级资源,实现模块化协作
隔离安全:不同上下文之间 Bean 不可见,降低耦合风险

💡 实际项目中的建议:

  • 使用独立包路径避免 @ComponentScan 扫描错乱
  • 注意 JMX 名称冲突问题,尤其是在同一 JVM 多实例场景
  • 非 Web 父上下文适合放置通用服务、数据层、工具类等
  • Web 子上下文负责接口暴露、MVC 配置、安全控制等

该模式在需要 多接口模块共享核心服务但独立部署配置 的场景下非常实用,是一种简单粗暴又高效的架构手段。

示例代码已托管至 GitHub:https://github.com/example/spring-boot-ctx-fluent


原始标题:The Spring Boot Fluent Builder