先上一段我们都很熟悉的代码:
此次我们研究一下上下文启动,Spring
到底做了什么,以ClassPathXmlApplicationContext
为例。
在开始我们的源码追踪旅行之前,我们先贴几张关系图(这对于我们后面理解很重要!!)
这些关系图只是一部分关系,我会不段更新
上下文部分关系图:
一、启动大体流程思路
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);//第一步,加载配置路径
if (refresh) {
refresh();//第二步
}
}
其中第二步细分为:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备上下文刷新
prepareRefresh();
// 告诉子类刷新bean工厂。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备bean工厂方便使用该上下文。
prepareBeanFactory(beanFactory);
try {
// 允许在上下文子类中对bean工厂进行后处理。
postProcessBeanFactory(beanFactory);
// 后置处理器处理bean
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截bean创建的bean处理器.
registerBeanPostProcessors(beanFactory);
// 初始化此上下文的消息源。
initMessageSource();
// 初始化此上下文的事件多播器。
initApplicationEventMulticaster();
// 在特定的上下文子类中初始化其他特殊的bean。
onRefresh();
// 检查监听器bean并注册它们。
registerListeners();
// 实例化所有剩下的(非惰性初始化)单例。
finishBeanFactoryInitialization(beanFactory);
// 发布相应的事件。
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
我们先来讲一下
①setConfigLocations(configLocations)
作用:设置配置路径
这个步骤是做了些什么:
1、ClassPathXmlApplicationContext
调用父类AbstractRefreshableConfigApplicationContext
的setConfigLocations(configLocations)
,代码如下:
public void setConfigLocations(String[] locations) {
//locations是我们加载的application.xml,这个可以有多个,是一个String[]
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;
}
}
resolvePath
是AbstractRefreshableConfigApplicationContext
的一个私有方法,这个方法我们需要注意的是,this.getEnvironment()
是该类在构造时自己new
出来的StandardEnvironment
(具体大家可以自己对照代码看,笔者这里尽可能提供详细路线)。StandardEnvironment
类继承于AbstractEnvironment
。
protected String resolvePath(String path) {
return this.getEnvironment().resolveRequiredPlaceholders(path);
}
接下来我们进入resolveRequiredPlaceholders
方法,这是AbstractEnvironment
的公有方法。在这个方法里,我们需要注意的是this.propertyResolver
。this.propertyResolver
是PropertySourcesPropertyResolver
类型。
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
AbstractEnvironment
抽象类的构造函数里对propertySources
进行了初始化,再通过资源配置器构造出propertyResolver
。
ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
public AbstractEnvironment() {
String name = this.getClass().getSimpleName();
if (this.logger.isDebugEnabled()) {
this.logger.debug(format("Initializing new %s", name));
}
//该方法是一个空实现的方法,我们这里的StandardEnvironment对它进行了实现。
this.customizePropertySources(this.propertySources);
if (this.logger.isDebugEnabled()) {
this.logger.debug(format(
"Initialized %s with PropertySources %s", name, this.propertySources));
}
}
那我们再来看看StandardEnvironment里到底对propertySources进行了哪些初始:
protected void customizePropertySources(MutablePropertySources propertySources) {
//systemProperties
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
//systemEnvironment
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
我们现在知道了propertyResolver
的获取,而resolveRequiredPlaceholders
内部其实就是创建一个占位符解析器PropertyPlaceholderHelper
(不清楚占位符解析器干嘛的,可以看我的上一篇文章),并进行占位符解析和置换。
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (strictHelper == null) {
strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, strictHelper);
}
这里注意下面这个方法的getProperty(placeholderName)
部分是由PropertySourcesPropertyResolver
实现PropertyResolver
接口而来的。而在这个方法里,getProperty
实际就是从PropertySources
里获取占位符对应的值。而PropertySources
该资源实际就是上面我们提到的StandardEnvironment
里面加载的2个系统资源。
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PlaceholderResolver() {
public String resolvePlaceholder(String placeholderName) {
return getProperty(placeholderName);
}
});
}
说的有点乱,我总结一下,顺便放个test,大家可能就清晰了。第一步setConfigLocations(configLocations)
说白了就是替换调path里的占位符。但是只能从系统属性和环境变量的配置文件中寻找占位符的替换。
例子如下:
package com.xxx.xxx.xxx.facade;
import com.alibaba.fastjson.JSONObject;
import com.xxx.xxx.xxx.service.identification.phone3item.ThreeItemVerifyService;
import com.xxx.xxx.xxx.service.lang.GenericService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.*;
import java.util.Properties;
import java.util.UUID;
public class TestSpring {
@Test
public void test() throws Exception{
/**
* 新增系统属性,方便测试
*/
System.setProperty("test","applicationContext");
System.getProperties().list(System.out);
System.out.println("我设置的test占位符替换值:"+System.getProperty("test"));
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("${test}.xml");
GenericService genericService = (ThreeItemVerifyService)applicationContext.getBean("threeItemVerifyService");
JSONObject json = new JSONObject();
json.put("service", "suning.sfeicuss.identification.phone.3item.verify");
json.put("version", "1.0");
json.put("outBizNo", UUID.randomUUID());
json.put("appCode", "xxx");
json.put("phone", "xxx");
json.put("idNo", "xxx");
json.put("name", "xxx");
genericService.execute(json);
}
}
控制台结果:
我们开始分析第二步
②prepareRefresh()
作用:准备此上下文进行刷新,设置其启动日期和活动标志以及执行属性资源的任何初始化。
protected void prepareRefresh() {
//启动时间
this.startupDate = System.currentTimeMillis();
//加锁,防止,加状态
synchronized (this.activeMonitor) {
this.active = true;
}
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// 在上下文环境中初始化任何占位符属性源
initPropertySources();
// 验证标记为必需的所有属性是可解析的
// 请参阅ConfigurablePropertyResolver#setRequiredProperties
this.environment.validateRequiredProperties();
}
这里我们需要注意的是initPropertySources
这个方法是AbstractApplicationContext
父类的一个空实现,ClassPathXmlApplicationContext
继承关系中也没人实现它,所以对于ClassPathXmlApplicationContext
来说就是空实现,不做任何操作。
③obtainFreshBeanFactory()
作用:初始化bean工厂
protected final void refreshBeanFactory() throws BeansException {
//查看是否已经存在bean工厂,存在则销毁bean,且关闭工厂
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建bean工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
//为序列化目的指定一个id,允许这个BeanFactory,从此id反序列化回BeanFactory对象。
beanFactory.setSerializationId(getId());
//设置bean工厂属性
customizeBeanFactory(beanFactory);
//加载定义的bean
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
createBeanFactory()
该方法内部是用给定的父项创建一个新的DefaultListableBeanFactory,过程中给最上层是给AbstractBeanFactory
初始化parentBeanFactory
值,该字段是父bean工厂,用于bean继承支持。具体不在详细展开,也很清晰。代码如下:
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
customizeBeanFactory()
方法设置了”是否允许bean相同名称不同定义”、”是否自动尝试解决bean之间的循环引用”,最后又”为此BeanFactory
设置一个自定义autowire
候选解析器以供使用当决定一个bean
定义是否应该被视为一个自动装配的候选人”,即声明了一个AutowireCandidateResolver
(自动注解解析器):用来判断定义的bean是否需要被自动注入。代码如下:
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
}
loadBeanDefinitions(beanFactory)
是ClassPathXmlApplicationContext
的父类AbstractXmlApplicationContext
里的一个方法,这个方法用来加载xml,过程挺复杂的,但是思路都差不多。这里简要地介绍一下。
这里以application.xml的部分为例子:
第一步,对location
操作
- 对路径进行解析,包括是不是以
classpath*:
开头等。 - 将
location
字符串生成resource
,如果是classpath:
为起始的location
生成ClassPathResource
,如果是url
类型的location
生成UrlResource
,如果是其他,生成ClassPathContextResource
。
在生成resource
的过程中,对path
进行了StringUtils.cleanPath
。
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//看前缀是不是classpath*:
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
//// 加载出该路径下的所有配置文件,并将File构造成Resource
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
return findPathMatchingResources(locationPattern);
}
else {
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
//如果不是classpath*:开头的话,
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// 仅仅是一个简单的resource加载
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
第二步:对resource
操作
- 通过
ClassPathResource
的getClassloader.getResourceAsStream
获取流,再放入sax
的InputSource
。 - 再将
InputSource
和ClassPathResource
进行解析。 DocumentBuilderFactory
解析inputSource
获取xml
的doc
。获取doc
的root
标签,看root
标签是否包含profile
。这里案例不包含。registerBeanDefinitions(doc, resource)
解析, 解析根节点import
、alias
、bean
、beans
这些标签, 比如,当发现import
后,接着发现resource
,resource
标签的值是classpath*:beans-*.xml
。
接下来将classpath*:
和beans-*.xml
切开,现将classpath*:
下面的所有File
加载放入File[]
中,classpath*==getclassLoader.getresourcepath()
,然后再将File[]
里的文件用getPathMatcher().match()
和beans-*.xml
做匹配,匹配成功的File
放入Set(File)
中,再将每个配置文件构建成FileSystemResource
,返回了一个Resource[]
,再递归循环解析Resource
里面的每一个classpath*:
下与beans-*.xml
匹配成功的xml
文件。
这个过程中,代码量有点多,这里稍微附几张关键图:
上面所说的,你们一定会说乱,没事,这里总结下写了一个加载的demo帮助我们理解,加载的过程。代码如下:
@Test
public void test02() throws Exception{
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
logger.debug("加载前的bean工厂:{}",beanFactory.getBeanDefinitionNames());
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
logger.debug("我们开始解析....applicationContext.xml");
ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver();
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources("applicationContext.xml");
logger.debug("resources:{}",resources);
//循环加载resources
for (Resource resources1:resources){
logger.debug("第一个resource进来解析了:{}",resources1);
EncodedResource encodedResource = new EncodedResource(resources1);
//获取xml资源的流
InputStream inputStream = encodedResource.getResource().getInputStream();
InputSource inputSource = new InputSource(inputStream);
//解析xml
Document doc = new DefaultDocumentLoader().loadDocument(
inputSource, new ResourceEntityResolver(resourceLoader), new SimpleSaxErrorHandler(LogFactory.getLog(getClass())), 3, false);
Element root = doc.getDocumentElement();
logger.debug("applicationContext.xml的根标签是:{}",root);
//加载resource
xmlBeanDefinitionReader.registerBeanDefinitions(doc,resources1);
}
logger.debug("加载后的bean工厂:{}", Arrays.toString(beanFactory.getBeanDefinitionNames()));
}
运行结果:
…………………………………中间日志省略…………………………………
到此篇幅已经很长啦,下次再接着写。