Spring IoC 体系分析

一. 统一资源加载策略:

  1. Spring 将资源的定义和资源的加载区分出来

    • 资源描述接口: Resource
    • 资源加载规则接口: ResourceLoader
      用来根据定义的资源文件地址, 返回 Resource 对象. 该接口只有 2个方法
    Resource getResource(String location);  // 核心方法, 返回Resource
    ClassLoader getClassLoader();
    
  2. 资源和加载接口提供了默认实现

    • org.springframework.core.io.AbstractResourceResource 接口的默认抽象实现
      它实现了 Resource 接口的大部分的公共实现. 实现的默认方法包括: 判断文件是否存在, 最后修改时间等
    • DefaultResourceLoader类 为ResourceLoader 的默认实现, 每次只能返回单一的资源
    • PathMatchingResourcePatternResolver 是一个集大成者的 ResourceLoader. 它还实现了额外的接口 ResourcePatternResolver.
      既有 Resource getResource(String location) 方法,也有 Resource[] getResources(String locationPattern) 方法。

二. 加载 BeanDefinition

先看一段 spring 使用的标准代码

// <1> 获取资源
ClassPathResource resource = new ClassPathResource("application1.xml");
// <2> 获取 BeanFactory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// <3> 组合 BeanFactory 创建 BeanDefinitionReader, 该 Reader 为 Resource 的解析器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
// <4> 装载 Resource
reader.loadBeanDefinitions(resource); 

注释中初步给出 spring 容器的使用过程. 该过程可分三步理解:

  • 资源定位: 比如如果用 beans.xml 配置 bean, 则首先要拿到 beans.xml 这个资源文件. 这一步是返回 Resource 的过程
  • 装载:
    使用 BeanDefinitionReader 解析 Resource 文件, 将文件内容转变成 BeanDefinition 结构
    • 在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构
    • 在配置文件中每一个 <bean> 都对应着一个 BeanDefinition 对象。
  • 注册
    将解析出的 BeanDefinition 对象通过 BeanDefinitionRegistry 接口来实现注册, 将这些解析的 BeanDefinition 注入到一个 HashMap 容器中
    此时并没有创建 bean. 只是注册了 BeanDefinition
  1. 装载 BeanDefinition 在上面第<4>步
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }
        // this.resourcesCurrentlyBeingLoaded 是一个 ThreadLocal<Set<EncodedResource>>, 记录当前线程注册过的 EncodedResource
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
    
        try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // <核心流程>: 解析 Resource ,生成 BeanDefinition
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                // 当 currentResources 为空时, 证明该 ThreadLocal 已经不会被使用. 这会导致 ThreadLocalMap.Entry 
                // 键值对内部的 ThreadLocal key 软引用在 gc 时被置空. 而 key 为 null 的 value 访问不到, 却又 gc 不了, 导致溢出
                // 要及时把不再使用的 ThreadLocal 调用 remove 删除
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }
    
  2. 核心流程 doLoadBeanDefinitions()
    // XmlBeanDefinitionReader.java
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
        try {
            // <1> 解析 xml Resource, 返回 xml 的 Document 对象
            Document doc = doLoadDocument(inputSource, resource);
            // <2> 从 Document 对象中解析出 BeanDefinition 并注册
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
    }
    
    • <2> 步骤 registerBeanDefinitions(doc, resource) 返回注册的 BeanDefinition 个数
    // AbstractBeanDefinitionReader.java
    private final BeanDefinitionRegistry registry;
    
    // XmlBeanDefinitionReader.java
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        // <1> 创建 BeanDefinitionDocumentReader 对象
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // <2> 获取已注册的 BeanDefinition 数量
        int countBefore = getRegistry().getBeanDefinitionCount();
        // <3> 创建 XmlReaderContext 对象
        // <4> 注册 BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        // 计算新注册的 BeanDefinition 数量
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    
    • BeanDefinition 是去用来描述一个 bean.
      它的属性对应 xml 中配置的属性. 比如: 生成 bean 的工厂类, 生成 bean 的工厂方法, bean 的构造函数, bean 的 scope 等
      <!-- 空参构造函数生成bean -->
      <bean id="example1" class="xml.ExampleBean" init-method="init"/>
      
      <!-- 静态工厂生成 bean -->
      <bean id="example2"
            class="xml.FactoryExampleBean"
            factory-method="createInstance"/>
      
      <!-- 实例工厂生成 bean -->
      <bean id="instanceFactory"
            class="xml.instance.InstanceFactory"/>
      <bean id="example3"
            factory-bean="instanceFactory"
            factory-method="makeExample"
      />
      
  3. 什么是 BeanDefinition 的注册
    就是将<neanName, BeanDefinition><beanName,aliasName>两种映射关系注册到 BeanFactory
    • 步骤<1>, 注册 beanName 和 BeanDefinition 的方法是定义在 BeanFactory 内的.
      BeanFactory 内部持有 beanName 和 BeanDefinition 映射的 ConcurrentHashMap. 添加映射时, 还要用同步块处理并行的情况
    // DefaultListableBeanFactory.java
    
    // 是否允许同名 beanName 的 BeanDefinition 进行覆盖注册: true
    private boolean allowBeanDefinitionOverriding = true;
    /** 存储 beanName 和 BeanDefinition 映射的 HashMap */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    /** List of bean definition names, in registration order. */
    private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
    /** List of names of manually registered singletons, in registration order. */
    private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);
    
    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        ...  // 省略各种校验和判断是否可以覆盖同 beanName 和 BeanDefinition 的设置
        // beanDefinitionMap 为全局变量,避免并发情况
        // Cannot modify startup-time collection elements anymore (for stable iteration)
        synchronized (this.beanDefinitionMap) {
            // 添加到 BeanDefinition 到 beanDefinitionMap 中。
            this.beanDefinitionMap.put(beanName, beanDefinition);
            // 添加 beanName 到 beanDefinitionNames 中
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            // 从 manualSingletonNames 移除 beanName
            if (this.manualSingletonNames.contains(beanName)) {
                Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                updatedSingletons.remove(beanName);
                this.manualSingletonNames = updatedSingletons;
            }
        }       
    }
    
    • 步骤 <2> 中的注册 beanName 和 regist name 映射, 也是注册到一个 ConcurrentHashMap 中
    // SimpleAliasRegistry,java
    
    /** Map from alias to canonical name. */
    private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
    @Override
    public void registerAlias(String name, String alias) {
        ... 
        synchronized (this.aliasMap) {
            // (1) 循环检验: 看是否有和 nam->alias 相反的 alias->name 映射
            checkForAliasCircle(name, alias);
            // (2) 加入HashMap
            this.aliasMap.put(alias, name);
        }
    }
    

