一、BeanDefinition的加载:
在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用的。我们通过编程式使用IoC容器可以更好地理解其原理:
ClassPathResource res = new ClassPathResource(“beans.xml”);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Reader.loadBeanDefinition(res);
可以看到使用IoC容器主要包含4个步骤:
创建配置文件的抽象资源,包含了BeanDefinition的定义信息。创建一个BeanFactory,这里使用DefaultListableBeanFactory。创建一个载入BeanDefinition的读取器。从定义好的资源位置读入配置信息,解析过程由BeanDefinitionReader来实现。
1)首先,初始化容器包括BeanDefinition的Resource定位,载入和注册三个基本过程。资源定位统一通过Resource接口来完成,Spring实现了多种定位方式,例如文件系统中的信息可以使用FileSystemResource来定位,类路径中的信息可以通过ClassPathResource来定位等。
2)其次,在定位到配置文件后,就会将配置信息转换成BeanDefinition,BeanDefinition就是POJO对象在IoC容器中的抽象表示,通过这种数据结构,使得IoC容器能够对Bean进行管理。
3)第三个过程是向IoC容器注册这些BeanDefinition,这个步骤必须基于BeanDefinition信息来完成。通过调用BeanDefinitionRegistry接口,把载入过程中解析得到的BeanDefinition向IoC容器进行注册。
通过这3步,容器的初始化就完成了,如果使用的是BeanFactory或者开启了懒加载,依赖注入通常发生在第一次调用getBean()向容器索取Bean的时候。而使用ApplicationContext则会在容器初始化的时候就进行依赖注入。
BeanDefinition的加载,位于在refresh方法中obtainFreshBeanFactory方法:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
1、定位
在Spring中,资源的定位主要由ResourceLoader模块实现。
ResourceLoader 的默认实现是DefaultResourceLoader,而我们可以看到,AbstractApplicationContext通过继承DefaultResourceLoader已经具备了读入BeanDefinition的能力。
public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
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;
}
}
查看DefaultResourceLoader重要方法的代码实现:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return 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 {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
这是一个模板方法,作用是读取Resource资源, FileSystemXmlApplicationContext类重写了这个方法,返回一个FileSystemResource对象,通过这个对象,Spring可以进行相关的I/0操作,完成BeanDefinition的定位。如果是其他的ApplicationContext,那么会对应生成其他种类的Resource,比如ClassPathResource等。
在refresh()方法中调用了obtainFreshBeanFactory(),追踪这个方法可以发现它调用了refreshBeanFactory() -->loadBeanDefinitions(),这个方法的作用即定位载入Resource,在这个方法中,调用了getResourceByPath(),不同的子类具有不同的定位资源的方式实现。
2、载入和解析
载入的过程,相当于把定义的BeanDefinition在IoC容器中转化成一个Spring内部表示的数据结构的过程。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护。
容器的启动,主要由refresh()完成,这个方法描述了应用上下文的初始化过程。refreshBeanFactory()方法创建了BeanFactory,通过这个方法,可以了解bean载入的过程。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建IoC容器,这里使用的是DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//启动对BeanDefinition的载入
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
// loadBeanDefinitons是一个抽象方法,根据不同的子类实现不同资源的读取和载入。例如AbstractXmlApplicationContext中的实现:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
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);
loadBeanDefinitions(beanDefinitionReader);
}
由于AbstractXmlApplicationContext类是通过Xml读入资源的,基于之前configLocations参数,初始化读取器XmlBeanDefinitionReader,并将它在容器中设置好,最后启动这个读取器来完成BeanDefinition在IoC容器中的载入。
3、注册
通过前面的动作,用户定义的BeanDefinition信息已经在IoC容器中建立起了数据结构,但此时这些数据还不能提供IoC容器直接使用,需要在容器中对这些BeanDefinition数据进行注册。在DefaultListableBeanFactory中可以看到持有Bean的容器,是一个HashMap:
private final Map beanDefinitionMap = new ConcurrentHashMap(256);
该类实现了BeanDefinitionRegistry接口,具有注册的功能,通过实现registerBeanDefinition方法,实现注册的具体逻辑,将bean放入Map中。
调用关系如下图:
此时,IoC容器完成了初始化的过程,在容器中已经建立了整个Bean的配置信息,并且这些Bean可以被容器使用了,他们都在beanDefinitionMap里被检索和使用。