Spring Ioc源码分析系列–Ioc源码入口分析

Spring Ioc源码分析系列–Ioc源码入口分析

本系列文章代码基于Spring Framework 5.2.x

前言

上一篇文章Spring Ioc源码分析系列–Ioc的基础知识准备介绍了Ioc的基础概念以及Spring Ioc体系的部分基础知识。那么这一篇就会真正通过一个例子,启动Ioc容器,获取容器里的 bean

首先说明,本文的例子是基于 xml配置文件去完成的。

为什么是 xml因为 xml是Spring的灵魂,可能我们初学Spring都会有畏难情绪,看到繁杂的 xml就会打退堂鼓。但是实际上不然, xml的格式是相当清晰的,一个配置文件可以说没有一行配置是多余的。现在大部分的配置是用注解去完成的,相比 xml而言是简洁许多, 但是对于我们初学而言, xml 其实是更好的方式xml相对于注解而言是相对繁杂,但是它的信息也更多更明确, 注解只是添加了一个注解就完成配置,细节上是更为隐蔽的。再加上 xml配置文件和注解配置的原理是相通的,核心思想是一样的,掌握核心就万变不离其宗。所以这系列文章的例子大部分都会采取 xml的方式去配置,当然后续可能也会补充一下注解方式的例子和分析文章。

万事开头难,如果实在觉得看不懂但又想学的,可以硬着头皮看下去,等以后回过头来再看的时候,会有豁然开朗的感觉。

源码分析

启动容器示例

废话少说,下面开始搞个例子分析一下。所有源码都在我的仓库ioc-sourcecode-analysis-code-demo里找到。

首先弄个实体类 User

/**
 * @author Codegitz
 * @date 2022/4/26 10:58
 **/
public class User {
    private String id;

    private String name;

    private String age;
}

创建业务类 UserService以及业务实现类 UserServiceImpl,这里的逻辑很简单,就是根据传入的 nameage返回一个新的 user对象。

/**
 * @author Codegitz
 * @date 2022/4/26 10:59
 **/
public interface UserService {
    User getUser(String name,String age);
}

public class UserServiceImpl implements UserService {
    @Override
    public User getUser(String name, String age) {
        User user = new User();
        user.setId("1");
        user.setName(name);
        user.setAge(age);
        return user;
    }
}

业务类的准备工作已经完成了,接下来就是要写个xml配置文件,告诉Spring Ioc我需要一个 UserServicebean


这个xml配置文件比较简单,我们来解释一下每一行是什么意思。

<?xml version="1.0" encoding="UTF-8"?>为xml文件的规定头,没啥好说的。

xmlns="http://www.springframework.org/schema/beans"表明了xml的命名空间, xmlns全称为 xml namespace,一个xml里面命名空间可以有多个。

xmlns:xsixsi:schemaLocation是指明了xml文件的验证模型和验证模型文件的位置。可以看到验证模型能通过 http方式获取,但是为了防止网络抖动的影响,一般会在本地存放验证文件。 spring-beans.xsd就可以在图示的目录下找到。

Spring Ioc源码分析系列--Ioc源码入口分析

接下来就到了 <bean id="userService" class="io.codegitz.service.impl.UserServiceImpl"></bean>这一行,这是我们获取一个Bean的关键配置,这里告诉Ioc我需要一个 iduserServiceclassio.codegitz.service.impl.UserServiceImpl的Bean。

到这里配置已经完成了,接下来创建一个引导类启动Ioc就能获取到这个bean了。

新建引导类 Application,通过 ClassPathXmlApplicationContext类引导启动Ioc容器,这是加载 xml配置的常用引导类。

/**
 * @author Codegitz
 * @date 2022/4/26 10:57
 **/
public class Application {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        User codegitz = userService.getUser("codegitz", "25");
        System.out.println(codegitz);
    }
}

到这里其实已经完成了所有样例的准备,可以启动容器了。

Spring Ioc源码分析系列--Ioc源码入口分析