三. Spring Framework bean 的加载

  1. 如果是 singleton 的 bean, 尝试从单例缓存中获取

  2. 如果从单例缓存中没有获取到单例 Bean 对象,则说明两种两种情况:

    • 该 Bean 的 scope 不是 singleton
    • 该 Bean 的 scope 是 singleton ,但是没有初始化完成。
  3. 创建 bean 的过程

    1. 解析指定 BeanDefinition 的 class 属性。
      调用 ClassUtil.forName 解析类名为对应的类. 之所以没只用 Class.forName 是因为要解析的类名不只是包名.类名, 还有各种数组形式类名, 比如:
      * java.lang.String[] 类型数组
      * [Ljava.lang.String; 类型数组
      * [[I[[Ljava.lang.String; 类型
    2. 处理 override 属性。
      校验配置的 meta标签, loopup-method标签, replace-method标签 对应的方法是否存在
    3. 实例化的前置处理。
      BeanPostProcessors 一个机会返回 bean 的代理对象
    4. 创建 Bean 对象。
      (1) 如果配置了工厂方法(可能是静态方法, 也可能是实例方法.):
      1. 根据 factory-bean 是否为空, 判断是静态工厂还是实例工厂. 如果是实例工厂, 从beanFactory容器中获取 实例工厂对象
      2. 确定构造函数的参数 (3个来源)
      (1)explicitArgs 参数: getBean(...) 方法时传递进来的参数, 如果不为空, 就直接用它作为构造函数的参数
      (2)配置文件中解析: 配置文件中的信息, 都在BeanDefinition中, 可以从容器缓存的 BeanDefinition Map 中获取对应的 BeanDefinition, 再获取构造参数
      3. 获取所有的构造函数
      (1) 使用getCandidateMethods获取类的所有构造方法. (反射)
      (2) 对这些构造函数排序 (public的在前, 非public的在后; 构造参数多的在前, 少的在后)
      4. 筛选构造函数
      (1) 如果配置了 explicitArgs , 直接选取获取参数个数相同的构造方法
      (2) 如果没有配置, 就要调用org.springframework.core.ParameterNameDiscoverer作构造函数的参数探测
      (职责链模式, 先解析 javac -parameters 的输出; 没有在调用ASM库解析 class 文件内方法的局部参数列表) https://www.jianshu.com/p/8f5c2daa2b70
      (3) 筛选过程, 选择最为接近的构造函数. (参数类型的diffWight最小的构造函数)
      * 参数类型相同, diffWight += 0
      * 一个是Integer, 一个是Object, 则 diffWight += 2 (Integer的父类是Number, 而Nuber的父类是Object, 需要两层转换)
      * 一个是Integer, 一个是Number, 则 diffWight += 1 (因为只有一层父类层级)
      5. 创建 bean 实例
      (1) 如果配置了 lookup-method, 或 replace-method 属性, 使用 CGLIB 创建对象
      (2) 调用 org.springframework.beans.factory.support.InstantiationStrategy 接口的 instantiate() 实例化 bean
      方法内部调用 BeanUtils.instantiateClass() 实例化对象 (反射调用了构造函数)
  4. 在属性填充和初始化之前, 将 singleton 的 bean 放入二级缓存(earlySingletonObjects)和三级缓存(singletonFactories)

  5. 填充属性
    填充 <property> 标签中的属性, 这一步进行 byName 或者是 byType 的依赖注入. 直接完成注入(反射调用get方法), 未检测循环依赖, 后面再进行检测

<!--示例cml配置-->
<bean id="blogService" class="com.something.DefaultBlogService">
    <property name="blogDao" ref="blogDao" />
</bean>
  1. 初始化 Bean, 一些列接口
    (1) invokeAwareMethods: 调用一些列 Aware, 对 bean 属性进行设置
    * BeanNameAware: setBeanName()
    * BeanClassLoaderAware: setBeanClassLoader()
    * BeanFactoryAware: setBeanFactory(AbstractAutowireCapableBeanFactory.this)
    (2) applyBeanPostProcessorsBeforeInitialization
    执行 BeanPostProcessorpostProcessBeforeInitialization()
    (3) invokeInitMethods()
    * 如果实现了 InitializingBean 接口, 则调用 afterPropertiesSet()
    * 执行 <init-method> 标签的方法
    (4) applyBeanPostProcessorsAfterInitialization
    执行 BeanPostProcessorpostProcessAfterInitialization()

  2. 解决 bean 之间的循环依赖
    https://www.fatalerrors.org/a/how-spring-solves-circular-dependencies.html

    • 什么是三级缓存?
      (1) 一级缓存 singletonObjects: Map<String, Object> : beanName -> 已经构造完毕的 singleton 实例
      (2) 二级缓存 earlySingletonObjects: Map<String, Object> : beanName -> 正未初始化完的 bean 实例
      (3) 三级缓存 singletonFactories: Map<String, ObjectFactory<?>> : beanName -> 未初始化完)的 bean 的 factory
    • 为什么在被依赖的 bean 构造完成之前, 就先建立依赖关系, 后检测循环构造?

四.ApplicationContext 有什么扩展

截屏2021-05-27 下午3.13.58.png
  1. ApplicationContext 实现了多个接口:
    1. BeanFactory:Spring 管理 Bean 的顶层接口,我们可以认为他是一个简易版的 Spring 容器。
      ApplicationContext 继承 BeanFactory 的两个子接口:HierarchicalBeanFactoryListableBeanFactory
      • HierarchicalBeanFactory 是一个具有层级关系的 BeanFactory,可以获取父层级 的 BeanFactory 。
      • ListableBeanFactory 可以列举出当前 BeanFactory 中所有的 bean 对象
    2. ApplicationEventPublisher:用于封装事件发布功能的接口,向事件监听器(Listener)发送事件消息。
    3. ResourceLoader:Spring 加载资源的顶层接口,用于从一个源加载资源文件。
      ApplicationContext 继承 ResourceLoader 的子接口 ResourcePatternResolver. 该接口是将 location 解析为 Resource 对象的策略接口。
    4. MessageSource:解析 message 的策略接口,用于支撑国际化等功能。
    5. EnvironmentCapable:用于获取 Environment 配置的接口。
  1. 最熟悉的 ClassPathXmlApplicationContext 实现类
    ClassPathXmlApplicationContext 类的实现是标准的模板模式
    1. 首先, 一个大而全的接口 ConfigurableApplicationContext 组合了
      • ApplicationContext 接口
      • Lifecycle 接口
      • Closeable 接口
    2. 接着, 一个默认的抽象实现类, 实现了 ConfigurableApplicationContext 接口中的公共方法. 代码块中的提示:
    //---------------------------------------------------------------------
    // Implementation of ApplicationContext interface
    //---------------------------------------------------------------------
    
    //---------------------------------------------------------------------
    // Implementation of Lifecycle interface
    //---------------------------------------------------------------------
    
    //---------------------------------------------------------------------
    // Implementation of ResourcePatternResolver interface
    //---------------------------------------------------------------------
    
    //---------------------------------------------------------------------
    // Implementation of HierarchicalBeanFactory interface
    //---------------------------------------------------------------------
    
    1. 紧接着一些列抽象类, 实现接口中的不同方法, 最后的 ClassPathXmlApplicationContext 类只有构造方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容