此篇博客篇幅较长,请耐心阅读,可能我文笔不太好,但是请仔细看博客中的代码的注释和说明,这样能帮助你理解。有什么问题可以回复。
直接进入正题,在使用Spring时,我们经常要进行大量的配置来告诉Spring,我们需要什么样的功能。但是配置文件又是怎么被Spring解析变成Spring所需要的东西呢。
Spring中有一个接口类,名叫BeanDefinition,配置文件中的<bean>标签中有着class,scope,而BeanDefinition提供了相应的beanClass,scope属性,BeanDefinition中的属性和<bean>中的属性是一一对应的。
抠张图来看看BeanDefinition的种类以及他们之间的关系
图1
在Spring解析配置文件时,配置文件中的信息会转换为BeanDefinition存在于内存中。
接下来我们用一个小例子来逐步的进行阅读源码
@Test
public void run(){
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService bean = (UserService) context.getBean("userServiceImpl");
bean.sayHello();
}
配置文件如下
<!--加载配置文件-->
<context:property-placeholder location="classpath:db.properties" />
<!--配置数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="10" />
</bean>
<!--SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:SqlMapConfig.xml"/>
<property name="typeAliasesPackage" value="demo"/>
<property name="mapperLocations" value="UserMapper.xml"/>
</bean>
<!--Mapper文件扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper"/>
</bean>
<context:component-scan base-package="service"/>
配置文件图2
接下来让我们Debug来逐步去看Spring是如何解析配置文件的。
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
//将传入的配置文件名作为属性赋值给成员变量
this.setConfigLocations(configLocations);
if (refresh) {
//Spring一切的开始
this.refresh();
}
}
进入到refresh方法中一探究竟
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
//准备刷新的上下文环境,如你只是使用Spring,你可以不用理会这个方法
this.prepareRefresh();
//OK,这个方法就是解析XML文件的方法,当这个方法执行完时,XML文件已经解析完了
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
//初始化剩下实例,也就是我们自己写的那些代码,Service,Controller等等
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
如果你要想看Spring解析配置文件的步骤,只需要看
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();这一行代码就足够了。让我们一起来挑一些重要的方法来看吧。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//初始化BeanFactory,并进行读取XML文件,并将XML内容转换为BeanDefintion对象存储
this.refreshBeanFactory();
//返回当前实体的beanFactory属性
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
在这个方法中,我们只能看到又调用了refreshBeanFactory方法,所以我们继续往下看
protected final void refreshBeanFactory() throws BeansException {
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
//创建DefaultListableBeanFactory对象
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
//初始化DocumentReader,准备解析XML文件
this.loadBeanDefinitions(beanFactory);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
可以看到,我们还是没有找到解析XML文件的地方,但是在这里注意两点,DefaultListableBeanFactory对象是整个bean加载的核心部分,是Spring注册及加载Bean的默认实现,也就是说,我们从XML读取到的信息转换为BeanDefintion之后都注册到这个对象之中。
接下来进入loadBeanDefinitions方法之后继续追寻
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//以beanFactory为参数创建XmlBeanDefinitionReader对象
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
//设置环境变量
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
//解析XML文件(怎么还没到解析XML,好烦啊)
this.loadBeanDefinitions(beanDefinitionReader);
}
进入loadBeanDefinitions(XmlBeanDefinitionReader reader)方法继续寻找
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//获取配置文件名
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
//好,离真正解析XML的方法又近了一步
reader.loadBeanDefinitions(configLocations);
}
}
还是没有看到XML的解析,不过我们又近了一步,不急嘛,肯定会到的。继续深入查看reader.loadBeanDefinitions(configLocations);
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 +&