当前位置:   article > 正文

Spring框架源码(一) 如何加载并解析spring.xml配置文件?

spring.xml

目录

XmlBeanFactory

XmlBeanDefinitionReader

DefaultBeanDefinitionDoucmentReader 

doRegisterBeanDefinitions 

BeanDefinitionParserDelegate

BeanDefinitionHolder

DefaultListableBeanFactory


        早期我们使用Spring框架做开发时,经常会用到xml去配置bean,这些bean首先在xml文件里配置好,然后由Spring管理初始化,我们就可以拿来使用,那Spring框架是如何加载xml里的bean? 本篇文章将解析xml中的bean注册原理。

        首先我们可以新建一个maven project,引入bean 的jar包,spring的版本就用5.3.14

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-beans</artifactId>
  4. <version>${spring.version}</version>
  5. </dependency>

如果使用ClassPathXmlApplicationContext,那么需要引入spring-context依赖: 

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>${spring.version}</version>
  5. </dependency>

        采用xml的方式配置一个bean, 定义的xml文件中需要包含beans的命名空间声明,其中 "http://www.springframework.org/schema/beans" 是bean默认的命名空间地址,一定不能缺少,否则在getBean的时候会出现报错没有声明beans的问题, Spring-beans.xml文件配置如下:

  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="UserBean" class="com.example.User"></bean>
  6. </beans>

        

新建一个User类:

  1. package com.example;
  2. public class User {
  3. public void read(){
  4. System.out.println("学习..");
  5. }
  6. }

由于User类已经在xml文件中配置,因此我们可以拿到User这个bean了, 可以使用的XmlBeanFactory来获取bean, 但是现在已经过时, 不推荐使用, XmlBeanFactory继承了DefaultListableBeanFactory类。

  1. package com.example;
  2. import org.springframework.beans.factory.xml.XmlBeanFactory;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. import org.springframework.core.io.ClassPathResource;
  7. public class Main {
  8. public static void main(String[] args) {
  9. /*============================================读取xml形式========================================================*/
  10. // 方式一: 采用XmlBeanFactory加载xml配置,不推荐已过时
  11. XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-beans.xml"));
  12. User user = beanFactory.getBean("UserBean", User.class);
  13. user.read();
  14. // 方式二: 采用XmlClassPathApplicationContext加载
  15. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml");
  16. User user1 = applicationContext.getBean("UserBean", User.class);
  17. user1.read();
  18. // /*============================================无需xml,注解形式========================================================*/
  19. // // 方式三: 不采用xml形式,采用扫描注解的形式来注册bean, 需要注册配置类BeanConfig
  20. // ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
  21. // User user2 = annotationConfigApplicationContext.getBean("getUser", User.class);
  22. // user2.read();
  23. //
  24. // // 方式四, 配置扫描包,需要给配置类加上注解@configuration
  25. // ApplicationContext scanApplicationContext = new AnnotationConfigApplicationContext("com.example");
  26. // User user3 = scanApplicationContext.getBean("getUser", User.class);
  27. // user3.read();
  28. }
  29. }

执行查看打印结果:

学习.. 

学习.. 

由结果可知,能拿到User类的对象,说明Spring容器启动成功,我们只需要用getBean方法就能拿到指定的bean, 其实这二行代码内部做了非常多的事情,总结下来就是两件事情:

1. 初始化bean工厂,注册bean。

2. 从bean工厂里拿到bean。

两件事情看似简单,实际上包含了很多复杂的逻辑,本篇文章主要以研究第一步看bean工厂是怎么初始化的,而XmlBeanFactory的构造方法里包含了Spring注册bean的所有流程。

