第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

时间:2024-04-07 14:56:16

----开篇言:由于自己才疏学浅,加上入行两年多的时间了,如果还停留在if else的阶段,不去深入了解一款框架,不去深入学习一些源码,不去探索一些设计思想,那么自己的核心竞争力就约等于0 。打算利用这两天,深度分析一下Spring Framework框架启动机制与IOC、AOP的底层设计思想,还有著名的Dubbo框架的信息流转方式与底层netty的交互方式,还有Reactor的一些设计思路。

    ok,深度了解一款框架,听别人说,看别人文章我觉得不如自己亲自动手体会来的实在,但同时我们不必做到面面俱到,也就是我们要选择性的过滤一些可以屏蔽的信息,只关注核心思想,有些无用的内容或者不重要的内容会一笔带过。之前深入了解过Springboot的启动流程,这次依旧延续以前的风格。所以我们先把环境搭建起来,一步一步跟着源码去理解spring的世界。

第一章 Spring Framework启动流程

阅读文章需要掌握spring的基础用法及常用注解。

一、构建基础环境

1、pom

IDE构建maven时选择maven-archetype-webapp,这样可以方便我们构建好目录,为后续的学习提供帮助。在pom中依赖Spring三大核心组件Core、 Context 和 Bean。

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.3.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.3.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.1.3.RELEASE</version>
    </dependency>
  </dependencies>

这三大组件的意义如下所示:

core

我们可以把它理解为一个工具包,这个包中都是一些工具或者一些公共组件,例如cglib、asm、utils、annotation等。

有了工具包的支撑,为接下来的两个组件提供了很多服务。

beans bean相当于一个又一个的对象,有些对象是且默认是单例的,有些不是单例的。beans模块更像一个工厂,不仅有生产资料还有生产工具。
context context保证了bean的或者说spring的一个运行时大环境,我个人认为他就是一个大容器,包容了很多的bean对象,提供了很多环境访问接口,维护了bean对象的生命,也维护了bean之间的关系。

2、config

Spring Framework和Springboot的区别相比有所耳闻的人应该很清楚,规约大约配置的理念导致越来越多的人选择了投入springboot的怀抱,但是如果我们选择Spring Framework,配置一定少不了的。我们研究的是spring的启动流程,所以配置以最简单为好:

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 使用组件扫描器+注解方式 -->
    <context:component-scan base-package="com.learn"/>

</beans>

3、代码

启动Spring并且测试加载bean的代码

package com.learn.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @program: com.learn.spring
 * @description: spring启动类
 * @author: liujinghui
 * @create: 2019-02-22 19:18
 **/
public class SpringBootStract {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("config/applicationContext.xml");
        System.out.println(applicationContext.getBean("commonUtils").getClass().getName());
    }
}

----------------------------------------------------------------------------------


package com.learn.spring.utils;

import org.springframework.stereotype.Component;

/**
 * @program: com.learn.spring.utils
 * @description:spring组件
 * @author: liujinghui
 * @create: 2019-02-22 19:42
 **/
@Component
public class CommonUtils {

    private String sth;
    private static String staticSth;

    private CommonUtils(){
        System.out.println("invoke this construct");
    }

    public void doPrint(){
        System.out.println("invoke void method doPrint()");
    }

    public static void doStaticPrint(){
        System.out.println("invoke static void method doStaticPrint()");
    }

    static{
        System.out.println("invoke static method area");
    }

}

二、执行启动

当我们采用main方法执行spring启动的时候,首先会去创建ApplicationContext对象,所以与其说启动Spring,不如说是启动构建Spring的核心ApplicationContext。因为有了ApplicationContext这个世界,才为后续的bean创造了生命。

通过代码我们可以看出ApplicationContext是一个接口。它的类图如下所示:

第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

Spring的ApplicationContext本身方法并不多,但是它之所以能成为Spring的台柱子,就是因为继承了众多的组件,就好像不断地获得装备强化自己的战斗力一样。下面大致描述一下各个接口的作用。

我们大致分析一下ApplicationContext接口继承的这些接口

MessageSource

用于解析消息的策略接口,支持参数化以及此类消息的国际化。

EnvironmentCapable

字面上来看这是一个环境容器,它里面有getEnvironment方法。

