前言
上篇博客主要以ClassPathXmlApplicationContext为线,debug走了一遍构造函数。本篇严格来说是上一篇博客的继续,因此本文将继续以ClassPathXmlApplicationContext为线走进AbstractApplicationContext.refresh()。
概述
refresh方法位于ConfigurableApplicationContext接口类中,被AbstractApplicationContext抽象类继承,初步实现了ApplicationContext的一般功能,并且抽象类中使用了模板方法模式,给以后要实现的子类提供了统一的模板。
// 注代码中的中文只是单纯的翻译,以及简单的个人理解。
/**
* Load or refresh the persistent representation of the configuration,
* which might an XML file, properties file, or relational database schema.
* <p>As this is a startup method, it should destroy already created singletons
* if it fails, to avoid dangling resources. In other words, after invocation
* of that method, either all or no singletons at all should be instantiated.
* @throws BeansException if the bean factory could not be initialized
* @throws IllegalStateException if already initialized and multiple refresh
* attempts are not supported
*/
/**
* 加载或则刷新持久表示的配置,这个配置可能来自一个xml文件,配置文件或则关系型数据库对象的集合。由于这是一个 * 启动方法,它应该销毁已经创建的单例。 如果失败了,就避免了悬空的资源。 换句话说,在调用该方法之后,应该
* 实例化所有或者全部的no singletons(这里不知道怎么翻译)。
*/
void refresh() throws BeansException, IllegalStateException;
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 为刷新准备上下文,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
// 告诉子类需要刷新的内部bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 在上下文中准备bean工厂为了之后使用。
// 为bean工厂配置容器特征,例如类加载器,事件处理器等
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 在上下文子类中,允许对bean工厂进行后期处理
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 在上下文中,调用工厂处理器注册成为bean
// 调用bean工厂的后处理器,这些处理器是在bean定义中想容器注册的。
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 注册用于拦截bean创建的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.
// 初始化指定context子类中的特殊bean
onRefresh();
// Check for listener beans and register them.
// 注册并检查bean监听器
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 初始化剩下的非延迟加载(non-lazy-init)单例beans
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 singleßtons to avoid dangling resources.
// 销毁已经创建的单例bean
destroyBeans();
// Reset 'active' flag.
// 取消refresh操作,充值‘activity’标志位
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();
先来看看一下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() { // 设置启动时间 this.startupDate = System.currentTimeMillis(); // 设置关闭标志位false,运行标志位true. // close 和activityAtomicBoolean this.closed.set(false); this.active.set(true); if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); } // Initialize any placeholder property sources in the context environment // 初始化上下文环境中的所有占位符属性元 // 默认为空 initPropertySources(); // Validate that all properties marked as required are resolvable // see ConfigurablePropertyResolver#setRequiredProperties // 验证被标记为必要属性的可解析性 getEnvironment().validateRequiredProperties(); // 上面方法单步进去,看到如下代码。这里对requiredProperties属性的set还不知道在哪。 // public void validateRequiredProperties() { // MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); // for (String key : this.requiredProperties) { // if (this.getProperty(key) == null) { // ex.addMissingRequiredProperty(key); // } // } // if (!ex.getMissingRequiredProperties().isEmpty()) { // throw ex; // } // } // Allow for the collection of early ApplicationEvents, // to be published once the multicaster is available... // 在早期就建立起ApplicationEvent事件容器,一旦多路广播可用就可以发布了。 this.earlyApplicationEvents = new LinkedHashSet<>(); }
obtainFreshBeanFactory()—— 告诉子类刷新内部bean工厂
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { /** * 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工厂 refreshBeanFactory(); // 获取bean工厂 ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); }ß return beanFactory; }
##### 2.1 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. */ @Override 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); } }
refreshBeanFactory中主要分为以下几步:
- 判断是否之前是否存在bean工厂,如果存在,将bean工厂、缓存等信息清空
- 创建一个DefaultListableBeanFactory工厂
- 定制当前bean工厂,能否进行重写及循环引用 –customizeBeanFactory(beanFactory)
加载所有bean到beanFactory
2.2 loadBeanDefinitions(beanFactory)
在2.1中refreshBeanFactory中,loadBeanDefinitions是一个抽象方法,其具体实现交由子类。现在来看看AbstractXmlApplicationContext是如何实现的。
/** * Loads the bean definitions via an XmlBeanDefinitionReader. * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader * @see #initBeanDefinitionReader * @see #loadBeanDefinitions */ // 通过XmlBeanDefinitionReader加载bean定义 @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 创建一个XmlBeanDefinitionReader // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); // 设置当前的environment的对象 beanDefinitionReader.setResourceLoader(this); // 设置对应的ResourceLoader.ApplicationContext是ResourceLoader的子类 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); loadBeanDefinitions(beanDefinitionReader); // 加载对应BeanDefinition对象 }
2.2.1 loadBeanDefinitions
上面loadBeanDefinitions对应的实现
// 在AbstractXmlApplicationContext中loadBeanDefinitions的实现 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); // 为空 if (configResources != null) { reader.loadBeanDefinitions(configResources); } // 在AbstractRefreshableConfigApplicationContext中的getConfigLocations获取配置路径 String[] configLocations = getConfigLocations(); // configLocations=/org/springframework/context/support/test/context*.xml if (configLocations != null) { // 调用xml reader.loadBeanDefinitions(configLocations); } } // 在AbstractBeanDefinitionReader中loadBeanDefinitions()方法解析lications数组 @Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int counter = 0; for (String location : locations) { counter += loadBeanDefinitions(location); } return counter; }
具体的加载beanDefinition的实现在AbstractBeanDefinitionReader中
下面是它的时序图
// 上面for循环中loadBeanDefinitions(location)方法的在AbstractBeanDefinitionReader中的 public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); // 获取资源加载器 if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 将给定的位置模式解析为资源对象 int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } 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 loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }
在上面的getResources()方法之后又调用了一下这些方法:
由上图可知从refresh—>getResourses—>doRetrievenMatchingFiles.其中从getResourses到doRetrievenMatchingFiles主要做了以下这些事: 1) 看location里面是否以”classpath*:“,”war:“,”*/“开头,会做一系列事(这里流程没有进去)。这里主要在PathMatchingResourcePatternResolver#getResources(String locationPattern)
2) 找到location的上层路径rootDirResource,同时分离出外层子路径subPattern
3)转而根据rootDirResource获取完整文件路径rootDir
4)再将完整文件路径rootDir和外层自路径subPattern拼接得到fullPattern
5)最后找出rootDir下所有文件的全路径进行遍历,与fullPattern进行匹配.
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException { if (logger.isDebugEnabled()) { logger.debug("Searching directory [" + dir.getAbsolutePath() + "] for files matching pattern [" + fullPattern + "]"); } File[] dirContents = dir.listFiles(); if (dirContents == null) { if (logger.isWarnEnabled()) { logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]"); } return; } Arrays.sort(dirContents); for (File content : dirContents) { String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/"); if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) { if (!content.canRead()) { if (logger.isDebugEnabled()) { logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() + "] because the application is not allowed to read the directory"); } } else { doRetrieveMatchingFiles(fullPattern, content, result); } } if (getPathMatcher().match(fullPattern, currPath)) { result.add(content); } } }
上述方法中匹配方法调用 getPathMatcher().match(fullPattern, currPath)
最终匹配算法:
AntPathMatcher#doMatch( String pattern, String path,boolean fullMatch, @Nullable Map<String, String> uriTemplateVariables)
匹配算法的大致步骤(中间省略了一些):
1)将需要匹配的两个路径按照路径分割符分割,得到两个有序数组pathDirs(路径下的子文件全路径分割后的字符数组),pattDirs(要匹配的模式分割后的字符串数组)
2)在得到pttDirs后会去看这里面是否存在潜在的匹配,主要是为了去匹配正则表达式中的{ ‘*’, ‘?’, ‘{’ },若不存在潜在的匹配则返回false
3)遍历pattDirs,如果包含”**“,直接跳出遍历;记录当前位置,与pathDirs对应位置的字符串进行比较,不同则返回false
4)第3步全部匹配结束后,
在我们的路径中
pattern = /Users/chonglou/IdeaProject/spring-framework/spring-context/out/test/resources/org/springframework/context/support/test/context*.xml path = /Users/chonglou/IdeaProject/spring-framework/spring-context/out/test/resources/org/springframework/context/support/test/contextC.xml
最后只是去看看我们的模式匹配路径和全路径是否都是以”/“结束,显然上述两个均没有以”/“结束,返回false
这里我们只是粗略介绍,之后,会详细介绍。
注: 在AntPathMatcherTests测试用例中,还暂时未发现通过 - {spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring" 也就是通过花括弧,这种形式去匹配xml路径的。 不过却发现通过 result = pathMatcher.extractUriTemplateVariables( "{symbolicName:[\\w\\.]+}-sources-{version:[\\d\\.]+}-{year:\\d{4}}{month:\\d{2}}{day:\\d{2}}.jar","com.example-sources-1.0.0-20100220.jar"); 这样去解析jar包或则url路径的。之后我们会有单独的博客进行讲解。
总结
- refresh 的一些方法但看确实能懂一些,但是要串起来,感觉有点难啊。加油吧。。。
- prepareBeanFactory()看了一部分了,后面其他方法也会陆续跟上。