可以看到,只是简单的 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");就可以启动一个容器,随后就能通过 getBean()方法获取容器里的bean了,接下来我们看看 new ClassPathXmlApplicationContext("beans.xml")发生了什么。

容器启动分析

ClassPathXmlApplicationContext构造方法

这里我们使用的是 ClassPathXmlApplicationContext,那么就从这个类的构造方法开始分析。

摘取的代码片段我会保留原文注释,看官可以细细品味。

    /**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML file and automatically refreshing the context.
     * @param configLocation resource location
     * @throws BeansException if context creation failed
     */
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

继续跟进构造函数,根据方法上的注释和代码可以看到。这里就做了两件事,根据给定的xml配置文件加载BeanDefinition,然后调用 refresh()方法刷新容器。

    /**
     * Create a new ClassPathXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files.
     * @param configLocations array of resource locations
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {

        // 初始化父类
        super(parent);
        // 设置配置文件信息,这里会进行占位符的替换
        // 例如/${env}-beans.xml类型的路径会被占位符替换,如果env=sit,那么路径就会被替换为/sit-beans.xml
        setConfigLocations(configLocations);
        // 如果没有刷新,就会调用refresh()方法进行刷新,这个方法是整个Ioc的关键入口,由父类AbstractApplicationContext实现
        if (refresh) {
            refresh();
        }
    }

前面两个方法比较简单,可以跟着注释看一下。重点在 refresh()方法。这个方法会完成Ioc所需要的所有配置加载,完成所有bean的注册以及Spring的一系列实现。

refresh()方法

到这里,才是真正摸到了Ioc的源码入口。看一下 refresh()方法的代码。

    public void refresh() throws BeansException, IllegalStateException {
        //给容器refresh加锁,避免容器在refresh阶段时,容器进行了初始化或者销毁操作
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            //调用容器准备刷新的方法,获取容器当前时间,同时给容器设置同步标识、具体方法
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            /**
             * 告诉子类启动refreshBeanFactory方法,refreshBeanFactory方法会载入bean的定义文件,该方法的实现会针对xml配置,最终创建内部容器
             * 该容器负责bean的创建与管理,会进行BeanDefinition的注册
             */
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            //注册一些容器中需要使用的系统bean,例如classloader,BeanFactoryPostProcessor等
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses
                // 允许容器的子类去注册PostProcessor,钩子方法.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                //激活容器中注册为bean的BeanFactoryPostProcessor
                //对于注解容器,org.springframework.context.annotation.ConfigurationClassPostProcessor
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                // 创建对象
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

咋一看代码量非常大,但是逻辑是比较清晰的,结合注释来看,再调试一遍,没啥大问题。

下面对方法进行逐个分析,鉴于一次性写完会比较长,本文先分析前三个方法,即 prepareRefresh()obtainFreshBeanFactory()prepareBeanFactory(beanFactory)三个方法。

prepareRefresh()方法

跟进 prepareRefresh()方法,这个方法主要是做了一些初始化属性设置和校验。

    /**
     * Prepare this context for refreshing, setting its startup date and
     * active flag as well as performing any initialization of property sources.
     */
    protected void prepareRefresh() {
        // Switch to active.
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        // 省略部分日志...

        // Initialize any placeholder property sources in the context environment.
        // 初始化一些属性,交由子类实现
        initPropertySources();

        // Validate that all properties marked as required are resolvable:
        // see ConfigurablePropertyResolver#setRequiredProperties
        // 校验是否有必须的属性,如果有必须的属性但是环境没配置,则抛出异常
        getEnvironment().validateRequiredProperties();

        // Store pre-refresh ApplicationListeners...
        // 存储在refresh之前注册的ApplicationListener,避免这部分的ApplicationListener在后续的调用中被丢失
        // 这是一个2019年修复的bug
        if (this.earlyApplicationListeners == null) {
            this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
        }
        else {
            // Reset local application listeners to pre-refresh state.
            this.applicationListeners.clear();
            this.applicationListeners.addAll(this.earlyApplicationListeners);
        }

        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

这个里面比较简单,没啥好看。稍微来看下 getEnvironment().validateRequiredProperties()的代码。

    /**
     * Return the {@code Environment} for this application context in configurable
     * form, allowing for further customization.
     * If none specified, a default environment will be initialized via
     * {@link #createEnvironment()}.
     */
    @Override
    public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = createEnvironment();
        }
        return this.environment;
    }

    /**
     * Create and return a new {@link StandardEnvironment}.
     * Subclasses may override this method in order to supply
     * a custom {@link ConfigurableEnvironment} implementation.
     */
    protected ConfigurableEnvironment createEnvironment() {
        return new StandardEnvironment();
    }

