博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring源码阅读(2)-- 容器启动之加载BeanDefinition
阅读量:5097 次
发布时间:2019-06-13

本文共 20730 字,大约阅读时间需要 69 分钟。

  在一文中,阅读了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 
2
7 8
9 10

  通过阅读上文,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
currentResources = this.resourcesCurrentlyBeingLoaded.get(); 8 if (currentResources == null) { 9 currentResources = new HashSet
(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的使用可以。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             // recurse13             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
aliases = new ArrayList
(); 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 
2
9 10
11
12

 

  配置文件新添加了命名空间: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
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

转载于:https://www.cnblogs.com/hanjiehu/p/8625617.html

你可能感兴趣的文章
Don’t Be Afraid to Break Things
查看>>
jqGrid 笔记
查看>>
搭建web服务二
查看>>
一些面试题的整理
查看>>
JavaScript跨域总结与解决办法
查看>>
网络测试工具netperf(转)
查看>>
安装使用eclipse
查看>>
java.io.CharConversionException: Not an ISO 8859-1 character: xx
查看>>
tp5 问题
查看>>
CSS3中的Rem值与Px之间的换算
查看>>
test 190731 codechef March Challenge COOKING SCHEDULE
查看>>
php实现雪花算法(ID递增)
查看>>
JavaScript中DOM的概念及作用
查看>>
译:Guidelines – a hidden feature for the Visual Studio Editor
查看>>
BeautifulSoup的安装和使用
查看>>
阅读之线程连接池
查看>>
三状态玻璃效果导航的源代码
查看>>
清北学堂Day3
查看>>
高并发吹牛经验
查看>>
初探html-9 链接
查看>>