XmlBeanFactory

        XmlBeanFactory包含了一个final属性XmlBeanDefinitionReader, 该属性用来加载xml文件中配置的所有的bean,其中最核心的方法是this.reader.loadBeanDefinitions(resource), 其实这一行代码里包含了bean注册到Spring里的所有流程。

  1. @Deprecated
  2. public class XmlBeanFactory extends DefaultListableBeanFactory {
  3. private final XmlBeanDefinitionReader reader;
  4. public XmlBeanFactory(Resource resource) throws BeansException {
  5. this(resource, (BeanFactory)null);
  6. }
  7. public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
  8. super(parentBeanFactory);
  9. // 初始化XmlBeanDefinitionReader
  10. this.reader = new XmlBeanDefinitionReader(this);
  11. // 注册bean
  12. this.reader.loadBeanDefinitions(resource);
  13. }
  14. }

        我们可以看到这里调用了super(parentBeanFactory) ,因为XmlBeanFactory是BeanFactory的子类,因此需要先初始化父类的构造函数,我们可以在DefaultListableBeanFactory的构造方法里看到parentBeanFactory是允许为null的。

  1. public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
  2. super(parentBeanFactory);
  3. this.autowireCandidateResolver = SimpleAutowireCandidateResolver.INSTANCE;
  4. this.resolvableDependencies = new ConcurrentHashMap(16);
  5. // 存放bean的map
  6. this.beanDefinitionMap = new ConcurrentHashMap(256);
  7. this.mergedBeanDefinitionHolders = new ConcurrentHashMap(256);
  8. this.allBeanNamesByType = new ConcurrentHashMap(64);
  9. this.singletonBeanNamesByType = new ConcurrentHashMap(64);
  10. this.beanDefinitionNames = new ArrayList(256);
  11. this.manualSingletonNames = new LinkedHashSet(16);
  12. }

然后DefaultListableBeanFactory执行了一系列的初始化操作,为注册bean做准备工作, 其中包含初始化beanDefinition容器, 我们配置的所有bean最后都是以BeanDefinition的形式存放到该map里。

    private final Map<String, BeanDefinition> beanDefinitionMap;

XmlBeanDefinitionReader

 接着看XmlBeanDefinitionReader类里的LoadBeanDefinitions方法做了啥事:

  1. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
  2. return this.loadBeanDefinitions(new EncodedResource(resource));
  3. }

使用EncodedResource封装了resource对象,该对象时对resouce对象指定编码生成一个encodedResouce对象,doLoadBeanDefinitions()方法执行bean注册。

  1. /**
  2. * Load bean definitions from the specified XML file.
  3. *
  4. * @param encodedResource the resource descriptor for the XML file,
  5. * allowing to specify an encoding to use for parsing the file
  6. * @return the number of bean definitions found
  7. * @throws BeanDefinitionStoreException in case of loading or parsing errors
  8. */
  9. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  10. Assert.notNull(encodedResource, "EncodedResource must not be null");
  11. if (logger.isInfoEnabled()) {
  12. logger.info("Loading XML bean definitions from " + encodedResource.getResource());
  13. }
  14. // 将已经加载的资源给获取出来, resourcesCurrentlyBeingLoaded是一个ThreadLocal,封装了set集合
  15. Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
  16. if (currentResources == null) {
  17. currentResources = new HashSet<>(4);
  18. this.resourcesCurrentlyBeingLoaded.set(currentResources);
  19. }
  20. // 如果已经存在,那么就不能重新导入
  21. if (!currentResources.add(encodedResource)) {
  22. throw new BeanDefinitionStoreException(
  23. "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
  24. }
  25. try {
  26. InputStream inputStream = encodedResource.getResource().getInputStream();
  27. try {
  28. InputSource inputSource = new InputSource(inputStream);
  29. if (encodedResource.getEncoding() != null) {
  30. inputSource.setEncoding(encodedResource.getEncoding());
  31. }
  32. // doLoadBeanDefinitions方法是正式注册bean的逻辑
  33. return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  34. } finally {
  35. inputStream.close();
  36. }
  37. } catch (IOException ex) {
  38. throw new BeanDefinitionStoreException(
  39. "IOException parsing XML document from " + encodedResource.getResource(), ex);
  40. } finally {
  41. currentResources.remove(encodedResource);
  42. if (currentResources.isEmpty()) {
  43. this.resourcesCurrentlyBeingLoaded.remove();
  44. }
  45. }
  46. }

 doLoadBeanDefinitions()方法中主要包含2个步骤,第一步根据inputSource和resource获取到一个Document对象,我们知道xml文档可以解析成一个document树,其中最外层标签就是root元素,子标签就是一个个的叶子node,具体的解析成Document对象的过程不用过于纠结,Spring提供了详细实现, 第二步就是讲document对象注册到Spring容器里,而resouce参数用来选择XmlReaderContext

  1. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
  2. throws BeanDefinitionStoreException {
  3. try {
  4. // 将InputSource和编码后的resource解析为document对象。
  5. Document doc = doLoadDocument(inputSource, resource);
  6. // 注册bean
  7. return registerBeanDefinitions(doc, resource);
  8. } catch (BeanDefinitionStoreException ex) {
  9. throw ex;
  10. } catch (SAXParseException ex) {
  11. throw new XmlBeanDefinitionStoreException(resource.getDescription(),
  12. "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
  13. } catch (SAXException ex) {
  14. throw new XmlBeanDefinitionStoreException(resource.getDescription(),
  15. "XML document from " + resource + " is invalid", ex);
  16. } catch (ParserConfigurationException ex) {
  17. throw new BeanDefinitionStoreException(resource.getDescription(),
  18. "Parser configuration exception parsing XML from " + resource, ex);
  19. } catch (IOException ex) {
  20. throw new BeanDefinitionStoreException(resource.getDescription(),
  21. "IOException parsing XML document from " + resource, ex);
  22. } catch (Throwable ex) {
  23. throw new BeanDefinitionStoreException(resource.getDescription(),
  24. "Unexpected exception parsing XML document from " + resource, ex);
  25. }
  26. }

 接着看registerBeanDefinitions(doc, resource)方法:

  1. public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  2. // 获取到解析document对象里的beanDefinition的工具reader
  3. BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  4. int countBefore = getRegistry().getBeanDefinitionCount();
  5. // 注册bean
  6. documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  7. // 返回注册的个数
  8. return getRegistry().getBeanDefinitionCount() - countBefore;
  9. }

