Spring中AbstractApplicationContext的Refresh方法
Refresh方法可以算的上是SpringIOC容器启动的核心方法了。这几天进行了深入的学习,溯源,整理流程图。
文章内容
1.refresh下的prepareRefresh方法
2.refresh下的obtainFreshBeanFactory方法
refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. /** * 前戏,做容器刷新前的准备工作 * 1、设置容器的启动时间 * 2、设置活跃状态为true * 3、设置关闭状态为false * 4、获取Environment对象,并加载当前系统的属性值到Environment对象中 * 5、准备监听器和事件的集合对象,默认为空的集合 */ prepareRefresh() }
一.prepareRefresh():如注释中表明的,这是容器刷新前的准备工作。为了提高效率,我将代码详解写进代码段的注释中。
protected void prepareRefresh() {
// Switch to active.
// 设置容器启动的时间==>获取当前时间作为容器的启动时间
this.startupDate = System.currentTimeMillis();
// 容器的关闭标志位
this.closed.set(false);
// 容器的激活标志位
this.active.set(true);
// 记录日志
if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
}
else {
logger.debug("Refreshing " + getDisplayName());
}
}
// Initialize any placeholder property sources in the context environment.
/**
* 1.留给子类覆盖,初始化属性资源
* 2.假设:项目需要一些系统资源,那么子类重写这个方法的时候并被调用的时候,
* 就可以通过getEnvironment()方法,将所需要的系统环境变量名加入其中,以进行下一步的判断
*/
initPropertySources();
// Validate that all properties marked as required are resolvable:
// see ConfigurablePropertyResolver#setRequiredProperties
/**
* 1.先返回当前的系统环境对象
* 2.验证需要的属性文件是否都已经放入环境中
* 3.为了验证 initPropertySources();
*/
getEnvironment().validateRequiredProperties();
// Store pre-refresh ApplicationListeners...
// 判断刷新前的应用程序监听器集合是否为空,如果为空,则将监听器添加到此集合中
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<>();
}
二.obtainFreshBeanFactory方法。这里面进行了XML文件的解析工作,将XML解析成BeanDefinition。
refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Tell the subclass to refresh the internal bean factory.
// 创建容器对象:DefaultListableBeanFactory
// 设置BeanFactory的Id,设置一些默认值,比如是否需要解决循环依赖
// 加载xml配置文件的属性值到当前工厂中,最重要的就是BeanDefinition
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
}
阅读完代码段中关于该方法的释意后,我们来进行详解一下
1.在创建BeanFacory之前,先进行了刷新工厂的操作
2.接着来关注一下如何进行工厂刷新操作 ==>调用的是 AbstractRefreshableApplicationContext类下的方法protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { /** * 1.初始化BeanFactory * 2.对XML配置文件进行读取 * 3.将得到的BeanFactory记录到当前的实体内 */ refreshBeanFactory(); // 返回当前实体的beanFactory属性 return getBeanFactory(); }
@Override protected final void refreshBeanFactory() throws BeansException { // 如果存在beanFactory,则销毁beanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { /** * 创建DefaultListableBeanFactory对象 */ DefaultListableBeanFactory beanFactory = createBeanFactory(); // 为了序列化指定id,可以从id反序列化到beanFactory对象 beanFactory.setSerializationId(getId()); // 定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖 // 子类如果进行实现,那么该方法就调用子类的 customizeBeanFactory(beanFactory); // 初始化documentReader,并进行XML文件读取及解析,默认命名空间的解析,自定义标签的解析 loadBeanDefinitions(beanFactory); this.beanFactory = beanFactory; } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
3.该方法中最为重要的就是loadBeanDefinitions(beanFacory)方法,这里面准备进行XML的解析操作==>调用的是AbstractXmlApplicationContext类下的方法。
总结而言该方法主要的事情做了2个
1)创建XmlBeanDefinitionReader对象,也就是配置文件读取对象
2)就是引导下一步的解析操作
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // 创建一个xml的beanDefinitionReader,并通过回调设置到beanFactory中 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. // 给reader对象设置环境对象 beanDefinitionReader.setEnvironment(this.getEnvironment()); // 设置资源加载器 (this==ClassPathXmlApplicationContext) beanDefinitionReader.setResourceLoader(this); // 设置实体处理器(这里的实体是指XML内的信息当成实体) // new ResourceEntityResolver(this) 会将 xsd文件所映射的文件位置(META-INF/spring.schemas) /** * 1.创建了dtd解析器和xsd解析器对象赋值给==>ResourceEntityResolver对象 * 2.在创建解析xsd文件解析器时, 指定了要去查找的配置文件位置 "META-INF/spring.schemas" * 2.将当前上下文对象作为Reader的this.resourceLoader */ beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. // 初始化beanDefinitionReader对象,此处设置配置文件是否要进行验证 initBeanDefinitionReader(beanDefinitionReader); // 开始完成beanDefinition的加载==>解析XML loadBeanDefinitions(beanDefinitionReader); }
4.如下方法,进行读取配置文件名称,准备进行解析配置文件名的操作
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { // 以Resource的方式获得配置文件的资源位置 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // 以String的形式获得配置文件的位置 //我们通常都是以字符串形式来录入配置文件名称,所以在此方法中,只会执行以下分支分段,上面的分支判断是走不通的。因为我们的Debug就是以字符串形式在走 String[] configLocations = getConfigLocations(); if (configLocations != null) { //开始加载每一个XML配置文件 reader.loadBeanDefinitions(configLocations); } }
5.该方法是AbstractBeanDefinitionReader下的方法,我们创建的XmlBeanDefinitionReader继承与它
该方法本质上是将String[] 配置文件名数组,进行了遍历,从而进行逐个解析
@Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int count = 0; for (String location : locations) { //遍历每一个XML配置文件,然后进行加载单个配置文件的定义信息 count += loadBeanDefinitions(location); } return count; }
6.还是在AbstractBeanDefinitionReader方法中的主要操作
1)将遍历出来的配置文件名解析为Resource[] ,因为下面内容较多,解析Resource[]的流程我们不再赘言,大致来讲,就是将一个个的配置文件名称再进行解析一边,看一下是否包含“classpath:”此类的开头结尾以进行下一步操作
2)加载Bean的定义信息。接下来的小点就来解答一下 loadBeanDefinitions(resources);
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { //在创建XmlBeanDefinitionReader时,就将ClassPathXmlApplicationContext通过构造函数的方式写入了 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); } //AbstractApplicationContext的构造方法中进行了设置ResourcePatternResolver if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { /** * 1.ClassPathXmlApplicationContext也是继承了ResourcePatternResolver * 2.ResourcePatternResolver中的getResources()被AbstractApplicationContext重写了 * 3.getResources()这个方法的作用是对配置文件的前缀和后缀进行判断 * 前缀: "classpath*:"; * 后缀: "war:" */ // 调用DefaultResourceLoader的getResource完成具体的Resource定位 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); //加载Bean的定义信息 解析后的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 { // 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; } }
7.还是属性的配方,将Resource[] ==>Resource。通过循环遍历的方式取出一个个Resource进行解析。此时我们经历了配置文件名从
String[] ==> String ==> Resource[] ==> Resource
@Override 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; }
7.以下方法中 通过包含包裹着Ressource的流和Resource来进行解析配置文件的核心处理逻辑,此时的类是XmlBeanDefinitionReader
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!"); } // 从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream 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(); } } }
8.下面代码段中的重点有2个
1)将Rsource资源解析成Document对象,相对而言,可以不仔细研究
2)注册BeanDefinition。接下来会进行往下走。 registerBeanDefinitions(doc, resource)
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { /** * 1.doLoadDocument实现的过程中还定义了是以dtd还是xsd形式进行解析 * 2.此处获取xml文件的document对象,这个解析过程是由documentLoader完成的, * 3.从String[] - string - Resource[] - resource, * 4.最终开始将resource读取成一个document文档,根据文档的节点信息封装成一个个的BeanDefinition对象 */ Document doc = doLoadDocument(inputSource, resource); //注册BeanDefinition定义信息 int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
9.该方法有三个流程
1)创建一个用来解析Document对象的处理类
2)为了程序编译通过而维护的返回值设置
3)解析Docemnet对象的操作。下一步往内走
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 创建一个解析document解析对象 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //获取在这之前有的BeanDefinition int countBefore = getRegistry().getBeanDefinitionCount(); // 完成具体的解析过程 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
10.接下来的方法是DefaultBeanDefinitionDocumentReader类的方法
1)主要的方法是倒数第三个,传入root解析出BeanDefinition
protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> 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)) { /** * 判断是否有PROFILE_ATTRIBUTE这个属性值 * 1.PROFILE_ATTRIBUTE是否包含 profile这个属性值 * 2.用来指定选择哪一个配置文件 * 3.springboot可能会有 */ 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; } } } //SpringMVC时讲解 preProcessXml(root); //解析BeanDefinition 传入根节点,和解析器==>从跟节点解析document parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
11.判断标签属于哪种标签,以选择不同的方式来处理。本文叙述的是以<Bean>标签为代表的默认命名空间内的标签解析
1)因为是以<Bean>标签举例,所以我们走的是parseDefaultElement(ele, delegate);方法。然后接下来我们再追以下源码。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) {//是否是默认的命名空间 //<beans> NodeList nl = root.getChildNodes();//获取第一个子节点 for (int i = 0; i < nl.getLength(); i++) {//获取nl的长度 //比如<beans></beans> 下的<bean></bean>等 Node node = nl.item(i); //获取子节点下的每一个子节点==>空格换行也算 if (node instanceof Element) {//判断该节点是否为一个元素 Element ele = (Element) node; //将节点转为Element 元素对象 /** * 1.比如<bean><bean/>就是默认的命名空间里的元素 * 2.<aop:><aop:/> <context:><context/>这些是我们额外导入的命名空间 * 3.这两种的解析方式是不一样==>所以要分开去进行解析 * 4.可以看到方法调用了解不同的处理逻辑 */ if (delegate.isDefaultNamespace(ele)) {//根据当前这个元素判断是否为默认命名中间中的元素 parseDefaultElement(ele, delegate); } else { //解析自定义标签 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
12.该方法是用来解析默认命名空间内的标签的
1)通过这个方法,我们可以得出。默认命名空间的标签有四种
- <import>
- <alias>
- <bean>
- <beans>
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //如果是<import> if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } //如果是<alias> else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } //如果是<bean> else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } //如果是<beans> else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // doRegisterBeanDefinitions(ele); } }
13.接下来当然是走processBeanDefinition(ele, delegate);方法了
1)根据传入的节点对其进行解析得到beanDefinitionHolder对象。对于这个对象,代码中已经有注释了
2)如果想要研究以下具体解析流程的话,可以在往下追。大致提下流程。先读出节点中的属性值:id,class,等等。将其包装为AbstractBeanDefinition,返回的时候为beanDefinitionHolder【当然这是下面方法再往下追的流程了】
3)然后就是注册BeanDefinition信息BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());。接下来我们来看看
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // beanDefinitionHolder是beanDefinition对象的封装类, // 封装了BeanDefinition,bean的名字和别名,用它来完成向IOC容器的注册 // 得到这个beanDefinitionHolder就意味着 // beanDefinition是通过BeanDefinitionParserDelegate对xml元素的信息按照spring的bean规则进行解析得到的 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. // 向ioc容器注册解析得到的beanDefinition的地方 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. // 在beanDefinition向ioc容器注册完成之后,发送消息 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
14.以下方法开启了注册BeanDefinition的入口。该类是BeanDefinitionReaderUtils
1)追下一层的入口是registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());。我们来看看下
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. // 使用beanName做唯一标识注册 String beanName = definitionHolder.getBeanName(); 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); } } }
15.这个类是DefaultListableBeanFactory。还有印象吗?我们在第二步,刷新工厂时创建了它
1)下面代码很长,我来粗糙的说下。这里存了2个很重要的东西到我们的BeanFactory内
- beanDefinitionMap。用来存放BeanDefinition
- beanDefinitionNames。用来存放beanId
@Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { // 注册前的最后一个校验,这里的检验不同于之前的xml文件校验,主要是对应abstractBeanDefinition属性的methodOverrides校验, // 检验methodOverrides是否与工厂方法并存或者methodoverrides对应的方法根本不存在 ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); // 处理注册已经注册的beanName情况 if (existingDefinition != null) { // 如果对应的beanName已经注册且在配置中配置了bean不允许被覆盖,则抛出异常 if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } this.beanDefinitionMap.put(beanName, beanDefinition); } else { if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; removeManualSingletonName(beanName); } } else { // Still in startup registration phase // 注册beanDefinition this.beanDefinitionMap.put(beanName, beanDefinition); // 记录beanName this.beanDefinitionNames.add(beanName); removeManualSingletonName(beanName); } this.frozenBeanDefinitionNames = null; } if (existingDefinition != null || containsSingleton(beanName)) { // 重置所有beanName对应的缓存 resetBeanDefinition(beanName); } else if (isConfigurationFrozen()) { clearByTypeCache(); } }
到这里算是对创建BeanFactory流程分支中的刷新工厂以及解析配置文件做了一定的详细解读。最后就是配上解析配置文件的流程图