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

posted on 2018-04-25 15:48  hanjiehu  阅读(823)  评论(0编辑  收藏  举报