子上下文是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 框架的原生特性:
- 原生支持:Spring 框架的
ApplicationContext从一开始就支持层次结构 - 核心接口:
ApplicationContext.getParent()是框架标准接口 - 广泛使用:Spring MVC、Spring Boot、Spring Cloud 都利用了这个特性
- Feign 实现:Spring Cloud Feign 的
FeignContext是基于NamedContextFactory,而后者使用标准的 Spring 上下文层次结构 - 设计目的:实现配置隔离、Bean 覆盖、模块化和独立生命周期管理
你的 313 个 FeignClient 问题本质是:
- Spring 原生支持上下文层次结构
- Spring Cloud Feign 为每个客户端创建独立的子上下文
- 每个子上下文是标准的 Spring
ApplicationContext - 每个子上下文都会重新实例化配置类中的
@Bean方法 - 要解决内存浪费,需要将共享 Bean 定义在父上下文中