Spring读源码系列01---Spring核心类及关联性介绍

x33g5p2x  于2022-04-02 转载在 Spring  
字(11.1k)|赞(0)|评价(0)|浏览(242)

阅读本系列之前,建议先从本专栏的两个不同视角学习spring的系列作为入门学习点(这两个系列会持续更新),先大体理解spring的架构设计与精髓,然后再来阅读本系列,深入源码分析,而不再纸上谈兵。

从整体来学spring系列文章:

Spring复杂的BeanFactory继承体系该如何理解? ----上

Spring复杂的BeanFactory继承体系该如何理解? ----中

Spring复杂的BeanFactory继承体系该如何理解?—中下

Spring复杂的BeanFactory继承体系该如何理解?—下

Spring复杂的IOC容器之短小的注解篇

Spring繁华的AOP王国—第一讲

Spring繁华的AOP王国—第二讲

Spring繁华的AOP王国—第三讲

Spring繁华的AOP王国----第四讲

Spring繁华的AOP王国—第五讲之应用案例和扩展

该系列持续更新中…

独特视角学习spring系列文章:

不一样的视角来学习Spring源码之容器与Bean—上

不一样的视角来学习Spring源码之容器与Bean—下

不一样的视角来学习Spring源码之AOP—上

不一样的视角来学习Spring源码之AOP—中

不一样的视角来学习Spring源码之AOP—下

该系列持续更新中…

正式开始之前,还是说一下,本系列参考spring深度源码解析第一版书籍整理而来,这本书比较的老,但是我认为spring核心变化不大,还是可以学习一下的

Spring核心类

引子

  • 准备一个简单的bean
public class Bean {
}
  • 准备一个简单的配置文件
<?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 id="bean" class="org.deepSpring.Bean"/>
</beans>
  • 准备一个简单的容器
public class DeepSpringStudy {
    public static void main(String[] args) {
        ClassPathResource resource = new ClassPathResource("bean.xml");
        boolean exists = resource.exists();
        BeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
        Object bean = xmlBeanFactory.getBean("bean");
        System.out.println(bean);
    }
}
  • 进行一波简单的测试

上面的程序执行思路可以简化到上面这幅图描述的这样

  • reader负责读取配置文件相关信息,放在内存中
  • reflectionUtil负责读取放在内存中的信息,然后反射创建对象
  • 完成逻辑的串联工作

DefaultListableBeanFactory

//该类已经过时了,不推荐使用
@Deprecated
@SuppressWarnings({"serial", "all"})
public class XmlBeanFactory extends DefaultListableBeanFactory {
    //该方法就多了一个XmlBeanDefinitionReader 
	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		//通过这个XmlBeanDefinitionReader去解析配置文件,将解析后的信息放入内存中
		this.reader.loadBeanDefinitions(resource);
	}

}

DefaultListableBeanFactory的继承体系

这张图大家先至少看一遍把,然后根据名字去猜测每个类的作用是什么

大家主要看一下下面这张图,理解一下

为什么要设计成接口继承接口,这样和所有功能写在一个接口中不是一样的吗?
这样做的目的,是为了复用接口的功能,同样是符合接口的单一职责功能,是一种设计模式的思想,例如:如果后面我只想在BeanFactory底层接口的基础上进行扩展,那么就只需要继承顶层这个接口即可,不需要去实现其他与BeanFactory不相关的方法

XmlBeanDefinitionReader

容器的基础XmlBeanFactory

我们下面来研究一下这行代码:

BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));

xmlBeanFactory初始化时序图如下:

配置文件—>Resource

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}

public interface Resource extends InputStreamSource {
    boolean exists();

    default boolean isReadable() {
        return this.exists();
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();
}

当然这里对ClassPathResource等资源实现类的代码也都非常简单容易理解,可以一起看一下:

简单看一下ClassPathResource的getInputStream()获取资源输入流的方法源码:

public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
        //这里可以看出是从类路径下加载的资源
            is = this.clazz.getResourceAsStream(this.path);
        } else if (this.classLoader != null) {
        //这里也可以看出来
            is = this.classLoader.getResourceAsStream(this.path);
        } else {
        //也是从类路径下加载的资源
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }

        if (is == null) {
            throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
        } else {
            return is;
        }
    }

在来看看FileSystemResource的方法源码:

public InputStream getInputStream() throws IOException {
        try {
        //从文件系统中加载资源文件
            return Files.newInputStream(this.filePath);
        } catch (NoSuchFileException var2) {
            throw new FileNotFoundException(var2.getMessage());
        }
    }

更多实现细节,请自行翻阅源码查看

进入源码追踪

XmlBeanFactory的构造函数

public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}
	| |
	| |
   	\ /
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
	   //调用父类DefaultListableBeanFactory--->继续调用父类AbstractAutowireCapableBeanFactory的构造方法
		super(parentBeanFactory);
		//调用XmlBeanDefinitionReader的loadBeanDefinitions从配置文件中加载bean的定义信息
		this.reader.loadBeanDefinitions(resource);
	}

public AbstractAutowireCapableBeanFactory() {
		super();
		//这个比较重要---忽略给定接口的自动装配功能
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
		//这是spring3之后新增的代码---书上没讲,我也不清楚有啥用
		if (NativeDetector.inNativeImage()) {
		    //指定初始化策略为简单的初始化策略即反射创建对象
			this.instantiationStrategy = new SimpleInstantiationStrategy();
		}
		else {
		//指定初始化策略为cglib代理的策略
			this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
		}
	}

