Spring系列之IOC容器的实例化过程一

92 阅读4分钟

IOC容器初始化过程

上一篇文章我们讲解了spring容器中IOC的容器结构,IOC容器总共有:1.ResourceLoader(资源加载组件)2.Resource(资源描述组件)3.BeanDefinitionReader(bean构建组件)4.BeanDefinition(元数据组件)5.BeanRegister(Bean注册组件)6.BeanFactory(bean容器组件)

他们在spring的源码的初始化过程是怎么的呢?在这一节,我们通过查看、分析Spring源码进一步的去理解SpringIOC容器的启动流程是怎么样的。

在上一节,我重点讲解了SpringIOC容器中的Bean容器组件,其中Bean容器组件中有两个常用的容器,一个是BeanFactory,还有一个是ApplicationContext。而且在上节我也说过,其他组件都是服务于Bean容器的,因此,我们要探究IOC容器的初始化过程需要我们将重点放在Bean容器上,那么,接下来我们就拿在开发中最常用的Bean容器体系中的ClassPathXmlApplicationContext去讲解SpringIOC的初始化过程。

在还没有springboot之前,我们使用spring大部分会通过xml去定义bean然后让ClassPathXmlApplicationContext去读取、解析资源文件,进而让spring去管理xml中定义的bean。在这个过程中,我们都会在程序入口中new一个ClassPathXmlApplicationContext对象,并把表示资源文件的路径通过构造器进行获取(这里spring几种资源加载方式在上篇文章有讲解,如果不记得的小伙伴可以再回过头去看一下),其实SpringIOC的初始化也是在这个Bean容器类初始化的过程中实现的。

那我们就去这个对象的构造方法中看一下。这个类中有很多的重载的构造方法,但是这些重方法最终都会调用下面的这个构造方法。

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        this.setConfigLocations(configLocations);
        if (refresh) {
            this.refresh();
        }

    }

从上面的代码中我们可以看出会先调用父类的构造器方法,这里有一个构造器的构造器的调用过程:ClassPathXmlApplicationContext---->AbstractXmlApplicationContext----->AbstractRefreshableConfigApplicationContext---->AbstractRefreshableApplicationContext----->AbstractApplicationContext(注意这里的调用过程并不是说的jvm中的栈方法执行的过程,jvm中栈的执行过程恰恰与这个调用过程相反,会最先执行AbstractApplicationContext的构造器方法)。在类的实例化过程中其实只有AbstractApplicationContext的构造器方法中有真正实例化容器的实现逻辑。

public AbstractApplicationContext() {
        this.logger = LogFactory.getLog(this.getClass());
        this.id = ObjectUtils.identityToString(this);
        this.displayName = ObjectUtils.identityToString(this);
        this.beanFactoryPostProcessors = new ArrayList();
        this.active = new AtomicBoolean();
        this.closed = new AtomicBoolean();
        this.startupShutdownMonitor = new Object();
        this.applicationListeners = new LinkedHashSet();
        this.resourcePatternResolver = this.getResourcePatternResolver();
    }

    public AbstractApplicationContext(@Nullable ApplicationContext parent) {
        this();
        this.setParent(parent);
    }

在这个方法中,它对容器的一些基本参数进行了一个初始化,需要注意的是,这里的parent如果没有在ClassPathXmlApplicationContext构造器中指定的话,默认是为null的。在这个基本参数的一个设置中,需要我们关注一下resourcePatternResolver这个属性值的获取过程,如果大家认真看了我上一篇文章,应该对这个名字会有点熟悉。他就是ResourceLoader体系中有着解析资源功能的组件。这里我们看一下它的获取过程。

protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
    }

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
        Assert.notNull(resourceLoader, "ResourceLoader must not be null");
        this.resourceLoader = resourceLoader;
    }

它通过调用上面方法去获取一个PathMatchingResourcePatternResolver对象的实例,而在创建PathMatchingResourcePatternResolver实例的过程中指定AbstractApplicationContext为PathMatchingResourcePatternResolver的resourceLoader为了便于之后去读取资源文件。

好了,super(parent); 调用逻辑已经分析完毕,我们再来看看this.setConfigLocations(configLocations); 做了些什么。

public void setConfigLocations(@Nullable 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] = this.resolvePath(locations[i]).trim();
            }
        } else {
            this.configLocations = null;
        }

    }