可以看到这里返回的是 StandardEnvironment类型 Environment,这里的逻辑就是有则返回,无则创建。接下来看 validateRequiredProperties()方法。最终实现是在 AbstractPropertyResolver#validateRequiredProperties()方法。

    @Override
    public void validateRequiredProperties() {
        MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
        // 遍历requiredProperties,逐个去当前环境获取是否存在
        for (String key : this.requiredProperties) {
            if (this.getProperty(key) == null) {
                ex.addMissingRequiredProperty(key);
            }
        }
        // 如果存在缺失的必须属性,则抛出异常
        if (!ex.getMissingRequiredProperties().isEmpty()) {
            throw ex;
        }
    }

prepareRefresh()方法比较简单,到此已经基本看完,接下来看下一个 obtainFreshBeanFactory()方法。

obtainFreshBeanFactory()方法

obtainFreshBeanFactory()方法从名字上就知道是获取并且同时刷新一个 beanfactory

在没有分析代码之前,先来猜测一下它的实现。我们知道它最终会返回一个可以使用的 beanfactory,说明此时在 beanfactory里已经初始化完成了我们定义的 BeanDefinition,那么这里应该会有一些基础信息的配置,然后解析 xml文件,获取 xml配置信息,然后初始化相应的 BeanDefinition,准备就绪后返回 beanfactory。当然这只是猜测,具体实现如何,马上分析。

那么接下来就跟进这个方法看一下代码实现。

    /**
     * Tell the subclass to refresh the internal bean factory.
     * @return the fresh BeanFactory instance
     * @see #refreshBeanFactory()
     * @see #getBeanFactory()
     */
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        // 调用子类的实现,刷新beanFactory
        refreshBeanFactory();
        // 返回刷新完成(即启动完成)的beanFactory
        return getBeanFactory();
    }

跟进 refreshBeanFactory()方法,关键地方都已经加上注释,也比较简单易懂,跟着看一下就行。

    /**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     * 此实现执行此上下文的底层 bean 工厂的实际刷新,
     * 关闭先前的 bean 工厂(如果有)并为上下文生命周期的下一阶段初始化一个新的 bean 工厂。
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        // 如果有前一个,先关闭
        if (hasBeanFactory()) {
            // 先销毁所有已经注册的bean
            destroyBeans();
            // 关闭工厂,将this.beanFactory设为null
            closeBeanFactory();
        }
        try {
            //创建DefaultListableBeanFactory
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //为了序列化指定id,如果需要的话,让这个beanFactory从id反序列化到BeanFactory对象
            beanFactory.setSerializationId(getId());
            //定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象已经循环依赖
            //以及设置@Autowire和@Qualifier注册解析器QualifierAnnotationAutowire-CandidateResolver
            customizeBeanFactory(beanFactory);
            //初始化DocumentReader,并进行xml文件读取和解析
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

前面的准备工作是比较简单的,不再细说。重点在 loadBeanDefinitions(beanFactory)方法,这里会加载 xml获取我们配置的 BeanDefinition

记住这个位置 AbstractRefreshableApplicationContext#refreshBeanFactory(),这是经过前面繁杂的摸索后真正进入BeanDefinition加载的分水岭。

Spring Ioc源码分析系列--Ioc源码入口分析

跟进代码查看。

    /**
     * Loads the bean definitions via an XmlBeanDefinitionReader.
     * 通过 XmlBeanDefinitionReader 加载 bean 定义。
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     * @see #initBeanDefinitionReader
     * @see #loadBeanDefinitions
     */
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //为指定 beanFactory创建XmlBeanDefinitionReader
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        //对 beanDefinitionReader 进行环境变量的设置
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        // 允许子类提供阅读器的自定义初始化,然后继续实际加载 bean 定义。
        // 对 beanDefinitionReader 进行属性设置
        initBeanDefinitionReader(beanDefinitionReader);
        // 加载 bean 定义
        loadBeanDefinitions(beanDefinitionReader);
    }