看不懂就先放放,因为这里我也有点迷糊

loadBeanDefinitions—加载Bean

下面对时序图的处理过程进行梳理和分析:

XmlBeanDefinitionReader类:
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

EncodedResource类主要负责对字符进行编码处理:
    public Reader getReader() throws IOException {
        if (this.charset != null) {
            return new InputStreamReader(this.resource.getInputStream(), this.charset);
        } else {
            return this.encoding != null ? new InputStreamReader(this.resource.getInputStream(), this.encoding) : new InputStreamReader(this.resource.getInputStream());
        }
    }

/**
	 * Load bean definitions from the specified XML file.
	 * @param encodedResource the resource descriptor for the XML file,
	 * allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	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);
		}
        //通过属性记录已经加载的资源
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

        //将当前需要加载的资源填入集合中
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
        
        //从 encodedResource中获取已经封装号的Resource对象,并再次从Resource中获取其中的inputstream
		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
		   //InputSource这个类不来自于spring,它的全路径是org.xml.sax.InputSource
			InputSource inputSource = new InputSource(inputStream);
			//如果设置了编码的话
			if (encodedResource.getEncoding() != null) {
			    //就从encodedResource中取出设置好的编码
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			//真正进入了核心逻辑
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
		//从已加载结合中移除解析完毕的资源
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

到这里为止,只是做了定位资源,指定文件编码格式两件事情,下面才是进入真正加载逻辑

doLoadBeanDefinitions—真正将bean的定义信息从xml配置文件解析出来的过程

/**
	 * Actually load bean definitions from the specified XML file.
	 * @param inputSource the SAX InputSource to read from
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 * @see #doLoadDocument
	 * @see #registerBeanDefinitions
	 */
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
		    //加载xml文件,获取对应的Document对象
			Document doc = doLoadDocument(inputSource, resource);
			//根据返回的Document注册bean的定义信息
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}....全都是catch--当然这里抛出的异常也非常重要,值得各位去查看,但是限于篇幅原因,这里就不贴出来了
	}
doLoadDocument将xml配置文件先解析为DOM树

这个过程书上详细讲了一下,但是这里我不打算作为重点展开,大概贴一下思路吧:

  • 拿到XML文档的类型,是DTD还是XSD

如果不清楚啥是DTD和XSD可以自行了解一下,这里感兴趣可以去自己翻阅源码看一下大概的思路

  • 使用XML解析器对xml文档进行解析,这里感兴趣的小伙伴可以自行去了解一下解析过程,不感星球的小伙伴,只需要知道这里会读取xml文档,并按照xml解析方法将xml文件解析映射到Document对象上

registerBeanDefinitions—解析并注册BeanDefinitions

XmlBeanDefinitionReader类:
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	   //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader---如果你忘了,请回看上面的继承图 
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		//在实例化BeanDefintionReader的时候会将BeanDefinitionRegistry传入,默认使用继承至DefaultListableBeanFactory的子类
		//记录统计前BeanDefintion的加载个数
		int countBefore = getRegistry().getBeanDefinitionCount();
		//加载及注册Bean---重点
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//记录本次加载的BeanDefintion个数
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

在实例化BeanDefintionReader的时候会将BeanDefinitionRegistry传入

public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
		super(registry);
	}

BeanDefinitionDocumentReader#registerBeanDefinitions方法----吊胃口中
//加载及注册Bean---重点---传入的是解析得到的DOM树,还有一个上下文环境
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

|
|
\ /

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		//保存上下文环境
		this.readerContext = readerContext;
		//重点--真正干活的地方
		//doc.getDocumentElement()传入提取出来的root标签---这里是beans标签
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}
BeanDefinitionDocumentReader#doRegisterBeanDefinitions—准备一下,然后虚晃一枪

//传入的Element是根元素beans
	protected void doRegisterBeanDefinitions(Element root) {
	    //创建Bean定义解析器委托对象--由它完成bean定义解析工作
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
        
        //是否是默认命令空间
		if (this.delegate.isDefaultNamespace(root)) {
		   //如果标签上面标注了当前标签在测试,生产获取其他环境下才会生效,那么这里在解析前会进行一波判断
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			//是否定义了profile属性
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				//判断属性值与当前激活环境是否相符合---如果不符合就不进行解析
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
        
        //模板方法模式
        //解析前进行处理  
		preProcessXml(root);
		//真正进行解析---传入beans标签和负责解析的委托对象
		parseBeanDefinitions(root, this.delegate);
		//解析后进行处理
		postProcessXml(root);

		this.delegate = parent;
	}

BeanDefinitionDocumentReader#parseBeanDefinitions—开始解析
//真正进行解析---传入beans标签和负责解析的委托对象
		parseBeanDefinitions(root, this.delegate);

方法源码:

/**
	 * Parse the elements at the root level in the document:
	 * "import", "alias", "bean".
	 * @param root the DOM root element of the document
	 */
	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	    //判断是否是默认命令空间
		if (delegate.isDefaultNamespace(root)) {
		//获取当前beans标签下面的子标签
			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);
		}
	}

下一节将会讲解自定义标签解析的过程

相关文章