前言
上文 Spring IoC:ApplicationContext 刷新前的配置 介绍了 refresh 方法前的环境准备操作,接下来正式进入 refresh 方法。prepareRefresh 是refresh 里的第一个方法,主要是一些准备工作,比较简单的方法,看一下就了解了。接下来是 obtainFreshBeanFactory 方法,该方法用于获得一个新的 BeanFactory,本文将详细介绍该方法。
提醒读者的一点,本节了解即可,主要对于配置,BeanFactory有个印象即可,本章对于spring开发者用来扩展自己的业务的地方并不多
obtainFreshBeanFactory 方法概述
该方法会解析所有 Spring 配置文件(通常我们会放在 resources 目录下),将所有 Spring 配置文件中的 bean 定义封装成 BeanDefinition,加载到 BeanFactory 中。常见的,如果解析到<context:component-scan base-package="" /> 注解时,会扫描 base-package 指定的目录,将该目录下使用指定注解(@Controller、@Service、@Component、@Repository)的 bean 定义也同样封装成 BeanDefinition,加载到 BeanFactory 中。
上面提到的 “加载到 BeanFactory 中” 的内容主要指的是添加到以下3个缓存:
beanDefinitionNames缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 集合。
beanDefinitionMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和 BeanDefinition 映射。
aliasMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和别名映射。
正文
首先,我们来到 obtainFreshBeanFactory 方法。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 1.刷新 BeanFactory,由AbstractRefreshableApplicationContext实现
refreshBeanFactory();
// 2.拿到刷新后的 BeanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
1.刷新 BeanFactory,由 AbstractRefreshableApplicationContext 实现,见代码块1详解。
代码块1:refreshBeanFactory 方法
@Override
protected final void refreshBeanFactory() throws BeansException {
// 1.判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 2.创建一个新的BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 3.加载 bean 定义。
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
3.加载 bean 定义,由 XmlWebApplicationContext 实现,见代码块2详解。
代码块2:loadBeanDefinitions
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 1.为指定BeanFactory创建XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
// 2.使用此上下文的资源加载环境配置 XmlBeanDefinitionReader
beanDefinitionReader.setEnvironment(getEnvironment());
// resourceLoader赋值为XmlWebApplicationContext
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
// 3.加载 bean 定义
loadBeanDefinitions(beanDefinitionReader);
}
1.获取配置文件路径:如果 configLocations 属性不为空,则返回 configLocations 的值;否则,调用 getDefaultConfigLocations() 方法。在 Spring IoC:ApplicationContext 刷新前的配置 文中介绍过 configLocations 属性,该属性会被赋值为我们在web.xml中配置的 contextConfigLocation 的参数值,例如下图即为:classpath*:config/spring/appcontext-*.xml
如果 web.xml 中没有配置 contextConfigLocation 参数,则会拿到 Spring 默认的配置路径:/WEB-INF/applicationContext.xml。
2.根据配置文件路径加载 bean 定义,见代码块4详解。
代码块4:loadBeanDefinitions
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 1.获取 resourceLoader,这边为 XmlWebApplicationContext
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 2.判断 resourceLoader 是否为 ResourcePatternResolver 的实例
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 2.1 根据路径拿到该路径下所有符合的配置文件,并封装成Resource
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 2.2 根据Resource,加载Bean的定义
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
} else {
// Can only load single resources by absolute URL.
// 3.只能通过绝对URL加载单个资源
Resource resource = resourceLoader.getResource(location);
// 3.1 根据Resource,加载Bean的定义
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
1.获取 resourceLoader:这个 resourceLoader 值为 XmlWebApplicationContext。在上文代码块2中会将 resourceLoader 属性值赋为 XmlWebApplicationContext。
2.判断 resourceLoader 是否为 ResourcePatternResolver 的实例:这边 resourceLoader 为 XmlWebApplicationContext,而 XmlWebApplicationContext 的继承关系如下图,可以看到是有实现 ResourcePatternResolver 接口的,因此这边判断结果为 true。
2.1 根据路径拿到该路径下所有符合的配置文件,并封装成 Resource。如果我们配置的路径为:classpath*:config/spring/appcontext-*.xml,并且项目配置如下图,则该方法会拿到2个配置文件:appcontext-bean.xml 和 appcontext-core.xml。
2.2 根据 Resource,加载 bean 定义,见代码块5详解。
代码块5:loadBeanDefinitions
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
// 1.遍历所有的Resource
for (Resource resource : resources) {
// 2.根据Resource加载bean的定义,XmlBeanDefinitionReader实现
counter += loadBeanDefinitions(resource);
}
return counter;
}
2.根据 Resource 加载 bean 定义,由 XmlBeanDefinitionReader 实现,见代码块6详解。
方法块6:loadBeanDefinitions
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 加载 bean 定义
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
// 1.当前正在加载的EncodedResource
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 2.将当前encodedResource添加到currentResources
if (!currentResources.add(encodedResource)) {
// 如果添加失败,代表当前的encodedResource已经存在,则表示出现了循环加载
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// 3.拿到Resource的inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 4.将inputStream封装成org.xml.sax.InputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 5.加载 bean 定义(方法以do开头,真正处理的方法)
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
} finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
5.加载 bean 定义,方法以 do 开头,真正处理的方法,见代码块7详解。
代码块7:doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 1.根据inputSource和resource加载XML文件,并封装成Document
Document doc = doLoadDocument(inputSource, resource);
// 2.根据返回的Document注册Bean信息(对配置文件的解析,核心逻辑)
return registerBeanDefinitions(doc, resource);
} catch (BeanDefinitionStoreException ex) {
throw ex;
} catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
} catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
} catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
} catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
1.根据 inputSource 和 resource 加载 XML文件,并封装成 Document,见代码块8详解。
2.根据返回的 Document 注册 bean 信息,见代码块9详解。
代码块8:doLoadDocument
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// 1.getValidationModeForResource(resource): 获取XML配置文件的验证模式
// 2.documentLoader.loadDocument: 加载XML文件,并得到对应的 Document
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
// 1.1 如果手动指定了XML文件的验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 1.2 如果未指定则使用自动检测
int detectedMode = detectValidationMode(resource);
// 1.3 如果检测出的验证模式不为 VALIDATION_AUTO, 则返回检测出来的验证模式
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
// 1.4 如果最终没找到验证模式,则使用 XSD
return VALIDATION_XSD;
}
protected int detectValidationMode(Resource resource) {
// 1.2.1 校验resource是否为open stream
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}
InputStream inputStream;
try {
// 1.2.2 校验resource是否可以打开InputStream
inputStream = resource.getInputStream();
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}
try {
// 1.2.3 根据inputStream检测验证模式
return this.validationModeDetector.detectValidationMode(inputStream);
} catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
// 1.2.3.1 按行遍历xml配置文件,获取xml文件的验证模式
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
// 如果读取的行是空或者注释则略过
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
// 内容包含"DOCTYPE"则为DTD,否则为XSD
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
// 如果content带有 '<' 开始符号,则结束遍历。因为验证模式一定会在开始符号之前,所以到此可以认为没有验证模式
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
// 1.2.3.2 根据遍历结果返回验证模式是 DTD 还是 XSD
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
} catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
} finally {
reader.close();
}
}
// DefaultDocumentLoader.java
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 2.1 创建DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 2.2 通过DocumentBuilderFactory创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 2.3 使用DocumentBuilder解析inputSource返回Document对象
return builder.parse(inputSource);
}
1.获取 XML 配置文件的验证模式。XML 文件的验证模式是用来保证 XML 文件的正确性,常见的验证模式有两种:DTD 和 XSD,以下简单展示下这两种验证模式的配置。
DTD 验证模式(已停止更新)
要使用 DTD 验证模式的时候需要在 XML 文件的头部声明,以下是在 Spring 中使用 DTD 声明方式的代码:
XSD 验证模式
从 Spring的 源码中可以看到,dtd 验证模式已经停止更新,因此目前使用的验证模式基本上是 XSD。
代码块9:registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 1.使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 2.记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 3.createReaderContext:根据resource创建一个XmlReaderContext
// 4.registerBeanDefinitions:加载及注册Bean定义
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 5.返回本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
3.根据 resource 创建一个 XmlReaderContext,见代码块10详解。
4.加载及注册 bean 定义,由 DefaultBeanDefinitionDocumentReader 实现,见代码块11详解。
代码块10:createReaderContext
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
// DefaultNamespaceHandlerResolver.java
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
}
这边会根据 resource 构建一个 XmlReaderContext,用于存放解析时会用到的一些上下文信息。
其中 namespaceHandlerResolver 会创建默认的 DefaultNamespaceHandlerResolver,DefaultNamespaceHandlerResolver的handlerMappingsLocation 属性会使用默认的值 “META-INF/spring.handlers”,并且这边有个重要的属性 handlerMappings,handlerMappings 用于存放命名空间和该命名空间handler类的映射,如下图:
handlerMappings 的值默认位于“META-INF/spring.handlers” 文件下,一般在我们定义自定义注解时需要用到。
代码块11:registerBeanDefinitions
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 1.拿到文档的子节点,对于Spring的配置文件来说,理论上应该都是<beans>
Element root = doc.getDocumentElement();
// 2.通过拿到的节点,注册 Bean 定义
doRegisterBeanDefinitions(root);
}
2.通过拿到的节点,注册 bean 定义,见代码块12详解。
代码块12:doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
// 构建BeanDefinitionParserDelegate
this.delegate = createDelegate(getReaderContext(), root, parent);
// 1.校验root节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)
if (this.delegate.isDefaultNamespace(root)) {
// 2.处理profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 校验当前节点的 profile 是否符合当前环境定义的, 如果不是则直接跳过, 不解析该节点下的内容
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;
}
}
}
// 3.解析前处理, 留给子类实现
preProcessXml(root);
// 4.解析并注册bean定义
parseBeanDefinitions(root, this.delegate);
// 5.解析后处理, 留给子类实现
postProcessXml(root);
this.delegate = parent;
}
2.处理 profile 属性,见代码块13详解。
4.解析并注册 bean 定义,见代码块14详解。
代码块13
profile 属性主要用于多环境开发,例如下图:
我们可以在配置文件中同时写上多套配置来适用于开发环境、测试环境、生产环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。具体使用哪个环境在 web.xml 中通过参数 spring.profiles.active 来配置。
代码块14:parseBeanDefinitions
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 1.默认命名空间的处理
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
// 遍历root的子节点列表
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)) {
// 1.1 默认命名空间节点的处理,例如: <bean id="test" class="" />
parseDefaultElement(ele, delegate);
}
else {
// 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
delegate.parseCustomElement(ele);
}
}
}
} else {
// 2.自定义命名空间的处理
delegate.parseCustomElement(root);
}
}
最终,我们来到了解析 bean 定义的核心部分,这边会遍历 root 节点(正常为 节点)下的所有子节点,对子节点进行解析处理。
如果节点的命名空间是 Spring 默认的命名空间,则走 parseDefaultElement(ele, delegate) 方法进行解析,例如最常见的:。
如果节点的命名空间不是 Spring 默认的命名空间,也就是自定义命名空间,则走 delegate.parseCustomElement(ele) 方法进行解析,例如常见的: context:component-scan/、aop:aspectj-autoproxy/。
parseDefaultElement(ele, delegate) 和 delegate.parseCustomElement(ele) 方法是解析 bean 定义的两个核心方法,限于篇幅,将在之后的文章将分别介绍这两个方法。
如何判断默认命名空间还是自定义命名空间?
默认的命名空间为:http://www.springframework.org/schema/beans,其他都是自定义命名空间,例如下图 aop 的命名空间为:http://www.springframework.org/schema/aop
总结
本文主要介绍了加载 bean 定义的一些基本工作:
创建一个新的 BeanFactory:DefaultListableBeanFactory。
根据 web.xml 中 contextConfigLocation 配置的路径,读取 Spring 配置文件,验证配置文件的内容,并封装成 Resource。
根据 Resource 加载 XML 配置文件,并解析成 Document 对象 。
拿到 Document 中的根节点,遍历根节点和所有子节点。
核心的节点解析将在之后的文章单独介绍。