不同的实现类中会定义一些不同环境参数。

ApplicationEventPublisher

FunctionalInterface

主要功能是发布事件,告知监听接口

ResourceLoader

spring提供的统一的资源定位接口,可以获取context中的资源
ResourcePatternResolver 扩展了resourceLoader接口,支持通配符的方式获取多个资源,数组形式保存
HirearchicalBeanFactory

提供父容器的获取功能,提供判断本容器是否存在bean的功能。

完成了工厂分级的功能。也就是bean拥有了继承关系

ListableBeanFactory

获取bean定义的集合,也就是说你可以从这里获取bean的生产资料

BeanFactory 定义了获取bean,查找bean的接口

可以看出ApplicationContext的功能已经很强大了,拥有了上述那么多的功能。下面我们看看真正实现ApplicationContext接口的实现类都有哪些,他们分别做什么的:

第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

直接的继承是另两个抽象类

接口WebApplicationContext //针对的是Web应用的容器接口
接口ConfigurableApplicationContext //针对可配置的Spring容器接口

例如本例中我们使用的是ClassPathXMLApplicationContext(左下角),通过继承关系可以看出它其实是一个非Web类型的容器,我们主要使用它做一些getBean的操作,测试学习工作。但是无论使用哪种ApplicationContext,他们背后的Spring初始化工作都是一样的。而且ClassPathXMLApplicationContext(配置文件参数)构造器可以方便我们获取Spring的上下文,所以我们就用他来做演示。

下面让我们聚焦该类的构造器:

可以很清楚的看到传入的参数是一个配置路径,并自动刷新context容器。英文是刷新,我们不妨理解成初始化。

	/**
	 * 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);
	}

点击this方法,可以看出这里执行了三步

1、最终将父容器的环境merge到StandardEnvironment。

2、设置配置文件路径。

3、执行容器构建方法(重点)

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

refresh是重点方法,该方法所在类是AbstractApplicationContext,它实现的ConfigurableApplicationContext 接口,所以可以说所有的配置类容器都会使用这个refresh方法

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {

 第一步: prepareRefresh();

官方解释:Prepare this context for refreshing, setting its startup date and active flag as well as performing any initialization of property sources.

为context容器刷新做准备,设置启动日期和启动状态标志,同时执行属性的初始化工作

1、设置Spring容器启动时间

2、设置Active为true,设置close为false,标志容器正在启动

3、initPropertySources,这个是空方法,具体由子类实现。目前主要是Web应用会去实现,替换environment中的配置为servlet的配置属性。

4、确保所有的environment中required属性都不存在空值。

5、初始化earlyApplicationEvents LinkedHashSet集合,主要是配合监听器使用。

第二步: ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

官方解释:Tell the subclass to refresh the internal bean factory.Returns the fresh BeanFactory instance

官方注释是这一步主要是刷新所有的子容器。其实按照我的理解就是创建一个新的DefaultListableBeanFactory并设置相关属性,之后获取所有的BeanDefinition,并放入DefaultListableBeanFactory的map中缓存起来。

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		return getBeanFactory();
	}

obtainFreshBeanFactory方法中有两个处理逻辑

1、refreshBeanFactory:

protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
  • 判断beanFactory是否存在,如果存在即删除,并创建新的DefaultListableBeanFactory 
  • 设置序列化ID
  • 设置是否允许bean的循环引用和bean定义的重写,默认都不掺乎。
  • 最后这步是关键点:loadBeanDefinitions

打开loadBeanDefinitions,他定义了XmlBeanDefinitionReader,用于读取xml中的bean信息,核心关键点就是最后两行,initBeanDefinitionReader和loadBeanDefinitions。前者主要是设置是否开启校验验证;后者主要是加载资源文件,解析bean配置,生成BeanDefinition集合,真正存储bean的元数据信息和反射信息。

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

 其实我们点击initBeanDefinitionReader往里看,依照本例的方式启动spring容器:

 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("config/applicationContext.xml");

其实我们执行的是下面程序的String[] configLocations = getConfigLocations();这一段方法。在这段方法中,我们配置的"config/applicationContext.xml"将会被spring资源管理模块识别解析,并生产bean定义对象。如果存在多个资源定位,那么将会循环加载资源。

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

loadBeanDefinition时会判断资源是通配符类型资源还是单一资源,他们的差别无非就是读取单个资源还是多个资源的问题。

接下来会调用AbstractBeanDefinitionReader的loadBeanDefinitions方法获取资源文件

	public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int count = 0;
		for (Resource resource : resources) {
			count += loadBeanDefinitions(resource);
		}
		return count;
	}

 接下来调用XmlBeanDefinitionReader中的loadBeanDefinitions方法获取文件初始化currentResources集合,用于装当前正在加载的资源,并将该集合放入正在加载的ThreadLocal(resourcesCurrentlyBeingLoaded)中。之后通过流的方式最终执行doLoadBeanDefinitions方法,将资源加载并返回加载的数量。doLoadBeanDefinitions内部逻辑太复杂,后续有时间在深入分析,不过它具体的做法就是Read bean definitions from the given DOM document and * register them with the registry in the given reader context. -- 从dom文件获取beandefinitions然后注册到reader容器(XmlBeanDefinitionReader)中

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        ...
		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 {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
		}
	...
	}

2、getBeanFactory() 

返回容器中的beanfactory

public final ConfigurableListableBeanFactory getBeanFactory() {
		synchronized (this.beanFactoryMonitor) {
			if (this.beanFactory == null) {
				throw new IllegalStateException("BeanFactory not initialized or already closed - " +
						"call 'refresh' before accessing beans via the ApplicationContext");
			}
			return this.beanFactory;
		}
	}

第三步:prepareBeanFactory(beanFactory); 

官方解释:Configure the factory's standard context characteristics, such as the context's ClassLoader and post-processors.

对上一步创建的beanFactory做属性的深度配置,例如设置Classloader、添加后置处理器、忽略一些自动装配等。

第四步:postProcessBeanFactory(beanFactory);

官方解释:Modify the application context's internal bean factory after its standard initialization. All bean definitions will have been loaded, but no beans will have been instantiated yet. This allows for registering special BeanPostProcessors etc in certain ApplicationContext implementations.

注册那些实现了postProcessBeanFactory的容器吗,使其允许修改其内部的bean工厂。

例如我们可以定义一个类,声明为一个bean,然后实现他的postProcessBeanFactory方法,这样我们就可以有机会操控ConfigurableListableBeanFactory 参数对象。但是注意这步只是声明我要执行的方法,具体执行该方法是在下一步进行的。

/**
 * @program: com.learn.spring.utils
 * @description: 测试如何实现对beanfactory的后置修改
 * @author: liujinghui
 * @create: 2019-02-23 15:01
 **/
