白话文剖析[spring4.2.1.RELEASE] IOC核心架构

本文以白话文的形式详细介绍了Spring 4.2.1.RELEASE的IOC核心架构,从`ClassPathXmlApplicationContext`出发,深入到`prepareRefresh()`等关键方法,揭示了ApplicationContext的初始化、资源配置、BeanFactory的创建和刷新等过程,涉及占位符解析、BeanDefinition的加载以及BeanPostProcessor等核心概念。
摘要由CSDN通过智能技术生成

在这篇文章中,我将用第一人称的方式向你阐述spring4.2.1.RELEASE IOC部分的基本架构,你可以用如下的简单demo开启源码debug之旅

demo包含三个文件


User.java

public class User {
   
    @Override
    public String toString() {
        return "hi, I am a user!";
    }
}

user.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="ch0.User"/>

</beans>

UserTest.java

public class UserTest {
   
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ch0/user.xml");
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

UserTest.java 中通过ClassPathXmlApplicationContext 创建了一个上下文,xml文件在类路径下,然后通过getBean 的方式可以拿到该bean

我们直接进到new ClassPathXmlApplicationContext(“ch0/user.xml”); 看看我到底做了什么事情~_~

ClassPathXmlApplicationContext.java

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
}

调用

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
        // 层层调用父类构造函数, 最终只是为了获取ResourcePatternResolver, 我需要这玩意是因为我
        // 要将你传给我的配置文件("ch0/user.xml")这个参数转换为我内部可以处理的资源抽象 Resource 
        super(parent);
        // 你可以不用告诉我非常精确的配置文件, 可以写占位符。在这个函数里面,我可以解析出完整的配置文件路径
        setConfigLocations(configLocations);
        // 基本都需要刷新的啦(refresh == true),在刷新上下文的时候,我会用你给我的配置文件完成几乎所有的事情哦
        if (refresh) {
            refresh();
        }
}

上面的函数先调用super(parent) 层层往上调用,最终发现做了这么一件事情

AbstractApplicationContext.java

public AbstractApplicationContext() {
        this.resourcePatternResolver = getResourcePatternResolver();
}

resourcePatternResolver 这从字面上就可以知道这家伙是做资源解析的用的呢,查看一下ResourcePatternResolver 的定义,发现重点只有这么一行

ResourcePatternResolver.java

    Resource[] getResources(String locationPattern) throws IOException;

这里的 locationPattern 代表实际的资源路径(基本上就等同于xml文件路径啦),这家伙可以把一个资源路径转换为描述资源的抽象Resource, 好了,初次看spring源码了解到这里就够了,关于 Resource 的深入里面后面会后专题分析!

继续下面一行

setConfigLocations(configLocations);

public void setConfigLocations(String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
}

这一句的作用就是将创建ApplicationContext传入进来的ch0/user.xml 转换为合法的location,将location里面类似${key} 的placeHolder 转换为实际的值

protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
}

而转换过程中会通过getEnviroment()拿到ConfigurableEnvironment对象来进行占位符${}的替换,标准的Enviroment对象为

StandardEnvironment.java

public class StandardEnvironment extends AbstractEnvironment {
   
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }
}

可以看到在设置替换placeHolder的源的时候,最终会通过getProperties(),和System.getenv()来获取启动jvm的时候的环境变量和系统环境变量,也就是说,如果你传入的location里面有个${key} 占位,而jvm参数或者系统环境变量里面刚好有个变量叫做key 那么spring在解析该location的时候,会将该占位解析为对应的value


接下来,到了

if (refresh) {
    refresh();
}

这一行可是我的重头戏啊,我的核心功能都在这个refresh() 方法里面搞定哦
在我们这个demo中,refresh 参数为true,所以,直接进入到refresh()方法,激动人心的时刻终于到来,go!

AbstractApplicationContext.java

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 刷新工厂之前需要做一些准备工作的啦,就想你在运动之前要做一些准备运动一样哦 
            prepareRefresh();

            // 我会告诉我的子类创造一个工厂,来把我需要创建bean的原料BeanDefinition准备好
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // 原料准备好之后呢,我要声明一些特殊的依赖关系, 所谓依赖,就是我在创造一个bean A的时候,发现它里面有另外一个属性B
            // 那么B就是A的依赖,我在创造A的时候,必须先把B创造好,特殊关系的依赖就是指我遇到B的类型,我该放弃呢,还是告诉他直接用
            // 现成的(也就是不用再去创造B了)
            prepareBeanFactory(beanFactory);

            try {
                // 这里没啥,就是留给子类做扩展的啦
                postProcessBeanFactory(beanFactory);

                // 到了这里,工厂已经准备好了,如果你之前告诉过我工厂准备好之后应该干什么事情,这边我就可以满足你的需求哦
                // 不信,你去看看BeanFactoryPostProcessors接口是干嘛用的吧==
                invokeBeanFactoryPostProcessors(beanFactory);

                // 在创建一个bean的前后,我也留给你很多扩展,原理上和上面的工厂扩展差不多的哦
                registerBeanPostProcessors(beanFactory);

                // 就是处理一些国际化的操作啦,啊?什么是国际化,就是i18n啦,还不懂?你没救了
                initMessageSource();

                // 我的功能很丰富,除了可以给你创建bean,还可以有事件管理的功能哦,这里我就创建一个管理器(ApplicationEventMulticaster(),
                // 用来注册事件(ApplicationEvent)
                // 我会将这些事件广播给合适的监听者(ApplicationListener)那边哦
                initApplicationEventMulticaster();

                // 啥也不干,留给子类扩展啦
                onRefresh();

                // 前面不是事件管理器搞好了嘛,这边呢,就是把那些事件监听器给注册进来啦,这样来一个新的事件我就知道该发给谁啦
                registerListeners();

                // 如果某些bean告我我,他想在我工厂创建之初就想初始化(一般要是单件singleton并且lazy-init为false),那么我在这个函数会满足他
                finishBeanFactoryInitialization(beanFactory);

                // 终于刷新完了,我要开始发布事件了!
                finishRefresh();
            }

            // 什么?刷新的时候报错了?oh my god,我需要做一些清理
            catch (BeansException ex) {
                logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

                // 我需要将我创建的bean销毁掉
                destroyBeans();

                // 我不再活跃
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值