Spring原生支持子上下文层次结构详解

小豆丁 10小时前 ⋅ 3 阅读
聊天记录
头像

子上下文是Spring 原生支持吗?

头像

是的,子上下文是 Spring 框架的原生特性,称为 ApplicationContext 层次结构(Hierarchical ApplicationContext)。

这是 Spring 的核心设计之一,让我详细解释:

Spring 原生支持的上下文层次结构

1. 基本概念

JAVA
// Spring 原生支持创建父子上下文
public interface ApplicationContext extends ... {
    ApplicationContext getParent();  // 获取父上下文
    
    // 层次结构相关方法
    boolean containsBeanDefinition(String beanName);
    String[] getBeanDefinitionNames();
}

2. 创建父子上下文的原生方式

JAVA
// 手动创建父子上下文层次结构
public class SpringHierarchyDemo {
    
    public static void main(String[] args) {
        // 1. 创建父上下文
        AnnotationConfigApplicationContext parentContext = 
            new AnnotationConfigApplicationContext();
        parentContext.register(ParentConfig.class);
        parentContext.refresh();
        
        // 2. 创建子上下文,并设置父上下文
        AnnotationConfigApplicationContext childContext = 
            new AnnotationConfigApplicationContext();
        childContext.setParent(parentContext);  // 设置父子关系
        childContext.register(ChildConfig.class);
        childContext.refresh();
        
        // 3. 验证层次结构
        System.out.println("Child parent: " + (childContext.getParent() == parentContext));
        // 输出: true
        
        // 4. 验证 Bean 访问
        // 子可以访问父的 Bean
        ParentBean parentBeanFromChild = childContext.getBean(ParentBean.class);
        System.out.println("子上下文能访问父的 Bean: " + (parentBeanFromChild != null));
        // 输出: true
        
        // 父不能访问子的 Bean
        try {
            ChildBean childBeanFromParent = parentContext.getBean(ChildBean.class);
        } catch (NoSuchBeanDefinitionException e) {
            System.out.println("父上下文不能访问子的 Bean: " + e.getMessage());
        }
        
        childContext.close();
        parentContext.close();
    }
}

@Configuration
class ParentConfig {
    @Bean
    public ParentBean parentBean() {
        return new ParentBean();
    }
}

@Configuration  
class ChildConfig {
    @Bean
    public ChildBean childBean() {
        return new ChildBean();
    }
}

Spring 中上下文层次结构的常见用途

1. Spring MVC 的经典分层

JAVA
// Spring MVC 的传统配置
public class WebAppInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) {
        // 1. 创建根上下文(父)
        AnnotationConfigWebApplicationContext rootContext = 
            new AnnotationConfigWebApplicationContext();
        rootContext.register(RootConfig.class);
        
        // 2. 创建 Servlet 上下文(子)
        AnnotationConfigWebApplicationContext dispatcherContext = 
            new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebMvcConfig.class);
        dispatcherContext.setParent(rootContext);  // 设置父子关系
        
        // 3. 配置 DispatcherServlet
        ServletRegistration.Dynamic dispatcher = 
            servletContext.addServlet("dispatcher", 
                new DispatcherServlet(dispatcherContext));
    }
}

@Configuration
class RootConfig {
    // 业务逻辑、数据访问等共享 Bean
    @Bean
    public DataSource dataSource() { ... }
    
    @Bean
    public JpaTransactionManager transactionManager() { ... }
}

@Configuration
@EnableWebMvc
class WebMvcConfig implements WebMvcConfigurer {
    // MVC 相关配置
    // 可以覆盖父上下文的一些定义
}

2. Spring Boot 的内置层次结构

JAVA
// Spring Boot 也有层次结构
@SpringBootTest
class SpringBootHierarchyTest {
    
    @Test
    void testSpringBootHierarchy() {
        SpringApplication app = new SpringApplication(MyApp.class);
        ConfigurableApplicationContext context = app.run();
        
        // Spring Boot 应用默认的层次结构
        System.out.println("应用上下文类型: " + context.getClass());
        
        // 检查是否有父上下文
        ApplicationContext parent = context.getParent();
        if (parent != null) {
            System.out.println("父上下文: " + parent.getClass());
        }
        
        context.close();
    }
}