可以看到这里也是进行了一波准备工作,首先是给 beanFactory创建一个 XmlBeanDefinitionReader,后续xml解析都是由它来完成,接下来对这个 XmlBeanDefinitionReader进行一些属性设置,接下来调用 loadBeanDefinitions(beanDefinitionReader)加载 BeanDefinition,跟进代码查看。

    /**
     *
     * 使用给定的 XmlBeanDefinitionReader 加载 bean 定义。
     * bean 工厂的生命周期由 {@link refreshBeanFactory} 方法处理;因此这个方法只是应该加载和或注册 bean 定义。
     *
     * 在初始化了DefaultListableBeanFactory和XmlBeanDefinitionReader后就可以进行配置文件的读取了
     *
     */
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        // 返回一个引用构建此上下文的 XML bean 定义文件的 Resource 对象数组, 默认实现返回 {@code null}。
        // 子类可以覆盖它以提供预构建的资源对象而不是占位符字符串。
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        // 返回引用构建此上下文的 XML bean 定义文件资源位置数组,还可以包括占位符模式,这将通过 ResourcePatternResolver 进行解析获取。
        // 默认实现返回 {@code null}。子类可以覆盖它以提供一组资源位置以从中加载 bean 定义。
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

可以看到,由于没有预构建的资源,所以 configResources会为 nullconfigLocations则获取到了我们的 beans.xml,那么接下来就会解析 beans.xml获取 BeanDefinition

Spring Ioc源码分析系列--Ioc源码入口分析

跟进 reader.loadBeanDefinitions(configLocations)方法,具体的实现是在 AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations)方法。

    @Override
    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int count = 0;
        for (String location : locations) {
            count += loadBeanDefinitions(location);
        }
        return count;
    }

继续套娃,进入 loadBeanDefinitions(location)方法。

    public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {
        //获取资源加载器,主要功能是根据路径和类加载器获取Resource对象
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        //ResourcePatternResolver用于加载多个文件或者加载Ant风格路径的资源文件
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                // 以Resource形式返回所有配置文件
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                // 通过resources加载
                int count = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    Collections.addAll(actualResources, resources);
                }
                if (logger.isTraceEnabled()) {
                    logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                }
                return count;
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {
            /**
             * 加载单个资源,直接使用ResourceLoader加载
             */
            // Can only load single resources by absolute URL.
            Resource resource = resourceLoader.getResource(location);
            int count = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }
            return count;
        }
    }

这里会经过一系列的重载方法,最终会来到 doLoadBeanDefinitions()方法里,这才是真正解析的地方,好家伙,山路十八弯。

Spring Ioc源码分析系列--Ioc源码入口分析

跟进 doLoadBeanDefinitions()方法。

    /**
     * Actually load bean definitions from the specified XML file.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #doLoadDocument
     * @see #registerBeanDefinitions
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            /**
             * 创建Document对象,XML文档对象,即dom树
             * 使用这个Document对象可以获取XML文件中的节点并且创建节点
             * SAX XML
             * 解析dom树,即解析出一个个属性,将其属性保存到BeanDefinition当中
             * 并向容器注册BeanDefinition
             */
            Document doc = doLoadDocument(inputSource, resource);
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        // 省略部分异常信息
    }

