spring源码深度解析—容器的基本实现,你知多少?( 七 )

5.5 解析及注册BeanDefinitions当把文件转换成Document后 , 接下来就是对bean的提取及注册 , 当程序已经拥有了XML文档文件的Document实例对象时 , 就会被引入到XmlBeanDefinitionReader.registerBeanDefinitions这个方法:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore;}其中的doc参数即为上节读取的document , 而BeanDefinitionDocumentReader是一个接口 , 而实例化的工作是在createBeanDefinitionDocumentReader()中完成的 , 而通过此方法 , BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader了 , 进入DefaultBeanDefinitionDocumentReader后 , 发现这个方法的重要目的之一就是提取root , 以便于再次将root作为参数继续BeanDefinition的注册 , 如下代码:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root);}通过这里我们看到终于到了解析逻辑的核心方法doRegisterBeanDefinitions , 接着跟踪源码如下:
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isInfoEnabled()) { logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }12345678910111213141516171819202122我们看到首先要解析profile属性 , 然后才开始XML的读取 , 具体的代码如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }在Spring的XML配置里面有两大类Bean声明 , 一个是默认的 , 如: 
另一类就是自定义的 , 如: 
而这两种方式的读取及解析差别是非常大的 , 如果采用Spring默认的配置 , Spring当然知道该怎么做 , 但如果是自定义的 , 那么就需要用户实现一些接口及配置了 。对于根节点或子节点如果是默认命名空间的话采用parseDefaultElement方法进行解析 , 否则使用delegate.parseCustomElement方法对自定义命名空间进行解析 。而判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceURI()获取命名空间 , 并与Spring中固定的命名空间http://www.springframework.org/schema/beans进行对比 , 如果一致则认为是默认 , 否则就认为是自定义 。 
profile的用法 
通过profile标记不同的环境 , 可以通过设置spring.profiles.active和spring.profiles.default激活指定profile环境 。如果设置了active , default便失去了作用 。如果两个都没有设置 , 那么带有profiles的bean都不会生成 。 
有多种方式来设置这两个属性:
作为DispatcherServlet的初始化参数;作为web应用的上下文参数;作为JNDI条目;作为环境变量; System.set("spring.profiles.active","prod")作为JVM的系统属性; -Dspring.profiles.active="prod"在集成测试类上 , 使用@ActiveProfiles注解配置 。以前两种方式举例 , 它们都可以在web.xml中设置:
<?xml version="1.0" encoding="UTF-8"?><web-app version="3.0"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name></display-name><welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <context-param> <param-name>applicationContext</param-name> <param-value>/applicationContext.xml</param-value> </context-param> <!-- 在上下文中设置profile的默认值 --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 在servlet中设置profile的默认值 --> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping></web-app>


推荐阅读