此方法主要做了3件事:

  1. 获取到默认的文档解析器BeanDefinitionDocumentReader。
  2. registerBeanDefinitions(Document doc, XmlReaderContext readerContext),读取Document对象里的bean, 并实现注册。
  3. 返回注册成功的bean个数。

我们接着看BeanDefinitionDocumentReader接口里的registerBeanDefinitions(Document doc,XmlReaderContext context)方法的实现。

DefaultBeanDefinitionDoucmentReader 

        DefaultBeanDefinitionDoucmentReader是BeanDefinitionDocumentReader接口的一个默认实现,简单地讲就是用来解析Document对象里的beanDefinition,  其中regsiterBeanDefinitions(Document doc, XmlReaderContext readerContext)方法是注册bean的入口方法: 

  1. @Override
  2. public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
  3. this.readerContext = readerContext;
  4. logger.debug("Loading bean definitions");
  5. Element root = doc.getDocumentElement();
  6. doRegisterBeanDefinitions(root);
  7. }

doRegisterBeanDefinitions 

  1. protected void doRegisterBeanDefinitions(Element root) {
  2. // Any nested <beans> elements will cause recursion in this method. In
  3. // order to propagate and preserve <beans> default-* attributes correctly,
  4. // keep track of the current (parent) delegate, which may be null. Create
  5. // the new (child) delegate with a reference to the parent for fallback purposes,
  6. // then ultimately reset this.delegate back to its original (parent) reference.
  7. // this behavior emulates a stack of delegates without actually necessitating one.
  8. BeanDefinitionParserDelegate parent = this.delegate;
  9. this.delegate = createDelegate(getReaderContext(), root, parent);
  10. // 如果指定了profile,那么校验
  11. if (this.delegate.isDefaultNamespace(root)) {
  12. String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
  13. if (StringUtils.hasText(profileSpec)) {
  14. String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
  15. profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  16. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
  17. if (logger.isInfoEnabled()) {
  18. logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
  19. "] not matching: " + getReaderContext().getResource());
  20. }
  21. return;
  22. }
  23. }
  24. }
  25. // 准备解析
  26. preProcessXml(root);
  27. // 解析beanDefinition
  28. parseBeanDefinitions(root, this.delegate);
  29. // 解析完后
  30. postProcessXml(root);
  31. this.delegate = parent;
  32. }