@Component
public class TestPostProcessBeanFactory implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("do sth..");
    }
}

第五步:invokeBeanFactoryPostProcessors(beanFactory);

官方解释:Instantiate and invoke all registered BeanFactoryPostProcessor beans, respecting explicit order if given.Must be called before singleton instantiation.

初始化并调用所有注册了BeanFactoryPostProcessor的bean,如果显示声明了顺序的话要按照顺序调用。这一步操作必须在调用单例创建对象之前操作。但是注意这里只能是一个可配置的beanfactory

第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

可以看到我们的方法被执行了。实现这一步的底层逻辑是:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
		// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
		// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
		if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}
	}

第六步:registerBeanPostProcessors(beanFactory);

官方解释:Instantiate and invoke all registered BeanPostProcessor beans, respecting explicit order if given.Must be called before any instantiation of application beans.

注册所有的实现了BeanPostProcessor接口的beans,如果显示声明了顺序则初始化的时候要依据顺序执行。必须在应用bean实例化之前完成。

这里我们先看BeanPostProcessor接口,postProcessBeforeInitialization和postProcessAfterInitialization方法。java8中已经可以给接口增加实现,所以我们在实现该接口的类中继续重写这些方法即可。注意在容器中,执行这一步并不会触发方法,只是注册bean的环绕方法。

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

 下面我们定义自己的bean方法,并实现该接口中的两个方法,观察他的执行时间

第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

可以看到当执行到第十一步的时候,这两个方法被触发,所以到十一步的时候我们再去深入了解触发的时机。

第七步:initMessageSource();

官方解释:Initialize the MessageSource. Use parent's if none defined in this context.

