zoukankan      html  css  js  c++  java
  • spring源码阅读(2)-- 容器启动之加载BeanDefinition

      在《spring源码阅读(1)-- 容器启动之资源定位》一文中,阅读了spring是怎么根据用户指定的配置加载资源,当加载完资源,接下来便是把从资源中加载BeanDefinition。

      BeanDefinition作为spring其中一个组件,spring是这样描述BeanDefinition的:BeanDefinition描述了一个bean实例,它具有属性值,构造函数参数值以及具体实现提供的更多信息。个人的理解是BeanDefinition保存了一个bean实例的所有元数据,下面列举一些常用的BeanDefinition属性,更多属性可以通过查看spring-beans.xsd了解

        name:bean实例的别买,一个bean实例可以拥有多个别名

        class:bean实例的class,如果作为一个父bean可以为空

        parent:父bean的名称

        scope:声明bean实例是单例还是原型的,默认单例

        lazy-init:是否延迟加载,当是一个单例bean是,默认值是false

        init-method:设置完属性时调用的初始化方法

        destroy-method:在bean工厂关闭时调用

      项目沿用《spring源码阅读(1)-- 容器启动之资源定位》一文的,这里就不贴工程相关的配置文件,重点贴一下spring的配置文件

     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="
     5     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     6     ">
     7 
     8     <bean id="springtest" class="com.zksite.spring.test.SpringBeanTest" />
     9 
    10 </beans>

      通过阅读上文,BeanDefinition的加载是由BeanDefinitionReader组件负责,而具体的实现是XmlBeanDefinitionReader。BeanDefinition的加载是由BeanDefinitionReader从Resuouce中去完成的,下面是XmlBeanDefinitionReader.loadBeanDefinitions(EncodedResource encodedResource)方法

     1 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
     2         Assert.notNull(encodedResource, "EncodedResource must not be null");
     3         if (logger.isInfoEnabled()) {
     4             logger.info("Loading XML bean definitions from " + encodedResource.getResource());
     5         }
     6 
     7         Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
     8         if (currentResources == null) {
     9             currentResources = new HashSet<EncodedResource>(4);
    10             this.resourcesCurrentlyBeingLoaded.set(currentResources);
    11         }
    12         if (!currentResources.add(encodedResource)) {
    13             throw new BeanDefinitionStoreException(
    14                     "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    15         }
    16         try {
    17             InputStream inputStream = encodedResource.getResource().getInputStream();
    18             try {
    19                 InputSource inputSource = new InputSource(inputStream);
    20                 if (encodedResource.getEncoding() != null) {
    21                     inputSource.setEncoding(encodedResource.getEncoding());
    22                 }
    23                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    24             }
    25             finally {
    26                 inputStream.close();
    27             }
    28         }
    29         catch (IOException ex) {
    30             throw new BeanDefinitionStoreException(
    31                     "IOException parsing XML document from " + encodedResource.getResource(), ex);
    32         }
    33         finally {
    34             currentResources.remove(encodedResource);
    35             if (currentResources.isEmpty()) {
    36                 this.resourcesCurrentlyBeingLoaded.remove();
    37             }
    38         }
    39     }
    View Code

      方法里首先判断一下是否循环加载,然后通过资源创建InputSource(spring解析xml是通过sax去解析的),然后调用doLoadBeanDefinitions()去解析xml和加载BeanDefinition。下面是doLoadBeanDefinitions代码

     1   protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
     2             throws BeanDefinitionStoreException {
     3         try {
     4             Document doc = doLoadDocument(inputSource, resource);
     5             return registerBeanDefinitions(doc, resource);
     6         }
     7         catch (BeanDefinitionStoreException ex) {
     8             throw ex;
     9         }
    10         catch (SAXParseException ex) {
    11             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    12                     "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    13         }
    14         catch (SAXException ex) {
    15             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    16                     "XML document from " + resource + " is invalid", ex);
    17         }
    18         catch (ParserConfigurationException ex) {
    19             throw new BeanDefinitionStoreException(resource.getDescription(),
    20                     "Parser configuration exception parsing XML from " + resource, ex);
    21         }
    22         catch (IOException ex) {
    23             throw new BeanDefinitionStoreException(resource.getDescription(),
    24                     "IOException parsing XML document from " + resource, ex);
    25         }
    26         catch (Throwable ex) {
    27             throw new BeanDefinitionStoreException(resource.getDescription(),
    28                     "Unexpected exception parsing XML document from " + resource, ex);
    29         }
    30     }

      doLoadDocument方法通过配置指定的DocumentLoader和创建XmlBeanDefinitionReader时指定的EntityResolver(这里的实现是ResourceEntityResolver)去加载documen。sax在解析文档时,由于指定了EntityResolver,所以在校验xml文档时会调用ResourceEntityResolver.resolveEntity()方法去加载dtd或xsd

     1     public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
     2         InputSource source = super.resolveEntity(publicId, systemId);
     3         if (source == null && systemId != null) {
     4             String resourcePath = null;
     5             try {
     6                 String decodedSystemId = URLDecoder.decode(systemId, "UTF-8");
     7                 String givenUrl = new URL(decodedSystemId).toString();
     8                 String systemRootUrl = new File("").toURI().toURL().toString();
     9                 // Try relative to resource base if currently in system root.
    10                 if (givenUrl.startsWith(systemRootUrl)) {
    11                     resourcePath = givenUrl.substring(systemRootUrl.length());
    12                 }
    13             }
    14             catch (Exception ex) {
    15                 // Typically a MalformedURLException or AccessControlException.
    16                 if (logger.isDebugEnabled()) {
    17                     logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex);
    18                 }
    19                 // No URL (or no resolvable URL) -> try relative to resource base.
    20                 resourcePath = systemId;
    21             }
    22             if (resourcePath != null) {
    23                 if (logger.isTraceEnabled()) {
    24                     logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]");
    25                 }
    26                 Resource resource = this.resourceLoader.getResource(resourcePath);
    27                 source = new InputSource(resource.getInputStream());
    28                 source.setPublicId(publicId);
    29                 source.setSystemId(systemId);
    30                 if (logger.isDebugEnabled()) {
    31                     logger.debug("Found XML entity [" + systemId + "]: " + resource);
    32                 }
    33             }
    34         }
    35         return source;
    36     }

      方法里首先调用了父类提供的resolveEntity方法去加载,而父类是通过判断加载的是dtd或xsd然后使用持有的EntityResolver去加载。现在配置文件指定的http://www.springframework.org/schema/beans/spring-beans.xsd,xsd是由实例schemaResolver.resolveEntity去加载(schemaResolver实例的创建发生在XmlBeanDefinitionReader设置EntityResolver时)

     1     public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
     2         if (systemId != null) {
     3             if (systemId.endsWith(DTD_SUFFIX)) {
     4                 return this.dtdResolver.resolveEntity(publicId, systemId);
     5             }
     6             else if (systemId.endsWith(XSD_SUFFIX)) {
     7                 return this.schemaResolver.resolveEntity(publicId, systemId);
     8             }
     9         }
    10         return null;
    11     }

      schemaResolver是PluggableSchemaResolver的实例,进入PluggableSchemaResolver的resolveEntity方法

     1     public InputSource resolveEntity(String publicId, String systemId) throws IOException {
     2         if (logger.isTraceEnabled()) {
     3             logger.trace("Trying to resolve XML entity with public id [" + publicId +
     4                     "] and system id [" + systemId + "]");
     5         }
     6 
     7         if (systemId != null) {
     8             String resourceLocation = getSchemaMappings().get(systemId);
     9             if (resourceLocation != null) {
    10                 Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
    11                 try {
    12                     InputSource source = new InputSource(resource.getInputStream());
    13                     source.setPublicId(publicId);
    14                     source.setSystemId(systemId);
    15                     if (logger.isDebugEnabled()) {
    16                         logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
    17                     }
    18                     return source;
    19                 }
    20                 catch (FileNotFoundException ex) {
    21                     if (logger.isDebugEnabled()) {
    22                         logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
    23                     }
    24                 }
    25             }
    26         }
    27         return null;
    28     }
    View Code

      方法里首先调用getSchemaMappings()方法获取所有schema,然后再从里面获取指定systemId的schema,如果找到则返回一个设置好systemId的InputSource。getSchemaMappings()方法里面主要做的事情是,通过指定的ClassLoader查找出所有META-INF下的spring.schemas文件(当需要扩展spring的配置文件时,需要编写自定义的schema),然后再存到一个Map里面,key为命名空间,value为schema文件的路径。

      当加载完documen和校验通过后,接下来的便是加载BeanDefinition,进入XmlBeanDefinitionReader.registerBeanDefinitions方法

    1     public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    2         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    3         int countBefore = getRegistry().getBeanDefinitionCount();
    4         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    5         return getRegistry().getBeanDefinitionCount() - countBefore;
    6     }

      方法里首先创建一个BeanDefinitionDocumentReader(又一个新家伙,spring的抽象能力真的非常棒),BeanDefinitionDocumentReader的主要职责是负责解析dom文档并根据dom文档创建BeanDefinition然后注册到BeanDefinition注册中心(只有当标签是默认命名空间的,也就是http://www.springframework.org/schema/beans,当是扩展的标签时,需要自行实现BeanDefinitionParser进行解析),这里的实现为DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions实现了BeanDefinition的加载和注册,方法里首先根据parentDelegate(主要目的是用来传播默认设置)创建一个BeanDefinitionParserDelegate,然后判断是否设置了profile,如果当前的配置没有被激活,则会跳过解析,跳过的不是整个配置文件,有关profile的使用可以《详解Spring中的Profile》。doRegisterBeanDefinitions源码如下:

     1   protected void doRegisterBeanDefinitions(Element root) {
     2         BeanDefinitionParserDelegate parent = this.delegate;
     3         this.delegate = createDelegate(getReaderContext(), root, parent);
     4 
     5         if (this.delegate.isDefaultNamespace(root)) {
     6             String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
     7             if (StringUtils.hasText(profileSpec)) {
     8                 String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
     9                         profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
    10                 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
    11                     if (logger.isInfoEnabled()) {
    12                         logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
    13                                 "] not matching: " + getReaderContext().getResource());
    14                     }
    15                     return;
    16                 }
    17             }
    18         }
    19 
    20         preProcessXml(root);
    21         parseBeanDefinitions(root, this.delegate);
    22         postProcessXml(root);
    23 
    24         this.delegate = parent;
    25     }

      当配置文件没有跳过时,执行解析documen文档操作,doRegisterBeanDefinitions方法里的preProcessXml和postProcessXml是预留的扩展点,DefaultBeanDefinitionDocumentReader里的实现为空,所以直接进入parseBeanDefinitions方法,方法里获取所有的子节点,然后循环遍历解析。

     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     }

       如果是默认命名空间的标签,直接进入parseDefaultElement,方法里根据标签名字,进行不同的处理,如果是“import”将加载一个资源,然后执行上面的流程;如果是“alias”,向BeanDefinitionRegistry注册别名;如果是“bean”执行BeanDefinition的注册;如果是“beans”递归调用doRegisterBeanDefinitions方法。parseDefaultElement源码如下:

     1     private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
     2         if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
     3             importBeanDefinitionResource(ele);
     4         }
     5         else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
     6             processAliasRegistration(ele);
     7         }
     8         else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
     9             processBeanDefinition(ele, delegate);
    10         }
    11         else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    12             // recurse
    13             doRegisterBeanDefinitions(ele);
    14         }
    15     }

      当解析的标签是“bean”时,将会使用BeanDefinitionParserDelegate.parseBeanDefinitionElement去解析bean标签。进入BeanDefinitionParserDelegate.parseBeanDefinitionElement方法

     1     public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
     2         String id = ele.getAttribute(ID_ATTRIBUTE);
     3         String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
     4 
     5         List<String> aliases = new ArrayList<String>();
     6         if (StringUtils.hasLength(nameAttr)) {
     7             String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
     8             aliases.addAll(Arrays.asList(nameArr));
     9         }
    10 
    11         String beanName = id;
    12         if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
    13             beanName = aliases.remove(0);
    14             if (logger.isDebugEnabled()) {
    15                 logger.debug("No XML 'id' specified - using '" + beanName +
    16                         "' as bean name and " + aliases + " as aliases");
    17             }
    18         }
    19 
    20         if (containingBean == null) {
    21             checkNameUniqueness(beanName, aliases, ele);
    22         }
    23 
    24         AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    25         if (beanDefinition != null) {
    26             if (!StringUtils.hasText(beanName)) {
    27                 try {
    28                     if (containingBean != null) {
    29                         beanName = BeanDefinitionReaderUtils.generateBeanName(
    30                                 beanDefinition, this.readerContext.getRegistry(), true);
    31                     }
    32                     else {
    33                         beanName = this.readerContext.generateBeanName(beanDefinition);
    34                         // Register an alias for the plain bean class name, if still possible,
    35                         // if the generator returned the class name plus a suffix.
    36                         // This is expected for Spring 1.2/2.0 backwards compatibility.
    37                         String beanClassName = beanDefinition.getBeanClassName();
    38                         if (beanClassName != null &&
    39                                 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
    40                                 !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
    41                             aliases.add(beanClassName);
    42                         }
    43                     }
    44                     if (logger.isDebugEnabled()) {
    45                         logger.debug("Neither XML 'id' nor 'name' specified - " +
    46                                 "using generated bean name [" + beanName + "]");
    47                     }
    48                 }
    49                 catch (Exception ex) {
    50                     error(ex.getMessage(), ele);
    51                     return null;
    52                 }
    53             }
    54             String[] aliasesArray = StringUtils.toStringArray(aliases);
    55             return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    56         }
    57 
    58         return null;
    59     }
    View Code

      方法里首先获取bean标签的id和name属性,如果配置了id,那么beanNama就是id。然后判断是否配置了多个name,如果有将解析为别名。然后调用parseBeanDefinitionElement方法创建BeanDefinition,parseBeanDefinitionElement方法会根据标签的属性和子节点内容去设置BeanDefinition(关于BeanDefinition的属性有什么作用这里先跳过),至此,已经成功解析完整个bean标签并且创建了BeanDefinition,然后返回给上层调用者即parseBeanDefinitionElement方法。parseBeanDefinitionElement方法做最后的处理,判断用户是否设置了名称,如果没有生成一个。当这些操作都完成时,回到DefaultBeanDefinitionDocumentReader.processBeanDefinition方法进行进一步装饰BeanDefinition然后向给定的BeanDefinitionRegistry注册。

       如果解析的命名空间不是默认的,spring会怎么处理呢?现在来更改一下配置文件

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:context="http://www.springframework.org/schema/context"
     4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     5     xsi:schemaLocation="
     6     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     7     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     8     ">
     9 
    10     <bean class="com.zksite.spring.test.SpringBeanTest" />
    11     <context:component-scan base-package="com"></context:component-scan>
    12 </beans>

      配置文件新添加了命名空间:http://www.springframework.org/schema/context并配置了schemaLocation http://www.springframework.org/schema/context/spring-context.xsd。这里先介绍一下spring命名空间扩展机制,spring为了方便用户扩展,提供了NamespaceHandler接口,如果用户需要扩展spring的配置文件只需要做以下处理:

        1.编写xml schema文件

        2.编写spring.schemas文件,用于获取xml schema文件路径

        3.编写spring.handlers文件,用户获取自定义标签解析器

        4.实现NamespaceHandler接口,通常建议继承NamespaceHandlerSupport,实现init方法

      spring在解析到用户自定义的标签时,通过调用BeanDefinitionParserDelegate.parseCustomElement进行处理,方法里会通过持有的NamespaceHandlerResolver获取用户配置的NamespaceHandler,然后调用NamespaceHandler.parse方法去解析。NamespaceHandlerResolver是怎么获取到指定的NamespaceHandler的呢?进入DefaultNamespaceHandlerResolver的resolve方法

     1     public NamespaceHandler resolve(String namespaceUri) {
     2         Map<String, Object> handlerMappings = getHandlerMappings();
     3         Object handlerOrClassName = handlerMappings.get(namespaceUri);
     4         if (handlerOrClassName == null) {
     5             return null;
     6         }
     7         else if (handlerOrClassName instanceof NamespaceHandler) {
     8             return (NamespaceHandler) handlerOrClassName;
     9         }
    10         else {
    11             String className = (String) handlerOrClassName;
    12             try {
    13                 Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
    14                 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
    15                     throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
    16                             "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
    17                 }
    18                 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
    19                 namespaceHandler.init();
    20                 handlerMappings.put(namespaceUri, namespaceHandler);
    21                 return namespaceHandler;
    22             }
    23             catch (ClassNotFoundException ex) {
    24                 throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
    25                         namespaceUri + "] not found", ex);
    26             }
    27             catch (LinkageError err) {
    28                 throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
    29                         namespaceUri + "]: problem with handler class file or dependent class", err);
    30             }
    31         }
    32     }

      首先调用getHandlerMappings()方法,getHandlerMappings方法会根据指定的classLoader找出META-INF下面的所有spring.handlers,然后再把spring.handlers里面的内容存放到一个Map里,key存放命名空间,value存放NamespaceHandler。获取到NamespaceHandler时,先判断一下是否已经初始化了,如果没有,通过反射初始化,然后调用NamespaceHandler.init()方法。当找到指定的NamespaceHandler之后返回给BeanDefinitionParserDelegate.parseCustomElement,方法里再调用获取回来的NamespaceHandler.parse方法去解析自定义标签。

      至此,spring已经通过资源加载了BeanDefinition,接下里的便是向注册中心注册BeanDefinition。进入BeanDefinitionReaderUtils.registerBeanDefinition方法

     1     public static void registerBeanDefinition(
     2             BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
     3             throws BeanDefinitionStoreException {
     4 
     5         // Register bean definition under primary name.
     6         String beanName = definitionHolder.getBeanName();
     7         registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
     8 
     9         // Register aliases for bean name, if any.
    10         String[] aliases = definitionHolder.getAliases();
    11         if (aliases != null) {
    12             for (String alias : aliases) {
    13                 registry.registerAlias(beanName, alias);
    14             }
    15         }
    16     }

      方法里使用传入的BeanDefinitionRegistry进行注册,BeanDefinitionRegistry是通过一个Map将BeanDefinition存起来。

      最后总结一下注册BeanDefinition用到了哪些组件

        BeanDefinitionReader:负责从配置文件加载BeanDefinition

        BeanDefinitionDocumentReader:负责解析document加载BeanDefinition并注册

        BeanDefinitionParserDelegate:负责解析标签并根据标签内容构建BeanDefinition

        BeanDefinitionRegistry:负责BeanDefinition的注册

       当注册完BeanDefinition,接下来便是创建bean

  • 相关阅读:
    LeetCode 40. 组合总和 II(Combination Sum II)
    LeetCode 129. 求根到叶子节点数字之和(Sum Root to Leaf Numbers)
    LeetCode 60. 第k个排列(Permutation Sequence)
    LeetCode 47. 全排列 II(Permutations II)
    LeetCode 46. 全排列(Permutations)
    LeetCode 93. 复原IP地址(Restore IP Addresses)
    LeetCode 98. 验证二叉搜索树(Validate Binary Search Tree)
    LeetCode 59. 螺旋矩阵 II(Spiral Matrix II)
    一重指针和二重指针
    指针的意义
  • 原文地址:https://www.cnblogs.com/hanjiehu/p/8625617.html
Copyright © 2011-2022 走看看