看到了这里,我们就可以发现Spring解析xml中的bean方法和原理,然后该类定义了一个常量

	public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;

BeanDefinitionParserDelegate

        BeanDefinitionParseDelegate类里定义很多xml文档里用到的属性,例如bean标签, scope属性,lazy-init等,只要是在bean中用到的属性在BeanDefinitionParseDeletgae类里就有定义, 后续解析的时候会用到该类的定义的属性进行比较,每一个属性对应地做不同的事情:

        进入到parseBeanDefinitions方法里后,可以发现deletegate支持两种解析方式,一种是默认的namespace的解析,另外一种是自定义的命名空间方式,两者都是在xml文件的标签头部声明的,默认用到的我们可以在xml文件里看到的http://www.springframework.org/schema/beans。

  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  2. if (delegate.isDefaultNamespace(root)) {
  3. NodeList nl = root.getChildNodes();
  4. for (int i = 0; i < nl.getLength(); i++) {
  5. Node node = nl.item(i);
  6. if (node instanceof Element) {
  7. Element ele = (Element) node;
  8. if (delegate.isDefaultNamespace(ele)) {
  9. parseDefaultElement(ele, delegate);
  10. }
  11. else {
  12. delegate.parseCustomElement(ele);
  13. }
  14. }
  15. }
  16. }
  17. else {
  18. delegate.parseCustomElement(root);
  19. }
  20. }

所以默认会走defaultNameSpace的条件分支,进入到parseDefaultElement方法后,找到 processBeanDefinition方法:

  1. private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  2. // 如果是import形式的注解
  3. if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
  4. importBeanDefinitionResource(ele);
  5. }
  6. // 如果给了alias,那么此处需要和将node和alias一起注册
  7. else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
  8. processAliasRegistration(ele);
  9. }
  10. else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
  11. // 解析beanDefinition
  12. processBeanDefinition(ele, delegate);
  13. }
  14. else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
  15. // recurse
  16. doRegisterBeanDefinitions(ele);
  17. }
  18. }
  1. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  2. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  3. if (bdHolder != null) {
  4. bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  5. try {
  6. // Register the final decorated instance.
  7. // 注册bean
  8. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  9. }
  10. catch (BeanDefinitionStoreException ex) {
  11. getReaderContext().error("Failed to register bean definition with name '" +
  12. bdHolder.getBeanName() + "'", ele, ex);
  13. }
  14. // Send registration event.
  15. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  16. }
  17. }

BeanDefinitionHolder

        delegate最终把解析到的一个个的bean对象放在BeanDefinitionHolder对象里,BeanDefinitionHolder可以看到我们梦寐以求的beanDefinition属性,这也是最终需要放在beanDefinitionMap里的属性。

         可以在BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()找到注册bean的逻辑:

  1. public static void registerBeanDefinition(
  2. BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
  3. throws BeanDefinitionStoreException {
  4. // Register bean definition under primary name.
  5. // 拿到bean的名称
  6. String beanName = definitionHolder.getBeanName();
  7. // 使用BeanDefinitionRegistry注册beanDefinition
  8. registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
  9. // 如果含有别名,那么也要注册bean的别名
  10. // Register aliases for bean name, if any.
  11. String[] aliases = definitionHolder.getAliases();
  12. if (aliases != null) {
  13. for (String alias : aliases) {
  14. registry.registerAlias(beanName, alias);
  15. }
  16. }
  17. }

        看到这里基本可以看清XmlBeanFactory的整个注册Bean逻辑的庐山真面目, 而注册逻辑的实现是在DefaultListableBeanFactory里。

DefaultListableBeanFactory

        DefaultListableBeanFactory实现了BeanDefinitionRegistry接口的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法, 其中包含了所有的注册逻辑。

         最终将beanName和beanDefinition放入到beanDefinitionMap里, 为后续getBean()方法做流程上的准备。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/309190
推荐阅读
相关标签
  

闽ICP备14008679号