一、概述
Spring的IoC容器的启动过程,核心流程是将bean的配置项从不同渠道,包括XML、注解或者配置文件中读取和解析后,生成BeanDefinition的过程,在这过程中IoC容器会进行refresh操作,这个过程可以设置一些BeanPostProcesser的前置或后置操作,在执行完这些操作后,BeanDefinition就会被注册到BeanDefinitionRegistry容器中。
整体IoC容器的启动过程分为3个阶段:定位—>加载–>注册
- 定位: 通过ResourceLoader来完成资源的定位,后续的Bean配置项透视通过Resource资源文件来读取和解析的。
- 加载:ApplicationContext容器调用refresh()方法,解析Bean配置信息将其解析成BeanDefinition,然后调用一系列容器、Bean级别的前置后置处理器,包括调用BeanFactoryPostProcessor 的前置操作。
- 注册:BeanDefinition实例注册,将生成的BeanDefinition放到容器的缓存池BeanDefinitionRegistry中。
二、IoC容器介绍
首先必须要明确一点的就是BeanFactory作为所有IoC容器的顶级类,其包括哪些功能和层次结构,其中getBean(String name)是常见方法,通过该方法可以根据beanName获取Bean对象。
BeanFactory和其他IoC容器的层级关系如下。其中常见的是高级IoC容器ApplicationContext,该容器和BeanFactory相比,具有(1)能够响应时间机制;(2)具有国际化机制;
三、加载过程中常见类介绍
1.BeanDefinition
IoC容器管理的是Bean实例和其关系,在实现层面是通过BeanDefinition来进行描述的。
2.BeanDefinitionReader
BeanDefinitionReader是加载器和解析器,主要功能是从Resource资源类型中解析Bean实例出来,组装成BeanDefinition。
3.BeanDefinitionRegistry
BeanDefinition的注册表,实例化后的BeanDefinition会注册到这里。
四、源码层面分析IoC容器加载流程
IoC的的高级容器ApplicationContext有多种实现,现在以从XML中加载Bean的配置项的实例ClassPathXmlApplicationContext来具体说明IoC容器的加载流程。
1.定位阶段
通过XML配置文件启动一个ApplicationContext容器的方法。
public static void main(String[] args) { // 创建一个Spring容器 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); User user1 = (User) applicationContext.getBean("user1"); user1.sayHello(); }
2.加载阶段
通过ClassPathXmlApplicationContext类中的 refresh()进行刷新操作。
//ClassPathXmlApplicationContext.java ... public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh();//my-刷新容器,生成BeanDefinition } } ...
实现refresh()方法,其中核心是执行:obtainFreshBeanFactory() 方法,会创建一个BeanFactory的实例。同时在这个方法内也会进行一系列的初始化前或后的操作,比如执行方法:postProcessBeanFactory(beanFactory);
//AbstractApplicationContext.java public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // 这里会判断能否刷新,并且返回一个BeanFactory, 刷新不代表完全情况,主要是先执行Bean的销毁,然后重新生成一个BeanFactory,再在接下来的步骤中重新去扫描等等 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. // 准备BeanFactory // 1. 设置BeanFactory的类加载器、SpringEL表达式解析器、类型转化注册器 // 2. 添加三个BeanPostProcessor,注意是具体的BeanPostProcessor实例对象 // 3. 记录ignoreDependencyInterface // 4. 记录ResolvableDependency // 5. 添加三个单例Bean prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. // 子类来设置一下BeanFactory postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // Invoke factory processors registered as beans in the context. // BeanFactory准备好了之后,执行BeanFactoryPostProcessor,开始对BeanFactory进行处理 // 默认情况下: // 此时beanFactory的beanDefinitionMap中有6个BeanDefinition,5个基础BeanDefinition+AppConfig的BeanDefinition // 而这6个中只有一个BeanFactoryPostProcessor:ConfigurationClassPostProcessor // 这里会执行ConfigurationClassPostProcessor进行@Component的扫描,扫描得到BeanDefinition,并注册到beanFactory中 // 注意:扫描的过程中可能又会扫描出其他的BeanFactoryPostProcessor,那么这些BeanFactoryPostProcessor也得在这一步执行 invokeBeanFactoryPostProcessors(beanFactory); // scanner.scan() // Register bean processors that intercept bean creation. // 将扫描到的BeanPostProcessors实例化并排序,并添加到BeanFactory的beanPostProcessors属性中去 registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // Initialize message source for this context. // 设置ApplicationContext的MessageSource,要么是用户设置的,要么是DelegatingMessageSource initMessageSource(); // Initialize event multicaster for this context. // 设置ApplicationContext的applicationEventMulticaster,要么是用户设置的,要么是SimpleApplicationEventMulticaster initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. // 给子类的模板方法 onRefresh(); // Check for listener beans and register them. // 把定义的ApplicationListener的Bean对象,设置到ApplicationContext中去,并执行在此之前所发布的事件 registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); }
具体执行加载BeanDefinition实例的方法。
//AbstractApplicationContext.java protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); return getBeanFactory(); } ... //AbstractRefreshableApplicationContext.java protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory);//my-加载BeanDefinition类 this.beanFactory = beanFactory; } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
需要从资源文件中读取和解析Bean的配置信息,主要通过XmlBeanDefinitionReader来实现。
//AbstractXmlApplicationContext.java protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. 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. initBeanDefinitionReader(beanDefinitionReader);//my-初始化XmlBeanDefinitionReader来 loadBeanDefinitions(beanDefinitionReader);//my-通过XmlBeanDefinitionReader来读取BeanDefinition } ... //AbstractXmlApplicationContext.java 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); } }
实际加载BeanDefinitions的方法。
//XmlBeanDefinitionReader.java 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 currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } //my-从当前目录中加载bean的配置项 try (InputStream inputStream = encodedResource.getResource().getInputStream()) { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } } ... 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.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } //my-从当前目录中加载bean的配置项 try (InputStream inputStream = encodedResource.getResource().getInputStream()) { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource());//my-实际从XML中解析Bean } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
3.注册阶段
开始进行注册。
//XmlBeanDefinitionReader.java protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // spring.xml对应的Document对象 Document doc = doLoadDocument(inputSource, resource); // 解析spring.xml int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; }
通过建立委托类BeanDefinitionParserDelegate来进行注册。
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. BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { 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; } } } preProcessXml(root); parseBeanDefinitions(root, this.delegate);//my-进行注册的核心方法 postProcessXml(root);
将BeanDefinition注册到IoC容器,其底层的数据结构还是HashMap。
//SimpleBeanDefinitionRegistry.java @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "'beanName' must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); this.beanDefinitionMap.put(beanName, beanDefinition); }
4.各阶段时序图
创建ClassPathXmlApplicationContext的IoC容器类的时序图。
Spring的IoC容器启动过程之源码级分析:https://www.processon.com/diagraming/645648cb40d4bf03caee856f
五、总结
最后总结一下,Spring IoC启动的过程就是将Bean组装成BeanDefinition保存在HashMap中的过程。