在这篇文章中,我将用第一人称的方式向你阐述spring4.2.1.RELEASE IOC部分的基本架构,你可以用如下的简单demo开启源码debug之旅
demo包含三个文件
User.java
public class User {
@Override
public String toString() {
return "hi, I am a user!";
}
}
user.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="ch0.User"/>
</beans>
UserTest.java
public class UserTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ch0/user.xml");
User user = context.getBean(User.class);
System.out.println(user);
}
}
UserTest.java
中通过ClassPathXmlApplicationContext 创建了一个上下文,xml文件在类路径下,然后通过getBean
的方式可以拿到该bean
我们直接进到new ClassPathXmlApplicationContext(“ch0/user.xml”); 看看我到底做了什么事情~_~
ClassPathXmlApplicationContext.java
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
调用
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
// 层层调用父类构造函数, 最终只是为了获取ResourcePatternResolver, 我需要这玩意是因为我
// 要将你传给我的配置文件("ch0/user.xml")这个参数转换为我内部可以处理的资源抽象 Resource
super(parent);
// 你可以不用告诉我非常精确的配置文件, 可以写占位符。在这个函数里面,我可以解析出完整的配置文件路径
setConfigLocations(configLocations);
// 基本都需要刷新的啦(refresh == true),在刷新上下文的时候,我会用你给我的配置文件完成几乎所有的事情哦
if (refresh) {
refresh();
}
}
上面的函数先调用super(parent)
层层往上调用,最终发现做了这么一件事情
AbstractApplicationContext.java
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
resourcePatternResolver
这从字面上就可以知道这家伙是做资源解析的用的呢,查看一下ResourcePatternResolver
的定义,发现重点只有这么一行
ResourcePatternResolver.java
Resource[] getResources(String locationPattern) throws IOException;
这里的 locationPattern
代表实际的资源路径(基本上就等同于xml文件路径啦),这家伙可以把一个资源路径转换为描述资源的抽象Resource
, 好了,初次看spring源码了解到这里就够了,关于 Resource
的深入里面后面会后专题分析!
继续下面一行
setConfigLocations(configLocations);
public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
这一句的作用就是将创建ApplicationContext传入进来的ch0/user.xml
转换为合法的location,将location里面类似${key}
的placeHolder 转换为实际的值
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
而转换过程中会通过getEnviroment()
拿到ConfigurableEnvironment
对象来进行占位符${}的替换,标准的Enviroment
对象为
StandardEnvironment.java
public class StandardEnvironment extends AbstractEnvironment {
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
可以看到在设置替换placeHolder的源的时候,最终会通过getProperties()
,和System.getenv()
来获取启动jvm的时候的环境变量和系统环境变量,也就是说,如果你传入的location里面有个${key}
占位,而jvm参数或者系统环境变量里面刚好有个变量叫做key
那么spring在解析该location的时候,会将该占位解析为对应的value
接下来,到了
if (refresh) {
refresh();
}
这一行可是我的重头戏啊,我的核心功能都在这个refresh()
方法里面搞定哦
在我们这个demo中,refresh
参数为true,所以,直接进入到refresh()
方法,激动人心的时刻终于到来,go!
AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 刷新工厂之前需要做一些准备工作的啦,就想你在运动之前要做一些准备运动一样哦
prepareRefresh();
// 我会告诉我的子类创造一个工厂,来把我需要创建bean的原料BeanDefinition准备好
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 原料准备好之后呢,我要声明一些特殊的依赖关系, 所谓依赖,就是我在创造一个bean A的时候,发现它里面有另外一个属性B
// 那么B就是A的依赖,我在创造A的时候,必须先把B创造好,特殊关系的依赖就是指我遇到B的类型,我该放弃呢,还是告诉他直接用
// 现成的(也就是不用再去创造B了)
prepareBeanFactory(beanFactory);
try {
// 这里没啥,就是留给子类做扩展的啦
postProcessBeanFactory(beanFactory);
// 到了这里,工厂已经准备好了,如果你之前告诉过我工厂准备好之后应该干什么事情,这边我就可以满足你的需求哦
// 不信,你去看看BeanFactoryPostProcessors接口是干嘛用的吧==
invokeBeanFactoryPostProcessors(beanFactory);
// 在创建一个bean的前后,我也留给你很多扩展,原理上和上面的工厂扩展差不多的哦
registerBeanPostProcessors(beanFactory);
// 就是处理一些国际化的操作啦,啊?什么是国际化,就是i18n啦,还不懂?你没救了
initMessageSource();
// 我的功能很丰富,除了可以给你创建bean,还可以有事件管理的功能哦,这里我就创建一个管理器(ApplicationEventMulticaster(),
// 用来注册事件(ApplicationEvent)
// 我会将这些事件广播给合适的监听者(ApplicationListener)那边哦
initApplicationEventMulticaster();
// 啥也不干,留给子类扩展啦
onRefresh();
// 前面不是事件管理器搞好了嘛,这边呢,就是把那些事件监听器给注册进来啦,这样来一个新的事件我就知道该发给谁啦
registerListeners();
// 如果某些bean告我我,他想在我工厂创建之初就想初始化(一般要是单件singleton并且lazy-init为false),那么我在这个函数会满足他
finishBeanFactoryInitialization(beanFactory);
// 终于刷新完了,我要开始发布事件了!
finishRefresh();
}
// 什么?刷新的时候报错了?oh my god,我需要做一些清理
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
// 我需要将我创建的bean销毁掉
destroyBeans();
// 我不再活跃