获取 Document对象就不说了,可以单独出一篇文章,这里再跟进去就离主题太远了。

跟进 registerBeanDefinitions(doc, resource)方法。

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //创建BeanDefinitionDocumentReader,这个是实际从XML的dom树中服务BeanDefinition
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //获取注册表BeanDefinitionMap在本次加载前的BeanDefinition数量
        int countBefore = getRegistry().getBeanDefinitionCount();
        //加载并注册
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //用本次加载后的数据减去以前有的数量,即为本次加载的BeanDefinition数量
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

重点在方法 documentReader.registerBeanDefinitions(doc, createReaderContext(resource))里,跟进代码。

    protected void doRegisterBeanDefinitions(Element root) {
        // Any nested  elements will cause recursion in this method. In
        // order to propagate and preserve  default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        //BeanDefinition的解析委托类
        BeanDefinitionParserDelegate parent = this.delegate;
        //判断这个根节点是否是默认的命名空间
        //底层就是判断这个根节点的namespace="http://www.springframework.org/schema/beans"
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            //获取这个profile的值,表示剖面,用于设置环境
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                //根据分隔符换成数组
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                // We cannot use Profiles.of(...) since profile expressions are not supported
                // in XML config. See SPR-12458 for details.
                //判断这个切面是否是激活的环境,如果不是直接返回,表示这个配置文件不是当前运行环境的配置文件
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }

        //解析XML之前做的准备工作,其实什么也没做
        preProcessXml(root);
        //调用这个方法解析
        parseBeanDefinitions(root, this.delegate);
        //后续处理
        postProcessXml(root);

        this.delegate = parent;
    }

跟进解析BeanDefinition的方法 parseBeanDefinitions(root, this.delegate)里面。

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //如果是默认命名空间
        if (delegate.isDefaultNamespace(root)) {
            //获取根节点下的所有子节点
            NodeList nl = root.getChildNodes();
            //遍历所有子节点
            for (int i = 0; i < nl.getLength(); i++) {
                //取出节点
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    //Bean定义的document对象使用了spring的默认命名空间,如http://www.springframework.org/schema/beans
                    if (delegate.isDefaultNamespace(ele)) {
                        //若是则按照spring原有的逻辑进行解析
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            //使用扩展的自定义代理类去解析
            delegate.parseCustomElement(root);
        }
    }

这个例子没有自定义标签,进入到默认标签的解析。

    //根据不同的标签进行解析
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        //如果是import标签,进入导入解析
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        //若果是别名元素,则进行别名解析
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        //bean元素解析
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

显然我们的例子只有一个Bean标签,所以会进入到 processBeanDefinition()方法里。

    /**
     * Process the given bean element, parsing the bean definition
     * and registering it with the registry.
     */
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //BeanDefinitionHolder是对BeanDefinition的封装,即bean定义的封装类
        //对document对象中的bean标签解析由BeanDefinitionParserDelegate实现
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                //从springIOC容器注册解析得到的BeanDefinition,这是BeanDefinition向IOC容器注册的入口
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition
                                                       ·(bdHolder));
        }
    }

调用 BeanDefinitionReaderUtils.registerBeanDefinition()方法真正将BeanDefinition注册进容器里。咋注册呢?其实就是加到 BeanFactorybeanDefinitionMap属性里。 beanDefinitionMap可以说就是BeanDefinition的容器了。

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        // 这里真正把BeanDefinition注册到了BeanFactory里
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        // 注册别名
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

到这里已经完成了BeanDefinition的注册,是不是有点曲折? 其实很简单,就是细节比较多

到这里 obtainFreshBeanFactory()方法已经基本结束,已经完成了配置文件的解析并且注册BeanDefinition的过程了。剩下的操作就是把这个BeanFactory返回给上一步。

prepareBeanFactory()方法