初始化事件监听器(精力有限暂不作为研究重点)

第八步:initApplicationEventMulticaster();

官方解释:Initialize the ApplicationEventMulticaster. Uses SimpleApplicationEventMulticaster if none defined in the context.

事件广播器的初始化(精力有限暂不作为研究重点)

第九步:onRefresh();

官方解释:Template method which can be overridden to add context-specific refresh work. Called on initialization of special beans, before instantiation of singletons.This implementation is empty.

官方指出这是一个模板方法,也就是说子容器可以重写这个方法并实现自己的逻辑,但是这一步操作是发生在bean单例实例化之前。

第十步:registerListeners();

官方解释:Add beans that implement ApplicationListener as listeners. Doesn't affect other listeners, which can be added without being beans.

注册实现了ApplicationListener的监听器(精力有限暂不作为研究重点)

第十一步:finishBeanFactoryInitialization(beanFactory);

官方解释:Finish the initialization of this context's bean factory, initializing all remaining singleton beans.

完成对容器中bean工厂的实例化,对所有余下的单例bean做实例化操作。核心方法是最后一句:preInstantiateSingletons(-->getBean(-->createBean(-->doCreateBean())))。

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// Initialize conversion service for this context.
        //为容器初始化转换服务
		if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
				beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
			beanFactory.setConversionService(
					beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
		}

		// Register a default embedded value resolver if no bean post-processor
		// (such as a PropertyPlaceholderConfigurer bean) registered any before:
		// at this point, primarily for resolution in annotation attribute values.
        // 如果没有bean后处理器,则注册默认的嵌入式值解析器
        // 此时,主要用于解析注释属性值
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
		}

		// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
        // 尽早初始化LoadTimeWeaverAware bean,以便尽早注册它们的转换器。
		String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
		for (String weaverAwareName : weaverAwareNames) {
			getBean(weaverAwareName);
		}

		// Stop using the temporary ClassLoader for type matching.
        // 停止使用临时类装入器进行类型匹配。
		beanFactory.setTempClassLoader(null);

		// Allow for caching all bean definition metadata, not expecting further changes.
		// 允许缓存所有bean定义元数据,不期望有进一步的更改
        beanFactory.freezeConfiguration();

		// Instantiate all remaining (non-lazy-init) singletons.
        // 实例化所有的遗留单例对象
		beanFactory.preInstantiateSingletons();
	}

 下面来了解一下Spring中的bean到底是如何产生的:

1、初始化beanNames List<String>集合,集合中都是我们在代码中定义的和框架中定义的bean名称

第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

2、触发所有的非懒加载的单例bean

for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        if (isFactoryBean(beanName)) {
            Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
            if (bean instanceof FactoryBean) {
                final FactoryBean<?> factory = (FactoryBean<?>) bean;
                boolean isEagerInit;
                if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                    isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                    ((SmartFactoryBean<?>) factory)::isEagerInit,
                            getAccessControlContext());
                }
                else {
                    isEagerInit = (factory instanceof SmartFactoryBean &&
                            ((SmartFactoryBean<?>) factory).isEagerInit());
                }
                if (isEagerInit) {
                    getBean(beanName);
                }
            }
        }
        else {
            getBean(beanName);
        }
    }
}

RootBeanDefinition:他是spring运行时使用的bean的元数据对象,以我们测试代码中的CommonUtils为例,可以看到下图所示:几个关键信息 targetType、scope、beanClass、resource、source。基于这个完整的元数据信息,我们执行下面的创建活动。

第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

以一张流程图说明bean的创建过程:

第一章 基于Spring Framework 5.1.3 探索Spring的启动流程

 

 

第十二步:finishRefresh();

官方解释:Finish the refresh of this context, invoking the LifecycleProcessor's onRefresh() method and publishing the ContextRefreshedEvent.

结束refresh容器的过程,执行实现LifeCycle中onRefresh的方法并发布容易refreshed事件

第十三步:resetCommonCaches();

官方解释:Reset Spring's common reflection metadata caches, in particular the ReflectionUtilsAnnotationUtilsResolvableType and CachedIntrospectionResults caches.

清除反射注解解析类型和进行内省分析的缓存

暂且收工,今后持续补充更新