• 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

  • 相关阅读:
    软件测试原则
    java知识点
    jquery取值
    Javaweb命名规则
    @ResponseBody
    jquery ajax 方法及各参数详解
    @RequestMapping用法详解
    eclipse+android+opencv环境搭建的步骤
    Java中的内部类(回调)
    OpenCV直方图(直方图、直方图均衡,直方图匹配,原理、实现)
  • 原文地址:https://www.cnblogs.com/hanjiehu/p/8625617.html
Copyright © 2020-2023  润新知