接下来分析一下 prepareBeanFactory()方法,这方法也简单,主要就是对BeanFactory进行一些属性的设置,跟着注释看一下就行。

    /**
     * Configure the factory's standard context characteristics,
     * such as the context's ClassLoader and post-processors.
     * @param beanFactory the BeanFactory to configure
     */
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // Tell the internal bean factory to use the context's class loader etc.
        // 设置 beanFactory的classLoader 为当前 context的classLoader
        beanFactory.setBeanClassLoader(getClassLoader());
        /**
         * 设置 beanFactory的表达式语言处理器,Spring3 增加了表达式语言的支持,
         * 默认可以使用#{bean.xxx}的形式来调用相关属性值
         * @Qusetion 在注册了这个解析器之后,spring是在什么时候调用这个解析器的呢?
         * Spring在bean进行初始化的时候会有属性填充的步骤,而在这一步中
         * Spring会调用AbstractAutowireCapableBeanFactory类的applyPropertyValues函数来完成功能。
         * 就这个函数中,会通过构造 BeanDefinitionValueResolver类型实例valueResolver来进行属性值的解析。
         * 同时,也是在这个步骤中一般通过 AbstractBeanFactory 中的 evaluateBeanDefinitionString
         * 方法去完成SpEL解析
         */
        beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        //为beanFactory增加一个PropertyEditor,这个主要是对bean的属性等设置管理的一个工具
        beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

        // Configure the bean factory with context callbacks.
        //添加beanPostProcessor
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        //设置几个忽略自动装配的接口
        // aware都是由invokeAware方法注入,忽略自动Autowire
        beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
        beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
        beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
        beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

        // BeanFactory interface not registered as resolvable type in a plain factory.
        // MessageSource registered (and found for autowiring) as a bean.
        // 设置了几个自动装配的规则,后面三个都是把当前对象注册了进去,只有BeanFactory老老实实得注册了一个BeanFactory
        beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this);
        beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
        beanFactory.registerResolvableDependency(ApplicationContext.class, this);

        // Register early post-processor for detecting inner beans as ApplicationListeners.
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

        // Detect a LoadTimeWeaver and prepare for weaving, if found.
        // 添加对AspectJ的支持
        if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            // Set a temporary ClassLoader for type matching.
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }

        // Register default environment beans.添加系统环境默认的bean
        if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
    }

到这里refresh()的前面三个方法已经简单过完了,除了解析配置文件复杂点,其他的都是些属性配置居多。

小结

本文先通过一个列子驱动,找到了Ioc容器启动的入口,简单分析了一下前三个方法,最重要的就是加载BeanDefinition的过程了,可以仔细看多几遍。

今天发生了点小插曲,本该写得更详细点的,但是写不了了,只能作罢,将就着看吧。

如果有人看到这里,那在这里老话重提。 与君共勉,路漫漫其修远兮,吾将上下而求索。

