您的位置:首页 > 编程语言 > Java开发

【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的结构图
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: