文章引用:
https://www.cnblogs.com/wade-luffy/p/6066932.html(Spring的profile属性)
https://www.cnblogs.com/SummerinShire/p/6392242.html
继续上一遍文章Spring源码学习–获取Document(https://blog.csdn.net/u013412772/article/details/79833030),Spring将文件转换为Document后,接下来就是提取以及注册Bean;此时程序已经拥有XML文档文件的Document实例对象了。
一、XmlBeanDefinitionReader
/** * Register the bean definitions contained in the given DOM document. Called by * {@code loadBeanDefinitions}. * <p> * Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */
public int registerBeanDefinitions(Document doc, Resource resource)
throws BeanDefinitionStoreException {
// 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 在实例化BeanDefinitionReader时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类
// 记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 加载以注册BeanDefinition个数
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 记录本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
其中registerBeanDefinitions方法的第一个参数是Spring源码学习–获取Document(https://blog.csdn.net/u013412772/article/details/79833030)的loadDocument加载转换出来的。在这个方法中很好的应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是BeanDefinitionDocumentReader。其中BeanDefinitionDocumentReader如下所示:
public interface BeanDefinitionDocumentReader {
/** * Read bean definitions from the given DOM document and * register them with the registry in the given reader context. * @param doc the DOM document * @param readerContext the current context of the reader * (includes the target registry and the resource being parsed) * @throws BeanDefinitionStoreException in case of parsing errors */
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException;
}
而实例化的工作实在createBeanDefinitionDocumentReader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader类了,进入DefaultBeanDefinitionDocumentReader类中查看registerBeanDefinitions()的方法代码如下:
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
发现这个方法的重要目的之一就是提取xml文件中的root,以便于再次将root作为参数继续BeanDefinition的注册。经过上面的分析,我们终于到了核心逻辑的底部
protected void doRegisterBeanDefinitions(Element root)
如果说以前都是在XML加载解析的准备阶段,那么doRegisterBeanDefinitions算是真正的开始解析了,doRegisterBeanDefinitions方法的实现如下面所示:
/** * Register each bean definition within the given root {@code <beans/>} element. */
protected void doRegisterBeanDefinitions(Element root) {
// 专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 处理profile属性
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;
}
通过上面的代码我们可以开发处理流程,发现preProcessXml(root)和postProcessXml(root)方法代码是空的,既然是空的为什么还要写?就像面向对象设计方法学中说的一句话,一个类要么面向继承设计,要么用final修饰。在DefaultBeanDefinitionDocumentReader中并没有用final修饰,所以它是面向继承而设计的。这两个方法正式为子类设计的,如果了解设计模式,会发现这个正是模版方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些特殊的处理,那么只需要重写这两个方法就好了。
二、profile属性
具体profile的使用可以参照:https://www.cnblogs.com/SummerinShire/p/6392242.html
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注解配置。
三、解析并注册BeanDefinition
进过上面的分析,开始进行XML的读取,跟踪代码进入parseBeanDefinitions方法如下:
/** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 对beans的处理
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)) {
// 对bean的处理
parseDefaultElement(ele, delegate);
}
else {
// 对bean的处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在Spring中XML配置文件里面有两大类Bean声明,一类是默认的,如下所示:
<!-- 解决框架对url参数中含有.的string类型参数截断问题 -->
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="useSuffixPatternMatch" value="false" />
</bean>
另一类是自定义的如下所示:
<tx:annotation-driven/>
Spring针对这两种情况分别解析,如果采用Spring默认的配置,Spring当然之后该怎么解析,如果采用自定义,那么就需要用户实现一些接口以及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement(root);方法对自定义命名空间进行解析。二判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceUTl()获取命名空间,并与Spring中固定的命名空间 http://www.springframework.org/schema/beans 进行比对。如果一致则认为是默认,否则认为是自定义的。后续对默认标签解析与自定义标签解析再说明