Spring Cloud Feign 如何使用这个特性

JAVA
// Spring Cloud Feign 源码简化
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
    
    // FeignContext 继承自 NamedContextFactory
    // NamedContextFactory 管理多个子上下文
}

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {
    
    // 存储子上下文的映射
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    
    // 为每个名称创建子上下文
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        
        // 设置父上下文
        if (this.applicationContext != null) {
            context.setParent(this.applicationContext);
        }
        
        // 注册配置
        context.register(DefaultConfigurations.class);
        context.register(getConfigurationsForName(name));
        
        context.refresh();
        contexts.put(name, context);
        return context;
    }
}

实际验证 Spring 原生层次结构

JAVA
@SpringBootTest
class SpringHierarchyProofTest {
    
    @Test
    void testSpringNativeHierarchy() {
        // 1. 证明 Spring 原生支持层次结构
        AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
        parent.registerBean("parentBean", String.class, () -> "parent-value");
        parent.refresh();
        
        AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
        child.setParent(parent);
        child.registerBean("childBean", String.class, () -> "child-value");
        child.refresh();
        
        // 2. 验证 Bean 可见性
        // 子可以访问父
        String parentBeanFromChild = child.getBean("parentBean", String.class);
        System.out.println("子访问父: " + parentBeanFromChild);  // parent-value
        
        // 父不能访问子
        try {
            String childBeanFromParent = parent.getBean("childBean", String.class);
        } catch (NoSuchBeanDefinitionException e) {
            System.out.println("父不能访问子: " + e.getMessage());
        }
        
        // 3. 验证 Bean 覆盖
        AnnotationConfigApplicationContext child2 = new AnnotationConfigApplicationContext();
        child2.setParent(parent);
        child2.registerBean("parentBean", String.class, () -> "overridden-value");
        child2.refresh();
        
        // 子上下文覆盖父的 Bean
        String overridden = child2.getBean("parentBean", String.class);
        System.out.println("子覆盖父: " + overridden);  // overridden-value
        
        // 父的 Bean 不变
        String original = parent.getBean("parentBean", String.class);
        System.out.println("父的原始: " + original);  // parent-value
        
        child2.close();
        child.close();
        parent.close();
    }
}

Spring 层次结构的源码支持

JAVA
// Spring 框架核心接口
public interface ApplicationContext extends ... {
    
    /**
     * 返回父上下文,如果没有则返回 null
     */
    @Nullable
    ApplicationContext getParent();
    
    /**
     * 获取父上下文的 BeanFactory
     */
    @Nullable
    ConfigurableListableBeanFactory getParentBeanFactory();
}

// 在 AbstractApplicationContext 中的实现
public abstract class AbstractApplicationContext extends ... {
    
    @Nullable
    private ApplicationContext parent;
    
    // 在获取 Bean 时,会检查父上下文
    @Override
    public Object getBean(String name) throws BeansException {
        // 先在本上下文中查找
        // 如果找不到,并且有父上下文,则在父上下文中查找
        return doGetBean(name, null, null, false);
    }
}

层次结构的 Bean 查找规则

JAVA
@Component
class BeanLookupDemo {
    
    @Autowired
    private ApplicationContext context;
    
    public void demonstrateLookupRules() {
        // 创建层次结构
        ApplicationContext grandParent = createContext("grandParent");
        ApplicationContext parent = createContext("parent");
        parent.setParent(grandParent);
        ApplicationContext child = createContext("child");
        child.setParent(parent);
        
        // 1. 查找顺序:自底向上
        // 当在 child 中查找 Bean 时:
        // 1) 先在 child 中找
        // 2) 然后在 parent 中找  
        // 3) 最后在 grandParent 中找
        
        // 2. Bean 定义覆盖
        // 子上下文可以定义同名的 Bean,覆盖父上下文的定义
        // 查找时,优先使用子上下文中的定义
        
        // 3. 原型 Bean 的实例化
        // 每个上下文都有自己的原型 Bean 实例
    }
    
    private ApplicationContext createContext(String name) {
        GenericApplicationContext ctx = new GenericApplicationContext();
        ctx.registerBean(name + "Bean", String.class, () -> "value-from-" + name);
        ctx.refresh();
        return ctx;
    }
}

为什么 Spring 要支持层次结构?

