基于Spring相关的配置信息,使用IntelliJ IDEA 工具创建一个Spring工程,在使用得pom.xml中仅仅需要增加依赖为spring-context的依赖包即可,打开依赖关系图可以看到在spring-context包中集成了spring-aop、spring-beans、spring-core、spring-expression这四个主要的包。依赖信息如下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
在操作项目时搭配日志系统进行直观的了解其操作和启动相关的类信息情况。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.10</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
对于spring加载配置信息的方式,传统的使用的是通过加载spring.xml方式进行。在加载配置文件得方式上有以下几种:
//相对路径加载配置文件
new ClassPathXmlApplicationContext("spring.xml");
//绝对路径加载配置文件
new FileSystemXmlApplicationContext("E:\XXXX\spring.xml");
//无配置方式加载
new AnnotationConfigApplicationContext("com.xx.jack");
//以spring boot容器加载方式
new EmbeddedWebApplicationContext();
通过ClassPathXmlApplicationContext方式加载对应的xml文件。
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
//解析的相关方法在refresh方法。重点方法
refresh();
}
}
接着调用org.springframework.context.support.AbstractApplicationContext类中的。AbstractApplicationContext类中的refresh()方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//准备容器相关工作
prepareRefresh();
//将对应的下xml解析封装为Beandefinition
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
}
}
重点关注的是ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()方法;点击进入以后发现调用的是类AbstractApplicationContext中ConfigurableListableBeanFactory obtainFreshBeanFactory()方法,该方法的的源码如下:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//调用的核心方法,重点关注
refreshBeanFactory();
//将注入到Beanfactory类返回
return getBeanFactory();
}
点进去追踪发现使用的是AbstractApplicationContext抽象类,跳转至其子类中查看调用的逻辑,见AbstractRefreshableApplicationContext类。
AbstractRefreshableApplicationContext.java
@Override
protected final void refreshBeanFactory() throws BeansException {
//如果BeanFactory不为空
if (hasBeanFactory()) {
//销毁Bean
destroyBeans();
//关闭BeanFactory
closeBeanFactory();
}
try {
//BeanFactory 实例工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//设置是否可以循环依赖 allowCircularReferences
//是否允许使用相同名称重新注册不同的bean实现.
customizeBeanFactory(beanFactory);
//解析xml,并把xml中的标签封装成BeanDefinition对象;【重点方法】
loadBeanDefinitions(beanFactory);
//并将已经解析实例化的BeanFactory设置为当前环境的BeanFactory
this.beanFactory = beanFactory;
} catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
加载的机制逻辑处理是将在spring.xml配置的标签实例化为对应的bean示例。经过AbstractRefreshableApplicationContext跳转到对应的子实现AbstractXmlApplicationContext类中。在其中走的具体逻辑如下:
AbstractXmlApplicationContext.java
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//基于委托模式创建一个xml的解析器
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
//这里传一个this进去,因为ApplicationContext是实现了ResourceLoader接口的
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
//关键代码逻辑,【重点方法】
loadBeanDefinitions(beanDefinitionReader);
}
AbstractXmlApplicationContext.java中的方法loadBeanDefinitions(XmlBeanDefinitionReader reader);主要是将需要获取的xml文件通过流的方式加载进来
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//获取需要加载的xml配置文件 主要是sprin.xml字符串信息
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//重点解析xml方法 通过将对应的节点信息加载到对应的BeanDefinition中
reader.loadBeanDefinitions(configLocations);
}
}
该方法的功能是将对应的xml文件各种bean所持有的属性注入到BeanDefinitions对象中。
AbstractBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
for (String location : locations) {
//将对应的文件加载注入到BeanDefinitions对象中,【重点方法】
//将对应的Beans下的节点注入到相关的BeanDefinition中,为实现实例化做加载准备
count += loadBeanDefinitions(location);
}
return count;
}
将传入的文件加载到对应的对象中,实现示例化过程。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//把字符串类型的xml文件路径,形如:classpath*:user/**/*-context.xml,转换成Resource对象类型;本质是用流处理加载配置文件,封装为Resource对象,了解即可
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//主要看这个方法 ** 【重要方法】
int count = loadBeanDefinitions(resources);
...
}
}
将对应的spring.xml加载,其中获取得location即为对应得spring.xml的路径。
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
处理加载的配置文件AbstractBeanDefinitionReader.java。按照流文件方式将对应的配置文件加载并解析,最后包装为Beandefinition对象。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.将获取到的spring.xml配置文件加载后解析
try {
//把字符串类型的xml文件路径,形如:classpath*:user/**/*-context.xml,转换成Resource对象类型,其实就是用流的方式加载配置文件,然后封装成Resource对象,不重要,可以不看
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//【重要方法】不断加载和解析对应的节点信息,并将其设置到对应的BeanDefinition对象中
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
...return count;
}catch (IOException ex) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);
}
} else {
// 加载写了绝对路径的单个的配置文件,如 D:/aa/**/XXX.xml.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
AbstractBeanDefinitionReader.java类中使用的加载方法
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
//模板设计模式,调用到子类中的方法
count += loadBeanDefinitions(resource);
}
return count;
}
在该方法中使用模板设计模式方法调用子类的方法
BeanDefinitionReader.java模板设计模式方法调用子类实现类
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
该模板方法有多个子类实现
在实现过程中我们需要关注的是,本次加载的方式使用的是基于xml配置加载。故选择XmlBeanDefinitionReader.java方法及逆行追踪。
XmlBeanDefinitionReader.java。这里面做的处理是将加载进来的配置文件流数据经过编码读取,纠正可能会出现的乱码情况。
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//EncodedResource带编码的解析Resource对象并进行封装
return loadBeanDefinitions(new EncodedResource(resource));
}
调用带有编码格式的文件信息,将其转换为输入流文件获取xml记录信息,并解析其节点信息并封装
XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
...//获取Resource对象中的xml文件流对象
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
//InputSource是jdk中的sax xml文件解析对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//主要看这个方法 ** 重要程度 5
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
...
}
Spring源码中提供的代码解析使用的是对配置文件的解析,即对spring.xml配置的解析。
处理解析Document对象。XmlBeanDefinitionReader.java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
//调用JDK的API将输入的流数据文件封装成Document文件对象
Document doc = doLoadDocument(inputSource, resource);
//根据解析出来的document对象,解析对应的标签元素并封装成BeanDefinition【重点方法】
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}catch (BeanDefinitionStoreException ex) {
throw ex;
}
...
}
XmlBeanDefinitionReader.java总的registerBeanDefinitions()方法。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//又来一记委托模式,BeanDefinitionDocumentReader委托这个类进行document的解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//createReaderContext(resource) XmlReaderContext上下文,封装了XmlBeanDefinitionReader对象【重点方法】
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
DefaultBeanDefinitionDocumentReader.java将对应的document的对象传递,做解析。
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//把root节点传进去【重点方法】
doRegisterBeanDefinitions(doc.getDocumentElement());
}
解析DefaultBeanDefinitionDocumentReader.java
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
...
preProcessXml(root);
//解析Document对象的标签【重点方法】
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
解析DefaultBeanDefinitionDocumentReader.java。主要是对默认标签的解析和对自定义标签的解析内容。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
//默认标签解析
parseDefaultElement(ele, delegate);
}else {
//自定义标签解析
delegate.parseCustomElement(ele);
}
}
}
}else {
delegate.parseCustomElement(root);
}
}
先来看一下对默认的标签的解析。主要的默认标签的内容有:bean标签、alias标签、import标签、beans标签。我们关注的重点是在bean标签上。
自定义标签有:
一探究竟,对bean的解析具体做了哪些事。
解析DefaultBeanDefinitionDocumentReader.java对默认标签的bean的解析流程处理
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//import标签解析,了解即可。
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
...//bean标签,【重点方法】
}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
}
解析DefaultBeanDefinitionDocumentReader.java对bean标签内容的解析步骤:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//重点看这个方法,重要程度 5 ,解析document,封装成BeanDefinition
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//设计模式:装饰者设计模式,SPI设计思想
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//完成document到BeanDefinition对象转换后,对BeanDefinition对象进行缓存注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
...
}
}
BeanDefinitionParserDelegate.java对标签做解析操作。
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
//解析传入的标签下的内容
return parseBeanDefinitionElement(ele, null);
}
解析BeanDefinitionParserDelegate.java中的对bean标签的解析。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
...//检查beanName是否重复
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//<bean>标签解析的核心方法,重要程度5
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
...return null;
}
解析BeanDefinitionParserDelegate.java。对bean标签中的meta、lookup-method、replaced-method、constructor-arg、property这几个标签的解析。
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
...try {
//创建GenericBeanDefinition对象
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//解析bean标签的属性,并把解析出来的属性设置到BeanDefinition对象中
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析bean中的meta标签
parseMetaElements(ele, bd);
//解析bean中的lookup-method标签 重要程度:2,可看可不看
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析bean中的replaced-method标签 重要程度:2,可看可不看
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析bean中的constructor-arg标签 重要程度:2,可看可不看
parseConstructorArgElements(ele, bd);
//解析bean中的property标签 重要程度:2,可看可不看
parsePropertyElements(ele, bd);
//可以不看,用不到
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
...return null;
}
对应的懒加载标签等等都在方法parseBeanDefinitionAttributes中做的解析和操作。
以上就是解析配置文件的详细步骤,最后返回AbstractBeanDefinition的对象,从而完成对整个Spring.xml的加载解析工作。