Spring父子容器

问题呈现

一个容器可以存在父容器,比如定义一个 Book 类

java 复制代码
public class Book {
    private String name;
    private Double price;

    private Integer id;

    // set get toString...
}

定义两个 XML 文件,一个是父容器的,一个是子容器的:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.example.demo.Book" id="book" >
        <property name="id" value="111" />
        <property name="name" value="刘和广" />
        <property name="price" value="77d" />
    </bean>
</beans>
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.example.demo.Book" id="book" >
        <property name="id" value="222" />
        <property name="name" value="刘和广之子" />
        <property name="price" value="77d" />
    </bean>
</beans>

现在去加载两个容器,注意必须手动 setConfigLocations,配置文件不能放在 ClassPathXmlApplicationContext 构造方法

当从 child 中查找 bean 的时候,首先去 child 容器中查找,这个容器中如果不存在该 bean,那么就去父容器中查找,父容器中不存在,就去爷爷容器中查找。。。

java 复制代码
public class App {
    public static void main(String[] args) {
        // 父子容器
        ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext();
        parent.setConfigLocations("parent.xml");
        parent.refresh();
        ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext();
        child.setConfigLocations("child.xml");
        child.setParent(parent);
        child.refresh();
        Book book = child.getBean("book", Book.class);
        System.out.println("book = " + book);
    }
}

原理分析

要实现父子容器的效果,有一个前提,就是我们的容器本身要支持父子容器,而我们日常开发中最常用的 DefaultListableBeanFactory 这个容器,就是支持父子容器的:

HierarchicalBeanFactory 接口就表示带有层级关系的父子容器了,实现了该接口,就表示这个容器带有层级关系,即具备父子容器的功能。那么接下来,在 Bean 查找的过程中,首先就在当前容器中查找,如果找不到,就去父容器中查找。

根据类型查找,下面这个 getBean 方法最终会调用到 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveBean 方法:

text 复制代码
Book book = child.getBean( Book.class);
java 复制代码
private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
    //这里就是尝试在当前容器中去查找这个目标 bean
    NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
    if (namedBean != null) {
        return namedBean.getBeanInstance();
    }
    //如果当前容器中没有找到 Bean,则去查找到父容器,然后去父容器中查找 bean
    BeanFactory parent = getParentBeanFactory();
    if (parent instanceof DefaultListableBeanFactory dlfb) {
        //父容器存在,并且是 DefaultListableBeanFactory 类型的,那么则调用 resolveBean 方法去处理 bean,这其实相当于递归
        return dlfb.resolveBean(requiredType, args, nonUniqueAsNull);
    } else if (parent != null) {
        //父容器不是 DefaultListableBeanFactory 类型,但是父容器又存在,那么尝试调用父容器中的 getBeanProvider 方法去获取 bean
        ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
        if (args != null) {
            return parentProvider.getObject(args);
        } else {
            return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable());
        }
    }
    return null;
}

根据名字去查找 bean,最终会来到 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 方法中:

text 复制代码
Book book = child.getBean("book", Book.class);
java 复制代码
protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {
   ...

   else{
       // Fail if we're already creating this bean instance:
       // We're assumably within a circular reference.
       if (isPrototypeCurrentlyInCreation(beanName)) {
           throw new BeanCurrentlyInCreationException(beanName);
       }
       // 子容器不存在,去父容器查找
       BeanFactory parentBeanFactory = getParentBeanFactory();
       if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
           // Not found -> check parent.
           String nameToLookup = originalBeanName(name);
           if (parentBeanFactory instanceof AbstractBeanFactory abf) {
               return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
           } else if (args != null) {
               // Delegation to parent with explicit args.
               return (T) parentBeanFactory.getBean(nameToLookup, args);
           } else if (requiredType != null) {
               // No args -> delegate to standard getBean method.
               return parentBeanFactory.getBean(nameToLookup, requiredType);
           } else {
               return (T) parentBeanFactory.getBean(nameToLookup);
           }
       }
   }
   return adaptBeanInstance(name, beanInstance, requiredType);
}
相关推荐
xifangge202519 分钟前
AGENTS.md 怎么写?涵盖 Java、Python、Vue、Go 的 8 套开箱即用模板
java·vue.js·python
Wang's Blog23 分钟前
Java框架快速入门: Spring Security+OAuth2之云服务集成与多因子认证设计
java·开发语言·spring
xinjia_ctrl1 小时前
暑假实习总结
git·后端·spring·maven·intellij-idea
liangsheng_g1 小时前
SpringAOP拦截器链递归与事务钩子补偿源码实战
java·spring
SL_staff1 小时前
制造业私有化文档平台的技术实践:从知识孤岛到可追溯知识资产
java·spring·开源
SL_staff2 小时前
3天上线OKR系统:一名HR与1名工程师如何用JVS完成全栈交付
java·低代码·开源
2501_933923253 小时前
统一功能:统一返回格式+统一异常处理
spring·java-ee·状态模式·统一数据返回
wzdark4 小时前
基于链表的内存池设计与内存复用机制4
java·数据结构·链表
cnkeysky4 小时前
idea 中的 maven 项目重新加载子模块
java·idea
Escalating_xu4 小时前
【System V 信号量】从 P/V 原语到 Builder 封装:写出可控、可清理的进程互斥组件
java·linux·开发语言·jvm