所有文章已迁移至csdn,csdn个人主页https://blog.csdn.net/chaitoudaren
接下去将顺着流程图中6大转化过程,跟踪代码,逐一讲解
spring解析阶段.jpg
Xml文件 -> Resource
xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
- 创建ClassPathResource对象
// ClassPathResource.java
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
// 路径不允许为空
Assert.notNull(path, "Path must not be null");
// 规范路径
String pathToUse = StringUtils.cleanPath(path);
// 如果路径以/开头,去掉/
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
// 设置classLoader
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
- 从ClassPathResource获取InputStream
// ClassPathResource.java
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
// 使用类对象的getResourceAsStream
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
// 使用类加载器的getResourceAsStream
is = this.classLoader.getResourceAsStream(this.path);
}
else {
// 使用ClassLoader类的getSystemResourceAsStream
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
// 文件不存在
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
类加载器涉及面太宽,不是本专题的重点,有兴趣的可以参考下:
老大难的 Java ClassLoader
Resource -> InputStram
获取到Resource以后,继续跟踪代码
- 实例化XmlBeanFactory
// XmlBeanFactory.java
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
// XmlBeanFactory.java
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
// 核心逻辑,也是我们关注的重点
this.reader.loadBeanDefinitions(resource);
}
- 忽略指定接口的自动装配功能
在初始化XmlBeanFactory中,需要先初始化父类,而父类中有这么一段代码需要注意。spring各类感知器也就是Aware的使用可以参考:spring BeanPostProcessor 生命周期
// AbstractAutowireCapableBeanFactory
public AbstractAutowireCapableBeanFactory() {
super();
/**
* 自动装配时忽略给定的依赖接口
* 正常情况下,如果A类中有自动装配的属性B,则B如果未被创建则会被自动创建并自动装配到A当中
* 但是如果A实现了BeanFactoryAware,B是BeanFactory,则B不会被自动装配,而是通过调用重写的setBeanFactory方法进行注入
*/
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
- 初始化XmlBeanDefinitionReader对象
XmlBeanFactory读取Xml的工作并没有自己完成,而是委托给了自己的属性reader,因此,在初始化XmlBeanFactory时,我们需要初始化XmlBeanDefinitionReader
// XmlBeanFactory.java
// 委托读取xml对象
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
初始化XmlBeanDefinitionReader最主要的工作是设置当前的资源加载器以及当前的相关环境变量,不做深究,将重点放在核心逻辑上
// XmlBeanDefinitionReader.java
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
// 有可继承的环境,则直接使用
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
// 否则新建一个环境,包括系统环境属性,JVM系统环境属性等
this.environment = new StandardEnvironment();
}
}
- 对资源文件进行编码
// XmlBeanDefinitionReader.java
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 对资源进行编码
return loadBeanDefinitions(new EncodedResource(resource));
}
- 获取InputStream
获取InputStream无非就是调用resource的getInputStream方法,再进行编码操作。
值得注意的是currentResources这个对象,该对象用于处理资源相互循环引用的检测。在spring中大量使用了这种思想,包括最著名的循环依赖也是使用这种方法进行检测的,需要多加理解
// XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
/**
* currentResources保存着当前正在加载的资源,用于处理资源循环引用的问题
* 例如:A资源引入B,B又引入A资源。则加载A时会将B资源引入进来,于是currentResources中包含AB,
* 而B资源又要将A资源引入,此时currentResources已经包含A,顾添加失败,抛出异常
*/
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 资源正在加载,相互循环依赖,抛出异常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// 获取输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
// 尝试对输入流进行编码
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 核心逻辑
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
// 资源加载完成,删除
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
至此,Xml文件已经转化为InputStream,虽然关键代码只有几行,但是spring做了大量的工作,下一节将继续跟踪核心逻辑doLoadBeanDefinitions。在spring中有一个有趣规则,就是任何以do开头的函数才是真正的核心逻辑,因此以后我们看到do开头的函数,基本上算是熬出头了
网友评论