spring源码阅读-- 容器启动之加载BeanDefinition
Posted hanjiehu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring源码阅读-- 容器启动之加载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 }
方法里首先判断一下是否循环加载,然后通过资源创建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 }
方法里首先调用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 }
方法里首先获取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
以上是关于spring源码阅读-- 容器启动之加载BeanDefinition的主要内容,如果未能解决你的问题,请参考以下文章
Spring源码阅读Spring容器启动原理(上)-配置资源加载和注册
Spring源码阅读Spring容器启动原理(下)-Bean实例的创建和依赖注入