Original: https://www.cnblogs.com/codegitz/p/16243680.html
Author: Codegitz
Title: Spring Ioc源码分析系列–Ioc源码入口分析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/714251/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 摆了

    ; ; 这段时间只放板子了。为什么不写题解?答:rt。 posted @2022-01-10 16:35 T_X蒻 阅读(30 ) 评论() 编辑 Original: https:…

    技术杂谈 2023年6月21日
    095
  • idea集成maven插件和使用骨架创建maven的java工程

    idea集成maven插件 打开idea点击配置搜索maven 配置自己的maven路径和仓库位置 使用骨架创建maven的java工程 项目结构: Original: https…

    技术杂谈 2023年6月21日
    0105
  • 如何将Excel中数据前的空格去除

    我们在工作中有时会碰到格式不统一的数据,这时会需要将数据前的空格去除,那么该如何操作呢? 下面小编就来具体介绍一下步骤吧~ Original: https://www.cnblog…

    技术杂谈 2023年5月31日
    0137
  • 最适合初学者的SpringBoot入门教程——动力节点王鹤

    ​Spring Boot 去除了大量的 xml 配置文件,简化了复杂的依赖管理,配合各种 starter 使用,基本上可以做到自动化配置。Spring 可以做的事情,现在用 Spr…

    技术杂谈 2023年7月25日
    0217
  • SpringBoot异步调用

    在程序执行时候还有一个瓶颈,串行执行,可以通过使用不同线程类快速提升应用的速度。 要启用Spring的异步功能,必须要使用 @EnableAsync注解。这样将会透明地使用 jav…

    技术杂谈 2023年7月24日
    096
  • 杂想闲思录——你的努力付出可能毫无价值可言!

    公司的清洁阿姨每隔一段时间就会换一个。个人上班一般来的比较早,所以经常会碰到清洁阿姨在办公室打扫卫生:有些阿姨工作比较敷衍,草草了事,完全是那种拿多少薪水干多少活的类型,完全看不到…

    技术杂谈 2023年5月31日
    0126
  • SSM配置文件的连接

    使用ssm框架配置数据库连接时的问题 如果MySQL数据库版本是8.0.11, url配置成了MySql5.0以上版本需要的驱动类名(com.mysql. cj.jdbc.Driv…

    技术杂谈 2023年6月21日
    0112
  • Redis-firewall使用命令

    Redis-firewall使用命令 一、iptables防火墙 1、基本操作 查看防火墙状态 service iptables status 停止防火墙 service ipta…

    技术杂谈 2023年6月21日
    0100
  • Eclipse插件安装方式及使用说明

    拷贝安装方式 1、通过ECLIPSE_HOME\plugins安装 在eclipse的主目录ECLIPSE_HOME, 比如在我的机器上安装的目录是:ECLIPSE_HOME有一个…

    技术杂谈 2023年5月31日
    084
  • 学习

    1.1、参考博客 参考的教程如下: Original: https://www.cnblogs.com/agui125/p/16032402.htmlAuthor: 风御之举Tit…

    技术杂谈 2023年6月21日
    095
  • Can’t find bundle for base name

    转自:https://blog.51cto.com/stones/389939 Struts2国际化异常处理 这是找不到指定文件; 你必须把 .properties 文件,放在与这…

    技术杂谈 2023年5月30日
    0125
  • 瞎子摸象与刻舟求剑

    我这几年越来越觉得,这两个成语故事是对我们世界深刻的隐喻。 瞎子摸象 从前,有四个盲人很想知道大象是什么样子,可他们看不见,只好用手摸。胖盲人先摸到了大象的牙齿。他就说:&#822…

    技术杂谈 2023年6月1日
    0103
  • OpenSSL命令—pkcs8

    用途: pkcs8格式的私钥转换工具。它处理在PKCS#8格式中的私钥文件。它可以用多样的PKCS#5 (v1.5 and v2.0)和PKCS#12算法来处理没有解密的PKCS#…

    技术杂谈 2023年5月31日
    097
  • 技术管理进阶——管理者如何进行梯队设计及建设

    原创不易,求分享、求一键三连 最近有个粉丝问了一个问题: 小钗,我是一个部门负责人,想知道你们具体是如何进行梯队建设的 之前我们聊过,大Leader的工作应该核心围绕五件事展开,最…

    技术杂谈 2023年6月1日
    087
  • Python3网络爬虫–爬取有声小说(附源码)

    一.目标 1.首页 2.网页源代码 二.爬取详情页 1.查看详情页 2.小说详情 3.小说简介 4.播放列表 三.爬取小说音频 1.确定数据加载方式 2.寻找真实音频播放地址 3….

    技术杂谈 2023年6月21日
    0143
  • 为在线数据库构建基于Kudu的实时数据同步

    Kudu 是 Cloudera 开源的新型列式存储系统,是 Apache Hadoop 生态圈的成员之一。它专门为了对快速变化的数据进行快速的分析,填补了以往Hadoop 存储层的…

    技术杂谈 2023年7月23日
    078
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球