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

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

阅读本系列之前,建议先从本专栏的两个不同视角学习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
  1. public class Bean {
  2. }
  • 准备一个简单的配置文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="bean" class="org.deepSpring.Bean"/>
  6. </beans>
  • 准备一个简单的容器
  1. public class DeepSpringStudy {
  2. public static void main(String[] args) {
  3. ClassPathResource resource = new ClassPathResource("bean.xml");
  4. boolean exists = resource.exists();
  5. BeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
  6. Object bean = xmlBeanFactory.getBean("bean");
  7. System.out.println(bean);
  8. }
  9. }
  • 进行一波简单的测试

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

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

DefaultListableBeanFactory

  1. //该类已经过时了,不推荐使用
  2. @Deprecated
  3. @SuppressWarnings({"serial", "all"})
  4. public class XmlBeanFactory extends DefaultListableBeanFactory {
  5. //该方法就多了一个XmlBeanDefinitionReader
  6. private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
  7. public XmlBeanFactory(Resource resource) throws BeansException {
  8. this(resource, null);
  9. }
  10. public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
  11. super(parentBeanFactory);
  12. //通过这个XmlBeanDefinitionReader去解析配置文件,将解析后的信息放入内存中
  13. this.reader.loadBeanDefinitions(resource);
  14. }
  15. }

DefaultListableBeanFactory的继承体系

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

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

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

XmlBeanDefinitionReader

容器的基础XmlBeanFactory

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

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

xmlBeanFactory初始化时序图如下:

配置文件—>Resource

  1. public interface InputStreamSource {
  2. InputStream getInputStream() throws IOException;
  3. }

  1. public interface Resource extends InputStreamSource {
  2. boolean exists();
  3. default boolean isReadable() {
  4. return this.exists();
  5. }
  6. default boolean isOpen() {
  7. return false;
  8. }
  9. default boolean isFile() {
  10. return false;
  11. }
  12. URL getURL() throws IOException;
  13. URI getURI() throws IOException;
  14. File getFile() throws IOException;
  15. default ReadableByteChannel readableChannel() throws IOException {
  16. return Channels.newChannel(this.getInputStream());
  17. }
  18. long contentLength() throws IOException;
  19. long lastModified() throws IOException;
  20. Resource createRelative(String var1) throws IOException;
  21. @Nullable
  22. String getFilename();
  23. String getDescription();
  24. }

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

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

  1. public InputStream getInputStream() throws IOException {
  2. InputStream is;
  3. if (this.clazz != null) {
  4. //这里可以看出是从类路径下加载的资源
  5. is = this.clazz.getResourceAsStream(this.path);
  6. } else if (this.classLoader != null) {
  7. //这里也可以看出来
  8. is = this.classLoader.getResourceAsStream(this.path);
  9. } else {
  10. //也是从类路径下加载的资源
  11. is = ClassLoader.getSystemResourceAsStream(this.path);
  12. }
  13. if (is == null) {
  14. throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
  15. } else {
  16. return is;
  17. }
  18. }

在来看看FileSystemResource的方法源码:

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

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

进入源码追踪

XmlBeanFactory的构造函数

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

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

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

loadBeanDefinitions—加载Bean

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

  1. XmlBeanDefinitionReader类:
  2. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
  3. return loadBeanDefinitions(new EncodedResource(resource));
  4. }

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

  1. /**
  2. * Load bean definitions from the specified XML file.
  3. * @param encodedResource the resource descriptor for the XML file,
  4. * allowing to specify an encoding to use for parsing the file
  5. * @return the number of bean definitions found
  6. * @throws BeanDefinitionStoreException in case of loading or parsing errors
  7. */
  8. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  9. Assert.notNull(encodedResource, "EncodedResource must not be null");
  10. if (logger.isTraceEnabled()) {
  11. logger.trace("Loading XML bean definitions from " + encodedResource);
  12. }
  13. //通过属性记录已经加载的资源
  14. Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
  15. //将当前需要加载的资源填入集合中
  16. if (!currentResources.add(encodedResource)) {
  17. throw new BeanDefinitionStoreException(
  18. "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
  19. }
  20. //从 encodedResource中获取已经封装号的Resource对象,并再次从Resource中获取其中的inputstream
  21. try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
  22. //InputSource这个类不来自于spring,它的全路径是org.xml.sax.InputSource
  23. InputSource inputSource = new InputSource(inputStream);
  24. //如果设置了编码的话
  25. if (encodedResource.getEncoding() != null) {
  26. //就从encodedResource中取出设置好的编码
  27. inputSource.setEncoding(encodedResource.getEncoding());
  28. }
  29. //真正进入了核心逻辑
  30. return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  31. }
  32. catch (IOException ex) {
  33. throw new BeanDefinitionStoreException(
  34. "IOException parsing XML document from " + encodedResource.getResource(), ex);
  35. }
  36. finally {
  37. //从已加载结合中移除解析完毕的资源
  38. currentResources.remove(encodedResource);
  39. if (currentResources.isEmpty()) {
  40. this.resourcesCurrentlyBeingLoaded.remove();
  41. }
  42. }
  43. }

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

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

  1. /**
  2. * Actually load bean definitions from the specified XML file.
  3. * @param inputSource the SAX InputSource to read from
  4. * @param resource the resource descriptor for the XML file
  5. * @return the number of bean definitions found
  6. * @throws BeanDefinitionStoreException in case of loading or parsing errors
  7. * @see #doLoadDocument
  8. * @see #registerBeanDefinitions
  9. */
  10. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
  11. throws BeanDefinitionStoreException {
  12. try {
  13. //加载xml文件,获取对应的Document对象
  14. Document doc = doLoadDocument(inputSource, resource);
  15. //根据返回的Document注册bean的定义信息
  16. int count = registerBeanDefinitions(doc, resource);
  17. if (logger.isDebugEnabled()) {
  18. logger.debug("Loaded " + count + " bean definitions from " + resource);
  19. }
  20. return count;
  21. }....全都是catch--当然这里抛出的异常也非常重要,值得各位去查看,但是限于篇幅原因,这里就不贴出来了
  22. }
doLoadDocument将xml配置文件先解析为DOM树

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

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

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

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

registerBeanDefinitions—解析并注册BeanDefinitions

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

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

  1. public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
  2. super(registry);
  3. }

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

|
|
\ /

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

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

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

方法源码:

  1. /**
  2. * Parse the elements at the root level in the document:
  3. * "import", "alias", "bean".
  4. * @param root the DOM root element of the document
  5. */
  6. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  7. //判断是否是默认命令空间
  8. if (delegate.isDefaultNamespace(root)) {
  9. //获取当前beans标签下面的子标签
  10. NodeList nl = root.getChildNodes();
  11. for (int i = 0; i < nl.getLength(); i++) {
  12. Node node = nl.item(i);
  13. if (node instanceof Element) {
  14. //拿到子节点
  15. Element ele = (Element) node;
  16. //判断子节点是否是默认命令空间
  17. if (delegate.isDefaultNamespace(ele)) {
  18. parseDefaultElement(ele, delegate);
  19. }
  20. else {
  21. //说明子节点是用户自定义标签
  22. delegate.parseCustomElement(ele);
  23. }
  24. }
  25. }
  26. }
  27. else {
  28. //说明传入的根标签就是用户自定义的标签
  29. delegate.parseCustomElement(root);
  30. }
  31. }

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

相关文章