IOC容器的初始化主要包括三个过程,即
- Resource定位过程;
- BeanDefinition(也就是XML配置文件)的载入;
- 向IOC容器注册BeanDefinition;
Resource定位过程
以FileSystemXmlApplicationContext为例,分析ApplicationContext的实现是怎样完成这个Resource定位过程的。
下面是FileSystemXmlApplicationContext的继承关系图
从类图中,可以发现:
FileSystemXmlApplicationContext继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader。
下面看下FileSystemXmlApplicationContext的源码:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
//构造函数
public FileSystemXmlApplicationContext() {
}
//根据双亲的IOC容器创建容器
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
//configLocation包含的是BeanDefinition所在的路径
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
//configLocationn允许包含多个BeanDefinition路径
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
//configLocationn允许包含多个BeanDefinition路径的同时,还允许指定自己的双亲IOC容器
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
//在对象的初始化过程中,调用refresh函数载入BeanDefinition,这个refresh启动了
//BeanDefinition的载入过程
public FileSystemXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
//1.应用于文件系统中Resource的实现,通过构造一个FileSystemResource来得到一个在文件系统中定
//义的BeanDefinition
//2.这个getResourceByPath是在BeanDefinitionReader的loadBeanDefinition中被调用的
//3.loadBeanDefinition采用了模板模式,具体的定位实现实际上是由各个子类完成的
@Override
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
定位过程分析
执行下面这段程序
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("file:///F:/JavaProdject/spring_bean/src/main/resources/bean.xml");
用Debug分析执行过程
- 首先执行的是AbstractApplicationContext中的静态代码块
- 开始执行FileSystemXmlApplicationContext的构造函数,最后调用的是下面这个构造函数
也就是对BeanDefinition资源的定位的过程,最初是由refresh()这个方法触发的。
- refresh()方法是在基类AbstractApplicationContext调用的
进入上面的这个方法
- obtainFreshBeanFactory方法
- refreshBeanFactory方法是Resource定位的关键
发现执行的是AbstractRefreshableApplicationContext的refreshBeanFactory()方法,this指的是FileSystemXmlApplicationContext
protected final void refreshBeanFactory() throws BeansException {
//判断是否已经建立BeanFactory,如果已经创建则销毁并关闭该BeanFactory
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
//
try {
//创建beanFactory
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
//设置序列化id
beanFactory.setSerializationId(this.getId());
//自定义beanFactory
this.customizeBeanFactory(beanFactory);
//加载BeanDefinition
this.loadBeanDefinitions(beanFactory);
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
createBeanFactory方法
//这里就是创建DefaultListableBeanFactory的地方
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}
//根据容器已有的双亲IOC容器的信息来生成DefaultListableBeanFactory的双亲IOC容器
protected BeanFactory getInternalParentBeanFactory() {
return (BeanFactory)(this.getParent() instanceof ConfigurableApplicationContext ? ((ConfigurableApplicationContext)this.getParent()).getBeanFactory() : this.getParent());
}
loadBeanDefinitions定义在AbstractXmlApplicationContext内
- loadBeanDefinitions方法
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建一个XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//设置值
beanDefinitionReader.setEnvironment(this.getEnvironment());
//在这里设置了属性resourceLoader,this指的是FileSystemXmlApplicationContext
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//初始化XmlBeanDefinitionReader,在这里设置validationMode和namespaceAware
this.initBeanDefinitionReader(beanDefinitionReader);
继续加载BeanDefinitions
this.loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//固定返回null
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//获取BeanDefinition地址,可以是多个
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
//加载BeanDefinition
reader.loadBeanDefinitions(configLocations);
}
}
@Nullable
protected Resource[] getConfigResources() {
return null;
}
- XmlBeanDefinitionReader的loadBeanDefintions调用的是基类AbstractBeanDefinitionReader的方法,这里的this指的是
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
//判断beanDefition的地址是否为空,为空则抛异常
Assert.notNull(locations, "Location array must not be null");
//用来记录beanDefinition的数量
int counter = 0;
String[] var3 = locations;
//获取配置文件的个数
int var4 = locations.length;
//循环加载beanDefinition
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
counter += this.loadBeanDefinitions(location);
}
//返回beanDefinition的个数
return counter;
}
- AbstractBeanDefinitionReader的loadBeanDefinition
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//这里取得resourceLoader,在AbstractXmlApplicationContext的loadBeanDefinitions方法中已经设置,
//取得即是FileSystemXmlApplicationContext
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int loadCount;
//这里对Resource的路径进行解析,得到需要的Resource集合,这些Resource集合指向的我们
//已经定义好的BeanDefinition信息,可以是多个;
//判断是否属于classpath*:的resource加载器
if (!(resourceLoader instanceof ResourcePatternResolver)) {
//调用DefaultResourceLoader的getResource完成具体的Resource定位
Resource resource = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
} else {
try {
//调用DefaultResourceLoader的getResource完成具体的Resource定位
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
loadCount = this.loadBeanDefinitions(resources);
if (actualResources != null) {
Resource[] var6 = resources;
int var7 = resources.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource resource = var6[var8];
actualResources.add(resource);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException var10) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
}
}
}
}
- 前一种getResource方法,这个方法定义在基类DefaultResourceLoader中
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//先使用协议解析器,如果能生成Resource就直接返回
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//如果beanDefinition的地址以"/"开头,就使用前面FileSystemXmlApplicationContext的getResourceByPath生成Resource
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//处理以"classpath:"开头的Resource
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 (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
//如果既不是classpath,也不是URL标识的Resource定位,则调用getResourceByPath
return getResourceByPath(location);
}
}
}
//根据beanDefinition地址得到一个ClassPathContextResource
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
-
后一种AbstractApplicationContext调用的是PathMatchingResourcePatternResolver的getResoruces方法
//判断locationPattern是否以classpath*开头
//true:以findPathMatchingResources的方法或者findAllClassPathResources方法
//false:以findPathMatchingResources的方法或者DefaultResourceLoader的getResource方法
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith("classpath*:")) {
return this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
} else {
int prefixEnd = locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(":") + 1;
return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
}
}
至此,我们已经明确的找到了Resource定位的方法,即DefaultResourceLoader中的getResource方法,该方法针对对各类的beanDefinition创建Resource;此外,AbstractApplicationContext的refrush方法是beanDefinition资源定位的入口。
归纳一下这些方法的调用:
1.FileSystemXmlApplicationContext 构造函数
2. AbstractApplicationContext refresh()
3. AbstractApplicationContext obtainFreshBeanFactory()
4. AbstractRefreshableApplicationContext refreshBeanFactory()
5. AbstractXmlApplicationContext 各种loadBeanDefinitions方法(5个)
6. AbstractBeanDefinitionReader 各种loadBeanDefinitions()(3个)
7. DefaultResourceLoader getResource(String location)
总结
Resource的定位过程包括
- AbstractApplicationContext refresh()方法是入口,是定位的开始方法;
-
AbstractXmlApplicationContext 的loadBeanDefinitions方法中获得beanDefinition的路径;
-
AbstractBeanDefinitionReader中调用getResource方法获取Resource
参考书籍:《SPRING技术内幕:深入解析SPRING架构与设计原理》