在这个方法中,先是将资源的描述字符串放入到String[] 中,然后就是通过this.resolvePath(locations[i]).trim(); ,这个方法的主要功能就是对传入的如applicationContext-${module}.xml中的占位符进行解析。

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

public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = this.createEnvironment();
        }

        return this.environment;
    }

protected ConfigurableEnvironment createEnvironment() {
        return new StandardEnvironment();
    }

由看上的我放上去的几个方法,this.getEnvironment()这个获取的是StandardEnvironment的对象,然后通过这个对象调用resolveRequiredPlaceholders方法,并把path传进去。

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }

上面是StandardEnvironment中resolveRequiredPlaceholders方法的具体逻辑。在这个方法里面,他通过propertyResolver属性中的resolveRequiredPlaceholders方法去解析path,而这个propertyResolver属性,是StandardEnvironment的父类AbstractEnvironment随StandardEnvironment的new过程指定的。

public AbstractEnvironment() {
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
    }

PropertySourcesPropertyResolver这个类中并没有实现resolveRequiredPlaceholders方法而是在PropertySourcesPropertyResolver的父类AbstractPropertyResolver中有实现。

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (this.strictHelper == null) {
            this.strictHelper = this.createPlaceholderHelper(false);
        }

        return this.doResolvePlaceholders(text, this.strictHelper);
    }

这里他会创建一个PropertyPlaceholderHelper的类,然后调用this.doResolvePlaceholders(text, this.strictHelper);方法。典型的面向对象编程,总喜欢用一个helper类去完成一些功能。真正执行逻辑的也肯定是这个helper类,大家一定要有这种概念存在。

这个方法调用到最后是通过helper类中的parseStringValue()方法去实现的。

protected String parseStringValue(String value, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
        int startIndex = value.indexOf(this.placeholderPrefix);
        if (startIndex == -1) {
            return value;
        } else {
            StringBuilder result = new StringBuilder(value);

            while(startIndex != -1) {
                int endIndex = this.findPlaceholderEndIndex(result, startIndex);
                if (endIndex != -1) {
                    String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                    String originalPlaceholder = placeholder;
                    if (visitedPlaceholders == null) {
                        visitedPlaceholders = new HashSet(4);
                    }

                    if (!((Set)visitedPlaceholders).add(placeholder)) {
                        throw new IllegalArgumentException("Circular placeholder reference '" + placeholder + "' in property definitions");
                    }

                    placeholder = this.parseStringValue(placeholder, placeholderResolver, (Set)visitedPlaceholders);
                    String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                    if (propVal == null && this.valueSeparator != null) {
                        int separatorIndex = placeholder.indexOf(this.valueSeparator);
                        if (separatorIndex != -1) {
                            String actualPlaceholder = placeholder.substring(0, separatorIndex);
                            String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                            propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                            if (propVal == null) {
                                propVal = defaultValue;
                            }
                        }
                    }

                    if (propVal != null) {
                        propVal = this.parseStringValue(propVal, placeholderResolver, (Set)visitedPlaceholders);
                        result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Resolved placeholder '" + placeholder + "'");
                        }

                        startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                    } else {
                        if (!this.ignoreUnresolvablePlaceholders) {
                            throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "' in value \"" + value + "\"");
                        }

                        startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                    }

                    ((Set)visitedPlaceholders).remove(originalPlaceholder);
                } else {
                    startIndex = -1;
                }
            }

            return result.toString();
        }
    }

这个方法的功能就是对path中的占位符的一个解析。

讲解这个方法需要一篇博客才能真正的讲解透彻,但是这篇文章的重点在于IOC容器的初始化过程,就不用过多的篇幅去讲解了,如果感兴趣的小伙伴可以自己去看一下。

好了,到此,我们已经讲解完ClassPathXmlApplicationContext构造器中的 super(parent)方法和this.setConfigLocations(configLocations)方法,接下来就是refresh方法。

if (refresh) {
            this.refresh();
        }

这个方法在ClassPathXmlApplicationContext中没有实现,而是在AbstractApplicationContext类中,下面贴上这个方法的代码。

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

在这篇文章中,我们只需要知道这个方法中完成了springIOC容器大部分的功能,具体reflesh中每一个方法调用实现那些功能呢,在接下来Spring系列会做一个详细的讲解。