定位
以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition。这时使用的是ClassPathResource,这意味着Spring会在类路径中去寻找以文件形式存在的BeanDefinition信息。这里定义的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。我们也可以看到使用ApplicationContext 相对于直接使用DefaultListableBeanFactory的好处。因为在ApplicationContext中,spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IoC容器,需要为它配置特定的读取器才能完成这些功能。 当然, 有利就有弊, 使用DefaultListableBeanFactory这种更底层的容器,能提高定制IoC容器的灵活性。 FilesystemXmlApplicationContext可以从文件系统载入Resource,ClassPathXmlApplicationContext可以从ClassPath载入Resource,,XmlWebApplicationContext可以在Web容器中载入Resource。
ClassPathResource res=new ClassPathResource("beans.xml");
以FileSystemXmlApplicationContext为例, 通过分析这个ApplicationContext的实现来看看它是怎样完成这个Resource定位过程的。下面是这个ApplicationContext的继承体系。
图1 FilesystemXmlApplicationContext的继承体系
从源代码角度看:
图2 代码角度看FilesystemXmlApplicationContext的继承体系
FilesystemXmlApplicationContext.class
我们可以看到在构造函数中,实现了对configuration进行处理的功能。让所有配置在文件系统中的,以.XML 文件方式存在的BeanDefnition都能够得到有效的处理。实现了 getResourceByPath方法,这个方法是个模板方法,是为读取Resource服务的。对于IoC容器功能的实现,这里没有涉及,因为它继承了AbstractXmlApplicationContext 。关于IoC容器功能相关的实现,都是在FileSysternXmlApplicationContext中完成的,但是在构造函数中通过refresh来启动IoC容器的初始化,这个refresh方法非常重要,也是我们以后分析容器初始化过程实现的一个重要入口。 关于读入器的配置,可以到它的基类AbstractXmlApplicationContext中查看。 图3 getResourceByPath的调用关系
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
public FileSystemXmlApplicationContext() {
}
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation file path
* @throws BeansException if context creation failed
*/
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML files and automatically refreshing the context.
* @param configLocations array of file paths
* @throws BeansException if context creation failed
*/
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
/**
* Create a new FileSystemXmlApplicationContext with the given parent,
* loading the definitions from the given XML files and automatically
* refreshing the context.
* @param configLocations array of file paths
* @param parent the parent context
* @throws BeansException if context creation failed
*/
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML files.
* @param configLocations array of file paths
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @throws BeansException if context creation failed
* @see #refresh()
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
/**
* Create a new FileSystemXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of file paths
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
/**
* Resolve resource paths as file system paths.
* <p>Note: Even if a given path starts with a slash, it will get
* interpreted as relative to the current VM working directory.
* This is consistent with the semantics in a Servlet container.
* @param path path to the resource
* @return Resource handle
* @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
*/
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
图4 getResourceByPath的调用过程我们重点AbstractRefreshableApplicationContext的refreshBeanFactory方法的实现。
AbstractRefreshableApplicationContext.class
protected final void refreshBeanFactory() throws BeansException {
//这里判断,如果已经建立了BeanFactory,则销毁并关闭该工厂。
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
//创建并设置持有的DefaultListableBeanFactory的地方同时调用loadBeanDefinitions载入beandefinition信息
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);
}
}
//在上下文中创建DefaultListableBeanFactory的地方载入bean定义,因为允许有多种载入方式,虽然用得最多的是XML定义的形式,
//这里通过一个抽象函数把具体的实现委托给子类来完成
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException;
AbstractBeanDefinitionReader.class
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的路径模式进行解析,得到需要的Resource集合
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;
}
}
DefaultResourceLoader.class
其他ApplicationContext会对应生成其他种类的Resource。下图中我们可以看到Resource类的继承关系。
//对于取得Resource的具体过程,看DefaultResourceLoader是怎样完成的。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//这里处理带有classpath标识的Resource
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
//这里处理带有URL标识的Resource
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
//两者都不是,用此方法,默认得到一个ClassPathContextResource,这个方法常用子类来实现
//前面看到子类FilesystemXmlApplicationContext实现此方法,返回一个Filesystemresource。
return getResourceByPath(location);
}
}
}
图5 Resource的定义和继承关系 我们通过对FilesystemXmlApplicationContext的实现原理为例,了解了Resource定位问题的解决方案。定位完成,接下来就是对返回的Resource对象进行载入了。接下来我们会介绍BeanDdfinition的载入和解析过程。