二.Spring源码剖析-IOC启动流程

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 上一章节我们讲的是Spirng中的一些核心类,包括IOC容器工厂,和配置解析的一些类,这一章主要是跟一下IOC启动流程。这里我先贴一下IOC的启动部分流程图,在后面的源码分析就是在走这张图,为什么是部分流程图,因为我先分多篇文章来写IOC启动流程,太长了看起来费劲。我把IOC启动流程分为4个阶段:容器创建 -> 配置加载 -> Bean的解析 -> Bean的注册,如下:

前言

上一章节我们讲的是Spirng中的一些核心类,包括IOC容器工厂,和配置解析的一些类,这一章主要是跟一下IOC启动流程。这里我先贴一下IOC的启动部分流程图,在后面的源码分析就是在走这张图,为什么是部分流程图,因为我先分多篇文章来写IOC启动流程,太长了看起来费劲。我把IOC启动流程分为4个阶段:容器创建 -> 配置加载 -> Bean的解析 -> Bean的注册,如下:
在这里插入图片描述

ClasspathXmlApplicationContext 容器

源码分析入口从 ClasspathXmlApplicationContext 开始,通过它来加载一个配置

//加载Spring配置文件,拿到Spring容器
ApplicationContext context = new  ClassPathXmlApplicationContext("配置文件.xml")
//从容器中拿到对象实例
MyBean myBean = context.getBean(MyBean.class);

进入ClassPathXmlApplicationContext构造器可以看到,该构造器接收一个配置文件,构造器的注释是这一样描述的:创建一个新的 ClassPathXmlApplicationContext,从给定的 XML 文件加载定义并自动刷新上下文。

/**
    创建一个新的 ClassPathXmlApplicationContext,从给定的 XML 文件加载定义并自动刷新上下文。
     * 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);
    }

    /**
     * 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);
        //设置位置文件地址
        setConfigLocations(configLocations);
        if (refresh) {
   
   
            //刷新容器【重点】
            refresh();
        }
    }

在ClasspathXmlApplication的构造器中做了如下事情:

  • 调用了父容器的构造器方法,目的是加载设置Bean的资源加载器 ResourcePatternResolver
  • 然后通过setConfigLocations方法保存好配置文件地址,
  • 最后调用refresh()刷新容器

    ResourcePatternResolver 资源加载器

    ResourcePatternResolver是Bean的资源加载器 ,通过父容器 AbstractApplicationContext 中的构造方法创建:
    ```java
    public abstract class AbstractApplicationContext extends DefaultResourceLoader

      implements ConfigurableApplicationContext
    

    public AbstractApplicationContext(@Nullable ApplicationContext parent) {

      //加载 resourcePatternResolver 
      this();
      //
      setParent(parent);
    

    }

    /**

    • Create a new AbstractApplicationContext with no parent.
      */
      //创建一个AbstractApplicationContext容器工厂,并构建一个ResourcePatternResolver
      public AbstractApplicationContext() {
      this.resourcePatternResolver = getResourcePatternResolver();
      }
      //获取 PathMatchingResourcePatternResolver
      protected ResourcePatternResolver getResourcePatternResolver() {
      return new PathMatchingResourcePatternResolver(this);
      }

      public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
      Assert.notNull(resourceLoader, "ResourceLoader must not be null");
      //资源加载器
      this.resourceLoader = resourceLoader;
      }

父容器AbstractApplicationContext 继承了 DefaultResourceLoader ,拥有资源加载的能力,在构造器中中创建了ResourcePatternResolver,使用的是`PathMatchingResourcePatternResolver`作为实现,它能够将指定的资源位置路径解析为一个或多个匹配的资源。
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/202106011009566.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0OTQxNDg=,size_16,color_FFFFFF,t_70)
下面是ResourceLoader 源码:

```java
public interface ResourceLoader {
    //默认从classpath中加载资源文件
    /** Pseudo URL prefix for loading from the class path: "classpath:". */
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
    //把资源文件转换成Resource
    Resource getResource(String location);
    ClassLoader getClassLoader();
}

public interface ResourcePatternResolver extends ResourceLoader {

    //从classpath加载资源
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    //把文件转换成Resource[] ,对ResourceLoader做了扩展
    Resource[] getResources(String locationPattern) throws IOException;
}

setConfigLocations 保存配置地址

然后就是保存配置地址 ,从源码可以看出,我们是可以传入多个配置文件给容器的。

public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean {
   
   
    //地址保存到这里
    @Nullable
    private String[] configLocations;

    /**
     * Set the config locations for this application context.
     * <p>If not set, the implementation may use a default as appropriate.
     */
     //可以传入多个配置
    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] = resolvePath(locations[i]).trim();
            }
        }
        else {
   
   
            this.configLocations = null;
        }
    }

Refresh() 刷新容器

ClasspathXmlApplication调用 AbstractApplicationContext#refresh 方法刷新容器,该方法中实现了IOC容器的整个初始化过程。

 @Override
    public void refresh() throws BeansException, IllegalStateException {
   
   
        synchronized (this.startupShutdownMonitor) {
   
   
            // Prepare this context for refreshing.
            //准备刷新工作 ,记录开始时间,初始化属性,校验配置文件,准备事件的存储Set
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            //告诉子类,刷新Bean工厂,销毁旧beanFactory,创建新beanFactory,默认DefaultListableBeanFactory
            //从子容器的refreshBeanFactory方法中载入Bean的资源文件
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            //准备工厂,配置工厂的下文特性, 例如上下文的 ClassLoader 和后处理器。Bean表达式解析器,
            //BeanPostProcessor和 Aware类的自动装配等
            prepareBeanFactory(beanFactory);

            try {
   
   
                // Allows post-processing of the bean factory in context subclasses.
                //BeanFactory初始化完成的后置工作,这是一个空方法,留给三方框架或者自己配置,作用是允许对beanFoctory进行扩展处理
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                //调用BeanFactory的后置处理器BeanFactoryPostProcessor,在 bean定义注册之后bean实例化之前调用
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                //注册Bean的后置处理器BeanPostProcessor,在Bean初始化前,后执行
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                //初始化信息源,国际化相关
                initMessageSource();

                // Initialize event multicaster for this context.
                //初始化容器事件传播器
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                //空方法,该方法子类实现,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
                onRefresh();

                // Check for listener beans and register them.
                //注册事件监听器,注册实现了ApplicationListener接口的监听器bean,
                //这些监听器是注册到ApplicationEventMulticaster中的
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                //实例化所有剩余的(非延迟初始化)单例的Bean
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                //完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)
                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.
                //销毁已经创建的单例Bean。
                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();
            }
        }
    }

refresh()方法主要是通过子类 refreshBeanFactory()方法加载Bean信息,然后就是一些列的容器生命周期事件。这里其实是用到了模板设计模式,在refresh()方法中指定容器刷新流程,很多的细节步骤由子类去实现。

工厂的创建:obtainFreshBeanFactory

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   
   
        //刷新工厂,有子类实现
        refreshBeanFactory();
        //通过子类返回工厂,默认 DefaultListableBeanFactory
        return getBeanFactory();
    }

这里只是定义了抽象方法,refreshBeanFactory由子类实现,见:AbstractRefreshableApplicationContext#refreshBeanFactory

AbstractRefreshableApplicationContext#refreshBeanFactory

@Override
    protected final void refreshBeanFactory() throws BeansException {
   
   
        if (hasBeanFactory()) {
   
   
            //如果已经有BeanFactory,销毁Bean,关闭容器
            destroyBeans();
            closeBeanFactory();
        }
        try {
   
   
            //创建IOC容器
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            //定制BeanFactory,如设置启动参数,开启注解的自动装配等
            customizeBeanFactory(beanFactory);
            //载入Bean,由子类实现
            loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {
   
   
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

该方法中先判断如果已经存在BeanFactory就销毁掉重新创建,默认使用的是DefaultListableBeanFactory作为BeanFactory,并loadBeanDefinitions方法加载Bean,方法由子类 AbstractXmlApplicationContext#loadBeanDefinitions实现。

加载Bean:AbstractXmlApplicationContext#loadBeanDefinitions

@Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   
   
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //创建 XmlBeanDefinitionReader ,用来从XML中读取Bean
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        //把Environment 和 ResourceLoader 设置给beanDefinitionReader
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        //设置Sax解析器
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        //初始化Bean的读取器,启用 Xml 的校验机制 , 允许子类自定义初始化读取器
        initBeanDefinitionReader(beanDefinitionReader);
        //加载Bean, XmlBeanDefinitionReader真正实现加载逻辑
        loadBeanDefinitions(beanDefinitionReader);
    }

loadBeanDefinitions方法是用来加载Bean的,创建了XmlBeanDefinitionReader 基于XML的Bean的读取器,最终会调用 XmlBeanDefinitionReader.(configLocations)从配置中加载Bean,见:AbstractXmlApplicationContext#loadBeanDefinitions(.XmlBeanDefinitionReader)

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
   
   
        //获取配置的Resource
        Resource[] configResources = getConfigResources();
        //如果有Resrouce就调用 XmlBeanDefinitionReader.loadBeanDefinitions 加载Bean
        if (configResources != null) {
   
   
            //Xml Bean 读取器调用其父类 AbstractBeanDefinitionReader 读取定位的 Bean 配置资源
            reader.loadBeanDefinitions(configResources);
        }
        // 如果子类中获取的 Bean 配置资源Resource为空,
        // 则获取 ClassPathXmlApplicationContext  构造方法中 setConfigLocations 方法设置的资源
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
   
   
            //Xml Bean 读取器调用其父类 AbstractBeanDefinitionReader 读取定位的 Bean 配置资源
            reader.loadBeanDefinitions(configLocations);
        }
    }

这里先尝试获取配置资源Resource,如果为空就通过指定配置reader.loadBeanDefinitions(configLocations);加载Bean,最终调用父类:AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String)方法

AbstractBeanDefinitionReader#loadBeanDefinitions(

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
   
   
        //资源加载器,在初始化IOC容器的时候创建的
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
   
   
            throw new BeanDefinitionStoreException(
                    "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
   
   
            // Resource pattern matching available.
            try {
   
   
                //把指定位置的配置文件解析成Resource,加载多个
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                //委派 XmlBeanDefinitionReader加载Bean
                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 {
   
   
            // Can only load single resources by absolute URL.
            //把指定位置的配置文件解析成Resource,加载单个
            Resource resource = resourceLoader.getResource(location);
            //委派 XmlBeanDefinitionReader加载Bean
            int count = loadBeanDefinitions(resource);
            if (actualResources != null) {
   
   
                actualResources.add(resource);
            }
            if (logger.isTraceEnabled()) {
   
   
                logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }
            return count;
        }
    }

在该方法中显示得到初始化容器时创建的ResourceLoader,通过ResourceLoader.getResource(location)得到Resource资源对象后,调用loadBeanDefinitions(resources);方法,其实是委派XmlBeanDefinitionReader去加载Bean。程序最终来到XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)方法

解析Bean:XmlBeanDefinitionReader#loadBeanDefinitions

//对 Resource进行了编码
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   
   
        return loadBeanDefinitions(new EncodedResource(resource));
    }


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);
        }
        //当前正在加载的资源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
   
   
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
   
   
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
   
   
            //从Resource中得到输入流
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
   
   
                //从输入流中得到XML配置文件源
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
   
   
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //加载BeanDefinitions , 加载Bean的核心方法
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
   
   
                //输入流关闭
                inputStream.close();
            }
        }
        catch (IOException ex) {
   
   
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
   
   
            //删除Resource
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
   
   
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

XmlBeanDefinitionReader#loadBeanDefinitions 对配置文件做了编码处理后,从Resource中得到输入流,然后包装成 InputSource(XML数据源),调用 doLoadBeanDefinitions方法去加载Bean,见:XmlBeanDefinitionReader#doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
   
   

        try {
   
   
            //将 XML 文件转换为 Document 对象,通过 documentLoader来解析
            Document doc = doLoadDocument(inputSource, resource);
            //【重要】解析和注册Bean的消息流程
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
   
   
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
        ...省略...

XmlBeanDefinitionReader 通过 DefaultDocumentLoader#loadDocument 把InputResource转成Document对象,然后委派 BeanDefinitionParserDelegate 去解析Document然后注册Bean。

文章就先到这里结束把,下一章接上Bean的解析和Bean的注册,如果喜欢就给个好评吧,你的肯定是我最大的动力~ 文章数量突破100啦~

相关文章
|
1月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
133 26
|
3月前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
123 69
|
1月前
|
IDE Java 应用服务中间件
spring boot 启动流程
Spring Boot 启动流程简介: 在使用 Spring Boot 之前,启动 Java Web 应用需要配置 Web 容器(如 Tomcat),并将应用打包放入容器目录。而使用 Spring Boot,只需运行 main() 方法即可启动 Web 应用。Spring Boot 的核心启动方法是 SpringApplication.run(),它负责初始化和启动应用上下文。 主要步骤包括: 1. **应用启动计时**:使用 StopWatch 记录启动时间。 2. **打印 Banner**:显示 Spring Boot 的 LOGO。 3. **创建上下文实例**:通过反射创建
107 5
|
7天前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
3月前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
70 21
|
2月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
113 7
|
3月前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
19天前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
69 12
|
22天前
|
Java 应用服务中间件 Maven
SpringBoot项目打包成war包
通过上述步骤,我们成功地将一个Spring Boot应用打包成WAR文件,并部署到外部的Tomcat服务器中。这种方式适用于需要与传统Servlet容器集成的场景。
41 8
|
2月前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
501 17
Spring Boot 两种部署到服务器的方式