【Spring源码--IOC容器的实现】(二)BeanDefinition的Resource定位
2016-08-12 17:42
811 查看
前言
1.上一篇我们说到了refresh()方法,这个是容器初始化的入口。我们知道,容器初始化共有三个阶段:Resource定位,BeanDefinition解析,BeanDefinition注册。今天我们要看的是Resource定位。2.重要提示:这三个过程是紧密联系的,可以说讲解了Resource定位的源码,其他两个过程的源码都出来了。所以我们今天只看Resource定位,对于其他不多做研究。因为:先走通主线,再关注细节。
3.理解下组合复用的设计模式。举例:ApplicationContext接口继承了ListableBeanFactory,HierarchicalBeanFactory等等接口,那么就意味着 ApplicationContext可以提供这些接口中定义的所有功能,但是这些功能的实际实现并不是由ApplicationContext的实现类提供的,而是以组合复用的方式委托给了各个接口的实际实现类来完成。
BeanDefinition的Resource定位
接着上一篇的refresh()方法,这里是进入了AbstractApplicationContext的refresh()方法。(大家理解下组合复用,这里我理解的也不是很清楚)。我们来看下这个refresh()方法,这个方法很长,大家先只关心关键代码。代码1.1:AbstractApplicationContext的refresh()方法
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取 容器的当时时间,同时给容器设置同步标识.
prepareRefresh();
//告诉子类启动refreshBeanFactory()方法,
//Bean定义资源文件的载入从子类的refreshBeanFactory()方法启动【重点】
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为BeanFactory配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
//为容器的某些子类指定特殊的BeanPost事件处理器
postProcessBeanFactory(beanFactory);
//调用所有注册的BeanFactoryPostProcessor的Bean
invokeBeanFactoryPostProcessors(beanFactory);
//为BeanFactory注册BeanPost事件处理器.BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
//初始化信息源,和国际化相关.
initMessageSource();
//初始化容器事件传播器.
initApplicationEventMulticaster();
//调用子类的某些特殊Bean初始化方法
onRefresh();
//为事件传播器注册事件监听器.
registerListeners();
//初始化所有剩余的单态Bean.
finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
上面这个方法是整个IOC容器的初始化全过程,那么我们先关注obtainFreshBeanFactory()这个方法,因为这个方法里面会实现我们上述的Resource定位及其他两个过程。来看下这个方法的源码:
代码1.2:AbstractApplicationContext的obtainFreshBeanFactory()方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
上面代码可以看到refreshBeanFactory,AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是其子类AbstractRefreshableApplicationContext实现的 refreshBeanFactory()方法,方法的源码如下:
代码1.3:AbstractRefreshableApplicationContext的refreshBeanFactory()方法
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {//如果已经有容器,销 毁 容器中的bean,关闭容器
destroyBeans();
closeBeanFactory();
}
try {
//创建IoC容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
//对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//调用载入Bean定义的方法,主要这里使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
上面代码注释中已经说到了,具体的方法在子类中实现,我们先来看下到底有哪些子类,然后找到我们Web容器启动走的路线:
从上面的图中可以看到,由于我们这里是通过Web启动的,所以我们自然来到了XmlWebApplicationContext的loadBeanDefinitions方法,来看下代码:
代码1.3:XmlWebApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 这里使用XMLBeanDefinitionReader来载入bean定义信息的XML文件(后面再讲)
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//这里配置reader的环境,其中ResourceLoader是我们用来定位bean定义信息资源位置的
///因为上下文本身实现了ResourceLoader接口,所以可以直接把上下文作为ResourceLoader传递给XmlBeanDefinitionReader
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);
//这里转到定义好的XmlBeanDefinitionReader中对载入bean信息进行处理
loadBeanDefinitions(beanDefinitionReader);
}
上面的XMLBeanDefinitionReader我们先不理睬,后面BeanDefinition解析的时候会讲到这个类,我们直接看loadBeanDefinitions方法,这里是具体载入bean信息,那么要载入就要知道bean的定义文件在哪,所以Resource定位还要继续跟下去,来看这个方法的源码:
代码1.4:XmlWebApplicationContext的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
//获取configLocations,也就是配置文件路径
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
看到这里大家还记得之前我们的web.xml里面配置的context-param吗?还记得我们的ContextLoader里面设置了wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM))吗?这里我们就取到了xml的位置。但是这里我们并没有看到我们一直在说的Resource,那么继续看代码:经过了一次重载的方法,我们最终可以看到这个方法:
代码1.5:AbstractBeanDefinitionReader的loadBeanDefinitions方法
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//这里得到当前定义的ResourceLoader,默认的使用DefaultResourceLoader
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//下面这堆是对Resource的路径模式进行解析(像我们在web.xml里面有可能使用通配符等),得到需要的Resource集合,这些Resource集合指向了我们定义好的BeanDefinition的信息,可以是多个文件。
if (resourceLoader instanceof ResourcePatternResolver) {
//这里处理我们在定义位置时使用的各种pattern,需要ResourcePatternResolver来完成
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 {
// 这里通过ResourceLoader来完成位置定位
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;
}
}
那么对于取得Resource的具体过程,((ResourcePatternResolver) resourceLoader).getResources(location)这个方法大家点开看看就可以知道,是PathMatchingResourcePatternResolver中的实现的,它具体里面就是针对我们配置的是否以“classpath*:” 开头分别处理。对于resourceLoader.getResource(location)方法,具体是交给继承Resource的子类完成的。这里不再不多了,就放一张Resource的代码路径图吧:
Resource组件
Resource组件与ResourceLoader组件一起工作,将字符串格式指示的资源解析为Resource对象。事实上ResourceLoader是Resource的工厂类, ResourceLoader的核心工作就是解析location。location示例:”classpath:applicationContext.xml”,”classpath:applicationContext-.xml”,”file:/some/resource/path/myTemplate.txt”,
”http://XX/resource/path/myTemplate.txt“
ResourceLoader根据所指示的前缀返回特定的Resource对象。看下Resource的结构图:
Resource的结构图:
相关文章推荐
- Spring技术内幕之IOC容器的实现(02)-BeanDefinition的Resource定位
- 【Spring源码--IOC容器的实现】(三)BeanDefinition的载入和解析【II】
- 【Spring源码--IOC容器的实现】(四)BeanDefinition的注册
- 【Spring源码--IOC容器的实现】(三)BeanDefinition的载入和解析【I】
- Spring源码阅读之IoC容器初始化2 -- BeanDefinition载入与解析
- Spring源码阅读--BeanDefinition 在 IOC 容器中的注册
- Spring源码阅读之IoC容器初始化3 -- BeanDefinition在IoC容器中的注册
- Spring源码解析-BeanDefinition在IOC容器中的注册(三)
- Ioc容器beanDefinition-Spring 源码系列(1)
- Spring IOC容器分析(2) -- BeanDefinition
- 深探spring系列-----ioc初始化(1)BeanDefinition的Resource定位
- spring 技术内幕--IOC初始化之BeanDefinition的在IOC容器中的注册
- spring IOC源码学习(二):BeanDefinition资源加载
- 【spring源码分析】BeanDefinitionRegistryPostProcessor接口可自定义bean加入IOC
- (spring-第8回【IoC基础篇】)BeanDefinition在IoC容器中的注册
- IOC容器初始化——BeanDefinition的Resource定位
- spring技术内幕笔记:IoC容器的初始化过程(3)- BeanDefinition的注册
- 小读spring ioc源码(四)——BeanDefinitionReader
- spring技术内幕笔记:IoC容器初始化过程(2)- BeanDefinition的载入
- 小读spring ioc源码(五)——BeanDefinitionDocumentReader