bean 的声明方式,已经在概述中描述,此处就不再赘述, 声明成概述的样子,就已经能满足我们的大多数应用了。
在文章开始是之前,先上一段最精简的代码,本篇文章都是围绕下面这段代码进行的,抽丝拨茧留下的才是精华。
如下:
//首先进行BeanDefinition资源文件的定位,封装为Source的子类
ClassPathResource res = new ClassPathResource("application.xml");
//创建基本的IOC容器
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//创建文件读取器,并进行回调设置。
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//资源加载解析
reader.loadBeanDefinitions(res);
MessageService bean = factory.getBean(MessageService.class);
String message = bean.getMessage();
System.out.println(message);
上面的代码,打破了代码的封装,直接穿透到了底层,执行流程是否是更加的清晰了。为了让读者有一个更清晰的认识,我特地画了一张流程 图。首先有一个宏观上的把控,这也是本篇文章的叙述线索。没有接触过源码的同学,此时看这张 图可能 还是有点模糊,不过没有关系,后面我会进行更加详细的分析,建议读者同学,在阅读文章 的同时,更跟随一下本地代码的执行,这样流程能更加的清晰。
流程图如下:
图片有些失真,看不太清,没关系,上传到了github, 点我就可以
启动过程分析
接下来,从ClassPathXmlApplicationContext 的构造函数开始,分析定义Bean的资源文件解析过程。有些人会用FileSystemXmlApplicationContext 作为入口,这里我使用的是ClassPathXmlApplication。 这个 没有关系,无论以哪个类最为入口最终都会流向AbstractApplicationContext 的refresh()方法,效果都是一样的。
为了不脱离主线和节省篇幅,后边的流程我只截取最主干的部分就行解析。ClassPathApplicationContext 提供了多个构造函数,但是最终都会转到下面的方法来执行。
此构造方法的代码并不是很多,只有四行,并且在该方法中显示的使用了super 关键字,其实在类初始化的过程中,super关键字并不是必须的,编译器会默认帮我们添加上,只不过执行的是无参的构造方法,这里要执行父类参数为ApplicationContext类型的构造方法,才进行了显示的声明。
来看一下上面的构造方法中,最终都执行了那些操作,具体的操作都是由那些类执行的。
通过代码跟随,可以发现,super(parent)这个方法最终是由其基类(AbstractApplicationContext)来执行,执行了AbstractApplicationContext的无参构造方法和setParent()方法。其代码如下,省略了静态代码块的定义:
调用父类的构造方法执行完成后,返回ClassPathApplicationContext类,执行setConfigLocations(configLocations)方法,该方法的定义在类AbstractRefreshableConfigApplicationContext中:
在上面的方法中,看到参数采用的数组的方式,也就是说,资源文件支持下面两种实现方式,扩展性更好一些:
- ClasspathResource res = new ClasspathResource("a.xml,b.xml");
- ClasspathResource res = new ClasspathResource("new String(){'a.xml' , 'b.xml'}")
我们来看以上,程序执行到此处后,都做了哪些操作:
- 在AbstractApplicationContext中初始化了resourcePatternResolver(多资源文件的载入),用于获取Resource,关于何时使用后面再解释
- 将资源的定义路径保存在了configLocations数组中
到此,IOC容器根据资源定义路径获取Resouce的准备工作便完成了。
下面来看一下ClassPathXmlApplicationContext 中的关于refresh()方法的调用,实际调用的是AbstractApplicationContext中的refresh()方法,该方法定义如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//环境准备,获取容器启动的时间,设置活动标志,以及属性的初始化
prepareRefresh();
//在子类中启动resreshBeanfactory()方法
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为BeanFactory配置容器属性
prepareBeanFactory(beanFactory);
try {
//设置BeanFactory的后置处理
postProcessBeanFactory(beanFactory);
//调用后置处理,为这些后置处理器在Bean的定义中向容器注册
invokeBeanFactoryPostProcessors(beanFactory);
//注册Bean的后续处理,在Bean创建过程中调用
registerBeanPostProcessors(beanFactory);
//初始化上下文消息机制
initMessageSource();
//初始化上下文中事件机制
initApplicationEventMulticaster();
//初始化其他特殊的Bean
onRefresh();
//检查监听Bean并且将这些Bean向容器注册
registerListeners();
//初始化所有的singleton beans , 设置lazy-init = true 的bean除外
finishBeanFactoryInitialization(beanFactory);
//发布容器事件,结束Refresh过程
finishRefresh();
}catch (BeansException ex) {
// 为防止Bean资源占用,在异常处理中,销毁已经生成的单件Bean
destroyBeans();
//重置Rest标志
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
上面的代码便是整个执行的流程。可以看到使用了synchronized 关键字,防止没有初始化 完成,再次进行初始化。
refresh()方法主要为IOC容器Bean的生命周期提供管理条件。Spring IOC容器的生成是从refreshBeanFactory()方法开始的,也就是执行了下面的代码:
在AbstractApplicationContext抽象类中,只是进行了refreshBeanFactory()方法的定义,方法的实现是在其子类AbstractRefreshableApplicationContext中实现的,在子类的定义如下:
上面代码loadBeanDefinitions(beanFactory);我们说是一个委派模式,只是进行了方法的定义,具体实现则是由AbstractXmlApplicationContext类实现,在该方法中创建了读取器XmlBeanDefinitionReader的实例,然后把这个读取器在IOC容器中设置好,最后是启动读取器来完成对BeanDefinition在IOC容器中的载入,定义如下:
在XmlBeanDefinitionReader的初始化过程中,还进行了一些其他的操作,具体如下:
通过上面代码发现,在创建XmlBeanDefinitionReader的过程中,完成了resourceLoader和eviironment的赋值操作。
首先得到BeanDefinition信息的Resource定位,然后直接调用XmlBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader来完成的。因为使用的FileSystemXmlApplicationContext, getConfigResources()方法返回的是null,所以程序会走第二个分支
//Xml Bean读取器加载Bean定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取资源定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
reader.loadBeanDefinitions(configResources);
}
//如果子类获取的Bean定义为空,则获取FileSystemXmlApplication构造方法中setConfigLocations方法设置的资源。
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinition(configLocations);
}
}
程序分析到这,来梳理一下上面的执行流程。
在ClassPathXmlApplicationContext 一共做了三件事
调用了父类的构造器,进行了初始化
设置了BeanDefinition的定义路径
执行了Refresh()方法
refresh()方法来启动整个BeanDefinition的载入过程
创建容器 DefaultListableBeanFactory
创建了XmlBeanDefinitionReader
开始准备通过reader来加载资源
AbstractBeanDefinitionReader读取Bean定义资源,AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:
//方法重载
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
String[] var3 = locations;
int var4 = locations.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
counter += this.loadBeanDefinitions(location);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(location, (Set)null);
}
//重载的最终执行方法
public int loadBeanDefinitions(String location, Set actualResources) throws BeanDefinitionStoreException {
//获取资源加载器
//上面提到过,XmlBeanDefinitionReader初始化时,在其父类中执行了加载器的初始化操作
//resourceLoader的类型为PathMatchingResourcePatternResolver
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//判断类型
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//将指定位置的Bean定义资源文件解析转化为Resource
//加载多个指定位置的资源定义文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//读取Resource
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;
}
}
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
Resource[] var3 = resources;
int var4 = resources.length;
for(int var5 = 0; var5 < var4; ++var5) {
Resource resource = var3[var5];
counter += this.loadBeanDefinitions((Resource)resource);
}
return counter;
}
上面的方法主要进行了两件事:
调用资源加载器获取资源 resourceLoader.getResource(location)
真正执行加载功能的是子类XmlBeanDefinitionReader的loadBeanDefinitions方法
loadBeanDefinitions()方法在AbstractBeanDefinitionReader中并没有具体的实现,它会转到XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)中运行:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//对xml资源进行编码处理
return this.loadBeanDefinitions(new EncodedResource(resource));
}
//方法重载,转入此方法执行
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if(this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Object currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if(currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if(!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var5;
try {
//将资源文件转换为类型为InputStream的I/O流
InputStream ex = encodedResource.getResource().getInputStream();
try {
//从InputStream中得到xML的解析源
InputSource inputSource = new InputSource(ex);
//编码如果不为null, 则设置inputSource的编码
if(encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//读取数据
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
ex.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if(((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
return var5;
}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {
try {
//转化为Document 对象
Document doc = doLoadDocument(inputSource, resource);
//启动对Bean定义解析的详细过程,会用到Spring Bean的配置规则
return registerBeanDefinitions(doc, resource);
}
//删除了部分catch语句
catch (....) {
throw ex;
}
}
将XML文件转换成Document对象,解析过程由documentLoader实现。到此就完成了xml文件的读取工作。
下面详细分析一下,文件是如何读取的? , 回到下面的这段代码
//Xml Bean读取器加载Bean定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取资源定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
reader.loadBeanDefinitions(configResources);
}
//如果子类获取的Bean定义为空,则获取FileSystemXmlApplication构造方法中setConfigLocations方法设置的资源。
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinition(configLocations);
}
}
程序有两个分支,一个是获取Resource[] 数组的分支,一个是 String[]数组的分支。在创建ClassPathXmlApplicationContext 的时候,初始化了getConfigLocations()方法,所以程序会走第二个分支。configLoactions的内容就是 classpath:application.xml .之后程序进入到XmlBeanDefinitionReader中将 文件转换为流。
运行 AbstractBeanDefinitionReader中的 loadBeanDefinitions(String location, @Nullable Set
getResourceLoader()方法,返回了 ClassPathXmlApplicationContext ,ClassPathXmlApplicationContext 是 ResourcePatternResolver的子类。所以会执行 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 此处代码就是资源定位的核心。 PathMatchingResourcePatternResolver 是 ResourcePatternResolver 的实现类 ,最终会执行 它的getResource()方法。
经过一系列判断,最终程序会走 return new Resource[] {getResourceLoader().getResource(locationPattern)}; 这段代码 ,getResourceLoader()返回的是DefaultResourceLoader 类。执行的是它的getResource()方法,方法定义如下:
程序最终是由这段代码,进行文件的加载:
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
getClassLoader()返回的是 AppClassLoader .
ClassPathResource 作为Resource的子类,自然可以得到 Resource[]类型的数组。那么只要调用 getInputStream()方法,便可以得到文件流。
那么下面看一下,是否是这样实现的。
下面的代码我就直接罗列了, 没有什么太复杂的逻辑。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
# 在进行一个封装
return loadBeanDefinitions(new EncodedResource(resource));
}
和预想的一样,最后调用了getInputStream()方法,使用了InputStream将流转换成字节流。既然有了流,就可以创建Document了。以便来解析文件中定义的内容。
既然分析到这了,就多说一点,上面的文件加载最终是通过ClassLoader加载的。
ClassLoader: 负责加载类的对象。是一个抽象类。通过一个指定类的全限定名,找到对应的Class字节码文件,然后加载它并转化成一个java.lang.Class类的实例。
ClassLoader 分类:
- 启动类加载器: 负责加载\lib 目录下的类加载到虚拟机内存中
- 扩展类加载器:这个类负责加载\lib\ext目录下的类库
- 应用类加载器:这个类加载器负责加载用户类路径 classpath 下的类库,一般自己编写的类都是由这个加载器进行加载的。
除了上面的三种加载器,还可以自定义类加载器,不过一般用不到。
说到类加载器自然离不开双亲委派模型- 它的工作原理是:如果一个类加载器收到类的加载请求,不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完整,这样层层递进,最后所有的请求被传到顶层的类加载器。只有当父类加载器无法加载,才会交给子类加载器去加载。这样的好处是防止类的重复加载。
ClassLoader是从classPath中读取资源的一个类,一般都是用来加载class, 实际上,但凡是处在classpath中的文件,统称为资源,都可以是classLoader来读取。推荐使用此种方式Thread.currentThread().getContextClassLoader() 获取classLoader , 尽量少用 ClassLoader.getSystemClassLoader()的方式。
本篇文章就到这,下次来写xml文件的读取和标签的解析。