1. 模块化和隔离

JAVA
// 不同模块可以有独立的配置
ApplicationContext moduleAContext = new AnnotationConfigApplicationContext();
moduleAContext.register(ModuleAConfig.class);

ApplicationContext moduleBContext = new AnnotationConfigApplicationContext();
moduleBContext.register(ModuleBConfig.class);

// 根上下文共享公共配置
ApplicationContext rootContext = new AnnotationConfigApplicationContext();
rootContext.register(SharedConfig.class);

moduleAContext.setParent(rootContext);
moduleBContext.setParent(rootContext);

2. 配置覆盖

JAVA
// 根上下文:默认配置
@Configuration
class DefaultConfig {
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();  // 默认数据源
    }
}

// 子上下文:覆盖配置
@Configuration
class OverrideConfig {
    @Bean
    public DataSource dataSource() {
        return new CustomDataSource();  // 自定义数据源
    }
}

3. 资源管理

JAVA
// 每个子上下文可以独立关闭
ApplicationContext child1 = createChildContext();
ApplicationContext child2 = createChildContext();

// 独立管理生命周期
child1.close();  // 只关闭 child1,不影响 child2

4. 测试隔离

JAVA
@SpringBootTest
class TestIsolation {
    
    // 每个测试可以有自己的子上下文
    // 避免测试之间的干扰
    @Test
    void test1() {
        // 使用独立的上下文配置
    }
    
    @Test  
    void test2() {
        // 另一个独立的上下文
    }
}

Spring Cloud 对层次结构的扩展

JAVA
// Spring Cloud 扩展了 Spring 的层次结构
public class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements ApplicationContextAware, DisposableBean {
    
    // 管理多个命名上下文
    private Map<String, AnnotationConfigApplicationContext> contexts = new HashMap<>();
    
    // 为不同的客户端创建隔离的上下文
    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        return context.getBean(type);
    }
    
    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }
}

验证 Feign 使用的确实是 Spring 原生特性

JAVA
@SpringBootTest
class FeignUsesSpringHierarchyTest {
    
    @Autowired
    private FeignContext feignContext;
    
    @Test
    void verifyFeignUsesSpringHierarchy() {
        // 获取主应用上下文
        ApplicationContext mainContext = ...;
        
        // 获取 FeignClient 的子上下文
        ApplicationContext feignChildContext = 
            feignContext.getInstance("myFeignClient", ApplicationContext.class);
        
        // 验证确实是 Spring 的 ApplicationContext
        System.out.println("子上下文类型: " + feignChildContext.getClass());
        // 输出: org.springframework.context.annotation.AnnotationConfigApplicationContext
        
        // 验证父子关系
        System.out.println("父上下文: " + feignChildContext.getParent());
        System.out.println("父上下文就是主上下文: " + 
            (feignChildContext.getParent() == mainContext));
        
        // 验证层次结构的方法
        System.out.println("是否有父上下文: " + 
            (feignChildContext.getParent() != null));
        
        // 验证 BeanFactory 层次结构
        ConfigurableListableBeanFactory beanFactory = 
            (ConfigurableListableBeanFactory) feignChildContext.getAutowireCapableBeanFactory();
        System.out.println("父 BeanFactory: " + beanFactory.getParentBeanFactory());
    }
}

总结

是的,子上下文是 Spring 框架的原生特性

  1. 原生支持:Spring 框架的 ApplicationContext 从一开始就支持层次结构
  2. 核心接口ApplicationContext.getParent() 是框架标准接口
  3. 广泛使用:Spring MVC、Spring Boot、Spring Cloud 都利用了这个特性
  4. Feign 实现:Spring Cloud Feign 的 FeignContext 是基于 NamedContextFactory,而后者使用标准的 Spring 上下文层次结构
  5. 设计目的:实现配置隔离、Bean 覆盖、模块化和独立生命周期管理

你的 313 个 FeignClient 问题本质是

  • Spring 原生支持上下文层次结构
  • Spring Cloud Feign 为每个客户端创建独立的子上下文
  • 每个子上下文是标准的 Spring ApplicationContext
  • 每个子上下文都会重新实例化配置类中的 @Bean 方法
  • 要解决内存浪费,需要将共享 Bean 定义在父上下文中

全部评论: 0

    我有话说: