一.前言

回顾

在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续。经过一个星期的准备,梳理了Spring中的BeanDefinition以及它的解析和注册过程。本文着重介绍其特点及用途并延续前面上下文启动过程的步骤继续分析源码。

目录

本文主要从以下几个方面介绍BeanDefinition

  • 什么是BeanDefinition

  • BeanDefinition解析注册过程及组件概览

  • 从路径到资源加载

  • 资源解析形成BeanDefinition

  • BeanDefinition注册

二.什么是BeanDefinition

个人对Spring喜爱不止是其设计上鬼斧神工,也不仅是其生态的完整性,还有一部分原因是其源代码的命名。笔者日常开发时偶尔感觉很无奈,由于每个人都有自己的风格,工作中的项目代码命名可谓五花八门,着实让人看着糟心。

或许因为英语水平的限制,无法表达出像Spring那样生动的描述,从命名上就能给人醍醐灌顶般的感觉!

BeanDefinition,顾名思义即Spring中Bean的定义。XML语言配置描述的Bean配置,对于Java面向对象语言而言,需要将其配置转化为JAVA语言能够描述的内容,即面向对象。在Spring中抽象出BeanDefinition类型的对象用于描述XML中配置的Bean。BeanDefinition是Bean的元数据信息,其中囊括了一个Bean对象的所有信息:

  • Bean的类型
  • Bean的属性值
  • Bean的构造参数值
  • Bean的属性访问
  • 等等...

在Javadocs中是这样描述BeanDefinition对象:

A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.

对Spring中BeanDefinition的抽象形成如下UML图:

BeaDefinition是最上层的接口,定义了Bean定义配置的基本属性行为:

  • 定义作用域常量
  • 定义Bean的角色常量
  • 设置获取Bean的父Bean的名称
  • 设置获取Bean的Class类型
  • 设置获取Bean的作用域
  • 设置获取Bean是否懒惰初始化
  • 设置获取Bean的依赖
  • 设置获取Bean是否为自动装配候选者
  • 设置获取Bean的工厂方法名称
  • 设置获取Bean的构造参数和属性值
  • 等等....

AbstractBeanDefinition是BeaDefinition的基本实现,实现BeaDefinition的大多数行为。

RootBeanDefinition是合并的BeanDefinition,BeaDefinition之间存在依赖关系,有属性继承。Spring Bean工厂会将存在依赖关系的BeanDefinition合并成RootBeanDefinition。主要用于描述因依赖形成的合并Bean。

ChildBeanDefinition表示有继承父Bean的Bean定义,ChildBeanDefinition代表了继承关系中的子Bean定义。

GenericBeanDefinition是一站式的标准BeaDefinition,既可以灵活设置BeanDefinition属性,又可以动态设置父Bean。

虽然以上的RootBeanDefinition/ChildBeanDefinition都可以在配置解析阶段用于当做单个BeaDefinition注册,但是在Spring 2.5中加入GenericBeanDefinition,更偏爱使用GenericBeanDefinition用作注册。源码实现中也的确如此,解析阶段

BeanDefition能够描述如XML描述的Bean配置,Spring在启动过程中的一个非常重要环节就是将XML描述的Bean配置转为BeanDefinition的描述。接下来就循序渐进看这一过程的实现。

三.BeanDefinition解析注册过程及组件概览

1.解析注册过程概览

BeanDefinition解析注册过程是个非常复杂而漫长的过程。如果是XML配置需要加载XML资源、解析XML文档、形成BeanDefition对象、再注册BeanDefition,其中涉及到诸多Spring提供的组件。为了能够清晰的展示和顺利描述这些细节,先看下组件处理过程图:

  1. 当使用XML配置Bean定义时,Spring ApplicationContext通过委托XmlBeanDefinitionReader加载路径资源,并使用Java XML技术产生Document对象。XmlBeanDefinitionReader组合前篇文章中介绍的ResourceLoader,通过其加载路径资源产生Resource对象。
  2. 同时XmlBeanDefinitionReader组合BeanDefinitionDocumentReader,委托其解析Dom,产生Element。每个Element元素即是Bean定义配置。
  3. BeanDefinitionDocumentReader中组合BeanDefinitionParserDelegate,将产生Bean定义配置的元数据输入给Delegate,通过其解析Bean定义配置产生BeanDefinition对象。
  4. 最后将BeanDefinition输入Bean定义注册器,交由其注册为上下文中的BeanDefinition。

2.组件概览

BeanDefinitionReader

BeanDefinitionReader是Spring上下文中至关重要的组件。上下文通过组合BeanDefinitionReader从而具有解析Bean配置并进行读Bean定义的能力。

BeanDefinitionReader定义了一套读从路径或资源加载Bean定义行为:

  • int loadBeanDefinitions(Resource resource)
  • int loadBeanDefinitions(Resource... resources)

同时BeanDefinitionReader提供获取BeanDefinitionRegitry和ResourceLoader接口:

  • BeanDefinitionRegistry getRegistry()
  • ResourceLoader getResourceLoader()

其中根据读的资源不同,分为多种实现:

  1. 当Bean配置使用XML时,上下文将使用XmlBeanDefinitionReader加载解析Bean定义。
  2. 当Bean配置使用Groovy脚本时,上下文使用GroovyBeanDefinitionReader解析脚本,生成BeanDefinition。

其中最常用的XmlBeanDefinitionReader加载资源使用ResourceLoader,解析XML使用下述BeanDefinitionDocumentReader。

Note:

BeanDefinitionReader这里设计使用了无处不在传神模式:策略。

根据不同的资源读,分别封装相应的算法实现。

Tips:

ApplicationContext比BeanFactory具有更强的能力正是其组合了像BeanDefinitionReader这样的组件,扩展了加载解析资源,生成BeanDefinition。

BeanDefinitionDocumentReader

XmlBeanDefinitionReader具有解析XML文件的能力,但是实际的解析逻辑由被抽象成另一个对象实现,即BeanDefinitionDocumentReader。实际的XML解析由BeanDefinitionDocumentReader负责,BeanDefinitionDocumentReader的实现负责解析XML DOM树,生成一个一个的Element元素。

BeanDefinitionDocumentReader只有一个默认实现DefaultBeanDefinitionDocumentReader。其中持有了解析XML的上下文,主要保存一些解析上下文的配置。同时硬编码了一些Spring XML Bean的标签。

Note:

从XmlBeanDefinitionReader和BeanDefinitionDocumentReader的设计也可以看出,Spring的一些精妙之处。

其首先遵循了接口隔离原则,因为XML的解析可能会变化(比如扩展一些自定义的标签等),所以将读Document文档的逻辑隔离成BeanDefinitionDocumentReader。但是对于XML的加载却是XML标准,属于公共的逻辑由XmlBeanDefinitionReader完成。聚合公共,隔离变化。

其二遵循单一职责原则,加载DOM和解析DOM可以拆分。

BeanDefinitionDocumentReader除了以上功能外,还提供了注册BeanDefinition的能力。当然是通过BeanDefinitionRegistry完成,后续介绍。

  • void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
BeanDefinitionParserDelegate

以上BeanDefinitionDocumentReader负责解析DOM树,对于解析结果Element元素即是单个具体的Bean定义配置。在Spring中对于单个Bean定义配置的解析委托BeanDefinitionParserDelegate实现。

BeanDefinitionParserDelegate提供了两项能力:

  1. 解析能力:解析默认的XML命名空间(即Bean空间)的配置
  2. 委托能力:委托其他的解析器解析相应命名空间的配置,可以自由扩展自己实现的BeanDefinitionParser

javadocs中是这样描述该类:

Stateful delegate class used to parse XML bean definitions.

Intended for use by both the main parser and any extension {@link BeanDefinitionParser BeanDefinitionParsers} or {@link BeanDefinitionDecorator BeanDefinitionDecorators}.

BeanDefinitionParserDelegate硬编码了很多Bean配置的标签常量用于匹配解析。同时还维持解析状态,如:解析至哪个节点哪个属性等。

Note:

对设计模式比较熟悉,应该能立即联想到委托模式!BeanDefinitionParserDelegate为其他的解析器代理。整个应用的XML Bean配置解析对于上层组件调用者,只会感知BeanDefinitionParserDelegate存在,下层的各色的解析器可以自由演进、扩展,上层无需做任何改动(解耦)

BeanDefinitionRegistry

BeanDefinitionRegistry又是一个能让人觉醒的命名,即Bean定义注册器。先来看下它的接口定义:

  • void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  • void removeBeanDefinition(String beanName)
  • BeanDefinition getBeanDefinition(String beanName)
  • boolean containsBeanDefinition(String beanName)
  • 等等......

注册/移除/获取/是否包含Bean定义,一个非常标准的注册器。BeanDefinitionRegistry是将从配置中解析出的Bean定义注册为上下文合法的BeanDefinition。

该接口通常与Bean定义读取配合使用,后者用于读Bean定义,前者将读出的Bean定义注册。

而该接口大多数被BeanFactory实现,因为只有BeanFactory才会持有所有的BeanDefinition然后实例化为Bean实例。

下图展示BeanDefinitionRegistry实现关系

其中DefaultListableBeanFactory是其最通用的实现。该实现就是一个BeanFactory,让BeanFactory有注册Bean定义的能力。关于DefaultListableBeanFactory的很多实现细节,后面会详述。

SimpleBeanDefinitionRegistry只是纯粹的持有Bean定义和注册Bean定义,而不是BeanFactory,主要是用于测试。

四.从路径到资源加载

上篇文章Resource抽象中提及何时何地触发加载解析资源时,提到本篇文章中详述。同时本节也承接第二篇Envoriment组件继续跟踪上下文启动流程。

1.上下文的刷新

Override
public void refresh() throws BeansException, IllegalStateException {
// 使用监视器同步上下文启动和关闭
synchronized (this.startupShutdownMonitor) {
// 为上下文刷新做准备
// Prepare this context for refreshing.
prepareRefresh();
// 获取上下文中的Bean工厂:加载资源、解析资源、解析Bean定义、注册Bean定义
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

上下文刷新是整个Spring框架的中的核心流程,所有子上下文实现的启动都会调用refresh完成上下文的启动,即模板方法,Spring将上下文的启动过程抽象成一个固定的模板流程。Spring的启动流程的绝大部分逻辑都有该refresh完成,逻辑过程非常复杂且漫长。refresh中也的逻辑流程中也提现了Bean的生命周期。由于过程非常长,系列源码中也是会将其拆解成一个个单一的部分详解,最后再将其串联起来。

Note:

这里使用了另一个独领风骚的设计模式,模板方法模式。refresh是模板方法方法,其中预留抽象方法和扩展点给子上下文实现扩展。模板方法模式的主要应用核心要领在于多变的业务逻辑能被抽象成固定的模板流程,只要满足其要领,均可使用该模式实现出精彩的代码。

本节介绍refresh刷新步骤中的核心步骤之一:创建BeanFactory,承接前篇的Environment:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
// 如果Environment未初始化,其中初始化Environment
setConfigLocations(configLocations);
// 调用上下文刷新,进入Spring启动的核心
if (refresh) {
refresh();
}
}

refresh由AbstractApplicationContext实现,该上下文是上下文的基础实现。开始刷新上下文之前需要:进行同步,防止刷新时进行上下文关闭操作,正确同步后才正式刷新:

  1. 为刷新做准备工作
  2. 创建上下文内部的BeanFactory
protected void prepareRefresh() {
// 获取当前时间戳,记录上下文启动时间
this.startupDate = System.currentTimeMillis();
// 标识上下文为非关闭,原子变量
this.closed.set(false);
// 表示上下文为活跃状态
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// 初始化上下文环境中的占位属性源,主要用于给子上下文实现的扩展点
// 前篇环境组件的文章中介绍到上下文环境属性初始化,默认只有JVM系统属性源和操作系统环境变量属性源
// 在一些特殊环境的上下文中仍需要初始化其他的属性源,如web环境中需要初始化的属性源
// Initialize any placeholder property sources in the context environment
initPropertySources();
// 验证环境中设置的必要的属性
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// 初始化一个过早上下文事件容器,用于存储一些在广播器可用之前收集的上下文事件
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}

至此,上下文初始化的准备工作就完成,正式开始初始化上下文内部的Bean工厂:

获取BeanFactory,刷新上下文内部的BeanFactory
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

obtainFreshBeanFactory实现如下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 刷新BeanFactory
refreshBeanFactory();
// 获取BeanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

Spring上下文都是通过持有BeanFactory实例,从而具有IOC的能力。BeanFactory负责持有注册Bean定义、实例化BeanFactory、依赖注入等等功能。

refreshBeanFactory中主要完成以下逻辑:

  1. 从String路径加载资源,产生Resource对象
  2. 解析Resource,产生Document对象
  3. 解析DOM树,解析Bean定义,产生BeanDefinition
  4. 注册BeanDefinition

接下来逐步学习该过程。

refreshBeanFactory是AbstractApplicationContext中定义的抽象行为。AbstractApplicationContext并没限制规定需要使用的BeanFactory的具体,更没有实现默认行为,只是定义需要刷新BeanFactory。对于具体使用哪种BeanFactory和具体的刷新都交由子上下文扩展实现,非常标准的模板方法设计

刷新BeanFactory的行为由AbstractApplicationContext的子上下文AbstractRefreshableApplicationContext实现。javadocs是这样描述它的:

Base class for {@link org.springframework.context.ApplicationContext} implementations which are supposed to support multiple calls to {@link #refresh()}, creating a new internal bean factory instance every time. Typically (but not necessarily), such a context will be driven by a set of config locations to load bean definitions from.

该上下文主要是每次调用刷新时,都创建上下文内部的BeanFactory。

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

	// 是否允许Bean定义覆盖标志
private Boolean allowBeanDefinitionOverriding;
// 是否允许循环引用标志
private Boolean allowCircularReferences;
// 上下文内部持有的BeanFactory实例,默认使用DefaultListableBeanFactory
/** Bean factory for this context */
private DefaultListableBeanFactory beanFactory;
// beanFactory的同步器
/** Synchronization monitor for the internal BeanFactory */
private final Object beanFactoryMonitor = new Object();
}

该上下文中持有具体的BeanFactory实例,用于注册Bean定义、实例化Bean。这里是一个很大的扩展,可以实现定制的BeanFactory,注入ApplicationContext中:

//子上下文实现可以覆盖该创建BeanFactory的行为,创建定制化的BeanFacotry
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

AbstractRefreshableApplicationContext中除了创建BeanFactory实例,还定义了加载Bean定义进入BeanFactory的抽象行为:

// 载入Bean定义进入BeanFactory
void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)

该行为由子上下问实现,因为加载策略不一样。

Note:

从这里可以看出,这里的设计又是那个传说的策略模式

继续深入其refreshBeanFactory的实现:

@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果当前上下文有BeanFactory,则销毁其所有的Bean实例,然后关闭BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建新的BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 实现了序列化接口,设置序列化Id
beanFactory.setSerializationId(getId());
// 自定义BeanFactory的属性,又是BeanFactory的扩展点,用于给子上下文扩展
customizeBeanFactory(beanFactory);
// 调用加载Bean定义入BeanFactory
loadBeanDefinitions(beanFactory);
// 同步修改上下文的BeanFactory实例
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

从以上的逻辑可以看出,refreshBeanFactory中主要完成两项逻辑:

  1. 销毁当前上下文的BeanFactory和实例
  2. 创建上下文的新BeanFactory

其中customizeBeanFactory既是一处框架扩张点,又是BeanFactory的基本属性初始化。默认实现中初始化了BeanFactory是否允许Bean定义覆盖和是否允许循环引用。

不过该refreshBeanFactory的核心逻辑是触发了loadBeanDefinitions行为。该loadBeanDefinitions是一项策略行为,需要根据不同的上下文以不同的方式载入Bean定义。这里只分析ClassPathXmlApplicationContext的场景。

ClassPathXmlApplicationContext继承了AbstractXmlApplicationContext,该上下文实现了loadBeanDefinitions,因为只有AbstractXmlApplicationContext才能确定是从Xml中载入Bean定义,所以由其实现loadBeanDefinitions的策略是非常合理的。

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建一个XmlBeanDefinitionReader用于读Xml
// XmlBeanDefinitionReader中组合BeanFactory
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 配置BeanDefinitionReader,设置其环境
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 配置BeanDefinitionReader的资源加载器
beanDefinitionReader.setResourceLoader(this);
// 配置BeanDefinitionReader的Xml实体解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
// 又是一个扩展点,供自上下文扩展定制BeanDefinitionReader的行为
initBeanDefinitionReader(beanDefinitionReader);
// 载入Bean定义
loadBeanDefinitions(beanDefinitionReader);
}

再进一步深入loadBeanDefinitions之前,先看下XmlBeanDefinitionReader的内部结构:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

	...省略

	// 默认的BeanDefinitionDocumentReader类型
private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
// 解析XML出异常时能够报告问题
private ProblemReporter problemReporter = new FailFastProblemReporter(); private ReaderEventListener eventListener = new EmptyReaderEventListener(); private SourceExtractor sourceExtractor = new NullSourceExtractor(); // 命名空间解析器,主要处理不同命名空间的XML配置解析
private NamespaceHandlerResolver namespaceHandlerResolver;
// 文档加载器,将Resource抽象加载解析出Document对象,生成内存DOM树。主要是XML技术
private DocumentLoader documentLoader = new DefaultDocumentLoader();
// 也是XML技术,实体解析器
private EntityResolver entityResolver;
// 也是XML技术,SAX解析XML出错时的处理器
private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
// 也是XML技术,用于XML验证
private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();
// 使用ThreadLocal和Set保证单个资源只会单个线程被解析一次
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");
}

XmlBeanDefinitionReader是BeanDefinitionReader中关于在XML方面定义Bean配置的读取的最终实现,所以从以上的成员域持有也可以看出其主要是在XML的处理上。

其中需要重点关注的是NamespaceHandlerResolver。它是Spring实现多命名空间解析的关键。关于Spring多命名空间解析,后续再介绍Spring Aop文章中再详细介绍。

XmlBeanDefinitionReader主要关注的是Xml的处理,关于Bean定义的读取更多相关,比如:资源路径解析加载、环境等是在其非类,也就是通用的AbstractBeanDefinitionReader中定义。

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

	// Bean定义注册器
private final BeanDefinitionRegistry registry;
// 资源加载
private ResourceLoader resourceLoader;
// 加载Bean Class的类加载器
private ClassLoader beanClassLoader;
// 环境组件
private Environment environment;
// Bean名称生成器
private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
}

AbstractBeanDefinitionReader中持有的成员域都是和Bean定义解析相关的共性对象。与实际的Bean配置格式无关。

在熟悉BeanDefinitionReader后,再来重点看loadBeanDefinitions实现:

// 从location代表的资源文件加载Bean定义
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
} public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 获取资源加载器
ResourceLoader resourceLoader = getResourceLoader();
// 校验资源加载器不能为空
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 如果是模式解析,则转为ResourcePatternResolver类型解析路径模式,并加载为Resource
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 使用模式解析器解析资源路径并加载配置为Resource抽象。这部分内容在上篇文章Resource抽象中已深入介绍
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 从多个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 + "]");
}
// 返回加载的Bean定义个数
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
// 如果不是模式路径,则使用ResourceLoader加载单个资源
else {
// Can only load single resources by absolute URL.
// 资源加载器加载配置为Resource抽象,上篇文章已深入介绍
Resource resource = resourceLoader.getResource(location);
// 根据Resource抽象加载Bean定义
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
// 返回加载的Bean定义个数
return loadCount;
}
}

对于以上的源码逻辑有两点需要注意:

  1. Spring根据路径加载资源是什么时候开始,从哪里切入的
  2. BeanDefinitionReader载入Bean定义接口的多态所提供的能力

上篇文章留下的疑问:Spring根据路径加载资源是什么时候开始,从哪里切入的,这里基本上也已经解决。Spring在使用构造上下文内部的BeanFactory时,将使用XmlBeanDefinitionReader载入BeanDefinition进入BeanFactory,这时涉及到资源路径解析为Resource抽象。

总结下,什么时候触发资源路径解析加载为Resource抽象:只有当需要载入Bean定义的时候。由谁触发:实际处理Bean配置文件的阅读器BeanDefinitionReader负责。

关于解析资源路径且加载为Resource的详细内容上篇文章Resource抽象已经详细说明,这里不再赘述。

接下来再看第二个问题:BeanDefinitionReader载入Bean定义接口的多态所提供的能力

// 从resource载入Bean定义
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
// 从多个resource载入Bean定义
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
// 从resource的路径载入Bean定义
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
// 从多个resource路径载入Bean定义
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

BeanDefinitionReader中对于载入Bean定义具有多种形态,极大程度的提升了其载入Bean定义的能力。但是无从resource路径载入Bean定义,还是利用ResourceLoader将路径解析并加载为resource,从而转化为从resource载入Bean定义。

既然知道从路径载入Bean定义的原理,那继续看载入Bean定义的细节。实际的载入Bean定义loadBeanDefinitions(Resource resource)或者loadBeanDefinition(Resource... resources)完成。但是后者也是内部循环调用前者试下,反正就是多态!这里直接分析前者实现

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 将Resource包装成EncodedResource再载入,EncodedResource是具有编码能力能力的InputStreamResource
return loadBeanDefinitions(new EncodedResource(resource));
}

因为涉及到Resource的读取,必然会有Encode问题。不然会产生乱码,所以这里将描述资源的Resource转为具有编码的可输入的资源。

Note:

这里使用了装饰器模式,EncodedResource通过包装InputStreamSource的实现Resource并加上Encode增强了Resource的能力。从这里也可以看出Spring接口抽象的隔离的非常彻底。Resource/EncodeResource都是InputStreamSource的实现,但是职责并不相同。

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);
}
// 使用前文介绍XmlBeanDefinitionReader中resourcesCurrentlyBeingLoaded防重
// 如果当前文件未被正在解析,则加入本地线程缓存,防止后续重复解析
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// 将EncodedResource转为java中w3c实现的XML技术中的InputSource
try {
// 获取资源对应的输入流,这是Resource的基本能力,实现了InputStream接口,具有获取资源对应的输入流的能力
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// InputSource是JAVA XML技术中的规范api,代表XML输入源
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 做实际的加载Bean定义的逻辑
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();
}
}
}

该方法的重要逻辑在doLoadBeanDefinitions中实现,它主要负责载入Bean定义

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 根据InputSource加载XML的标准接口的文档对象Document,即将XML载入内存,形成DOM树
Document doc = doLoadDocument(inputSource, resource);
// 解析DOM树上的各个节点,每个节点即是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);
}
}

关于创建Document的过程这里不再详述,主要关注Bean定义的解析过程。直接分析registerBeanDefinitions的逻辑:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 此时的document实际上是包含Bean定义的DOM树,Spring中对于该Dom的解析,抽象出BeanDefinitionDocumentReader接口
// 这里创建其实现,采用默认实现DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 获取Bean定义注册器中当前BeanDefinition的个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 注册Bean定义
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 获取此次注册的Bean定义个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}

这里需要关注两点:

  1. BeanDefinitionDocumentReader
  2. XmlReaderContext

关于BeanDefinitionDocumentReader接口的抽象和其具体定义,在之前小节的组件概览中已经介绍,这里不再赘述。这里注意其创建过程的技巧:

// XmlBeanDefinitionReader中持有的BeanDefinitionDocumentReader实现的类型
private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class; // 根据持有的类型创建BeanDefinitionDocumentReader的实现实例
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}

在这里,XmlBeanDefinitionReader也提供了Set接口,可以写入自定义的BeanDefinitionDocumentReader的实现用于扩展。BeanDefinitionDocumentReader本身就是解析Bean定义配置的XML的SPI。

Note:

一般典型的SPI的扩展方式都是采用Java提供的SPI机制,但是这里的技巧也不失为一种方式,主要是面向Spring IOC。

对于createReaderContext实现。该方法主要是为了创建解析XML的上下文,持有一些全局的配置和组件。

以下是XmlReaderContext的成员域细节

// 解析XML,解析Bean定义是一个非常复杂而漫长的逻辑过程
// 需要有全局的对象能够hold这个过程的组件信息。抽象出XmlReaderContext
public class XmlReaderContext extends ReaderContext { // hold XmlBeanDefinitionReader后续会使用其持有的Bean定义注册器
// 后续需要使用Environment
private final XmlBeanDefinitionReader reader;
// hold namespaceHandlerResolver,后续再解析import资源时需要
private final NamespaceHandlerResolver namespaceHandlerResolver;
}
public XmlReaderContext createReaderContext(Resource resource) {
// 只是new 创建一个XmlReaderContext,hold当前解析XML对应的resource, XmlBeanDefinitionReader
// NamespaceHandlerResolver等关于XML解析的组件
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}

在创建了BeanDefinitionDocumentReader和XmlReaderContext后,继后再根据Document注册Bean定义。首先需要逐个逐个解析DOM中的Node,然后逐个解析为Bean定义配置。解析Dom形成NodeD额过程由BeanDefinitionDocumentReader负责。所以其中的核心逻辑是 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)):

registerBeanDefinitions(Document doc, XmlReaderContext readerContext)该接口由默认的DefaultBeanDefinitionDocumentReader实现,在XmlBeanDefinitionReader中创建的也是该默认实现的实例,前文已经介绍。DefaultBeanDefinitionDocumentReader中的实现:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
// 赋值readerContext
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 解析DOM树根节点root
Element root = doc.getDocumentElement();
// 根据根节点解析注册Bean定义
doRegisterBeanDefinitions(root);
}

解析DOM数的根元素,然后根据根元素解析并并注册Bean定义:

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.
// 每次开始解析根元素之前(更贴切的说,是Beans标签的元素),
// 将当前的BeanDefinitionParserDelegate作为父委托解析器,创建新的子委托解析器
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)) {
// profile可以配置多个,使用";"分割,这里按照此规则解析profile属性
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 之前文章已经介绍Environment的能力,这里不再赘述
// 前文也介绍了XmlReaderContext持有解析过程的组件,从其可以获取上下文的Environment组件
// 使用Environment组件抉择该profile是否为activeProfile,进一步决定该Bean定义配置是否需要解析
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
// 如果该profile不满足,则不解析,直接返回
return;
}
}
}
// 解析Bean定义前,处理Xml之前的逻辑
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
// 解析Bean定义后,处理Xml之前的逻辑
postProcessXml(root);
// 还原现场,这是递归的基本原则
this.delegate = parent;
}

该实现中有几点需要关注:

  1. 创建父子Bean定义解析委托器的原因
  2. spring profile的实现
  3. 命名空间解析
  4. 解析Bean定义的hook

逐个击破,来看这些问题。关于第一个问题,相信Spring注释说的也够清楚了。XML配置中,Bean定义的配置都是在Bean命名空间和Beans根元素下的,如:

<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true"
default-autowire="byName"
default-init-method="printHelloWorld"
default-destroy-method="printHelloWorld"
default-merge="false"
default-autowire-candidates=""
profile="default"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> ...bean定义配置省略 <beans>
...bean定义配置省略
</beans>
</beans>

从以上可以看出bean定义配置都是嵌入在根元素Beans下,同时Beans元素仍支持属性配置,用于做全局配置。但是Beans下还可以嵌套Beans,这里有点递归(嵌套)的表现。所以需要设置父子BeanDefinitionParserDelegate,用于分别持有Beans的属性。同时BeanDefinitionParserDelegate是有状态的解析器,它需要维持正在解析的位置在哪里,上一层解析位置在哪里等等状态信息,也需要父子BeanDefinitionParserDelegate维持各自的Beans解析。

在之前Environment组件的文章中已经介绍过Spring Profile的能力是Enviroment赋予的。其中之介绍Environment中的profile细节,但是Environment抉择Bean定义是否满足当前active profile的切入点并未详细说明。现在时机已到,从以上可以看出Environment是如何抉择Bean定义是否属于active profile的实现。需要两点:

  1. Environment当前的active profile是什么
  2. Bean定义配置的profile是多少

需要注意的是,如果Bean定义未配置profile,那么久认为是该Bean定义配置是全局生效的。

再者就是命名空间解析,在Spring中不同命名空间的配置是不一样的,这决定其解析方式不一样,且配置的作用也不一样决定其有相应的处理逻辑。在这里有命名空间解析处理相应配置是否属于默认的命名空间:

public boolean isDefaultNamespace(String namespaceUri) {
// 不为空且等于http://www.springframework.org/schema/beans,则认为是默认命名空间
// 可以看出Spring中默认命名空间是Beans命名空间
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
public boolean isDefaultNamespace(Node node) {
// 获取Node所属的命名空间,这是XML技术,再判断是否为默认命名空间
return isDefaultNamespace(getNamespaceURI(node));
}

关于第四点解析Bean定义的hook,Spring不愧是高扩展框架:扩展无处不在。这里留下解析前和解析后的扩展钩子,应用可以继承DefaultBeanDefinitionDocumentReader实现新的Reader并覆盖钩子,然后注入XmlBeanDefinitionReader中。

Note:

在写业务代码时,也要有这种思想,在易发生变化的地方可以预留hook,实现扩展,由其是模板方法模式中预留hook是必要的!

最后的重点就在于parseBeanDefinitions(root, this.delegate),它是用于触发Bean定义委托器解析Bean定义的关键:

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;
// 判断子节点是否为默认命名空间
// 如果是beans,使用默认方式解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
// 不是Beans命名空间,使用自定义方式解析元素
else {
delegate.parseCustomElement(ele);
}
}
}
}
// 不是Beans命名空间,使用自定义方式解析元素
else {
delegate.parseCustomElement(root);
}
}

对于Spring配置了解的读者,肯定知道Spring的XML配置可以通过引入其他的命名空间,可以在Beans元素下定义其他命名空间的配置。如:引入Context命名空间,可以占位解析器配置:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- context命名空间下的占位解析配置,上面引入了Context命名空间 -->
<context:property-placeholder></context:property-placeholder> </beans>

以上的代码逻辑就是处理这种case。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析import标签
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 解析alias标签
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 解析Bean标签,这是解析Bean定义
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 解析嵌套的Beans标签
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

从以上可以看出,默认的Bean空间解析,只有import/bean/beans/alias四种标签,且DOM的部分解析都是在BeanfinitionDocumentReader中完成。我们这里重点关注bean标签的解析,关于其他三种,考虑到篇幅原因这里不做详细介绍。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 委托Bean定义解析器解析Bean,产生BeanDefinitionHolder对象
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
// hold不为空
if (bdHolder != null) {
// 抉择是否需要修饰BeanDefinition
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 注册BeanDefinition进入BeanFactory
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
// 报告注册错误
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
// 发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

处理Bean定义的主要逻辑就是两点:

  1. 使用委托器解析BeanDefinition
  2. 注册解析结果BeanDefinition至BeanFactory

到这里,关于Spring加载资源和XML Bean配置的DOM解析就已经结束。这一节讲述的内容比较复杂漫长,这里做下总结:

  • AbstractRefreshableApplicationContext中定义了loadBeanDefinitions接口,为什么在该上下文中定义?因为它是可以刷新上下文内部的BeanFactory,在刷新的过程中需要载入Bean定义进入BeanFactory,所以在这里定义。由谁实现?由顶层接近应用的上下文(分类)实现,它们按照不同Bean定义的配置方式实现其不同的载入方式,如XML配置Bean定义,则由AbstractXmlApplicationContext实现;如Java Config配置,则由AnnotationConfigWebApplicationContext实现解析,等等。
  • 需要BeanDefinitionReader,它是什么?它是Bean定义读取器,这个组件应该需要哪些逻辑?能够加载资源,解析配置的BeanDefinition。
  • XmlBeanDefinitionReader的能力,如果通过ResourceLoader解析路径模式,加载资源为Resource抽象?如果解析Resource抽象为XML标准的DOM树,又是如和解析DOM树。

Spring上下文可以看成是kernal,通过组合XmlBeanDefinitionReader而具有处理XML配置的能力,通过将XmlBeanDefinitionReader和BeanDefinitionRegistry组合,可以达到注册BeanDefinition的能力。

五.资源解析形成BeanDefinition

这节中主要承接上文中委托器是如何解析Bean定义配置,如何构造产生BeanDefinition的。这部分逻辑主要在BeanDefinitionDelegate中完成。

BeanDefinitionDelegate委托器不仅用于解析默认的Bean定义配置,还集成BeanDefinitionParser用于扩扩展Spring配置的解析,如:AOP命名空间、Context命名孔家的解析,等等,甚至是自定义的命名空间。javadocs是这样描述这个类的:

Stateful delegate class used to parse XML bean definitions Intended for use by both the main parser and any extension {@link BeanDefinitionParser BeanDefinitionParsers} or {@link BeanDefinitionDecorator BeanDefinitionDecorators}.

它具有两项能力:

  1. 作为主要的默认Bean定义解析器
  2. 作为扩展的BeanDefinitionParser的委托
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
} public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 获取bean的id属性,这里且包括以下的获取方式都是XML技术提供的api
String id = ele.getAttribute(ID_ATTRIBUTE);
// 获取bean的name属性,name属性为bean的别名
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 生成别名,bean的name属性可以配置多个,使用",;"分割即可
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// 将id作为bean的名字
// 如果bean未配置id,则将别名中的第一个作为bean的id,也就是bean名字
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
// 如果containingBean等于空,则要检验bean的名字是否唯一
// bean的名字需要唯一
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 以上逻辑都是为了获取bean的名字和别名
// 这里使用bean的元数据(即Element)和bean的名字进一步解析该bean的属性配置,生成bean对应的BeanDefinition对象
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
// 如果bean定义不为空
if (beanDefinition != null) {
// 人员故意bean名字为空,则需要为其生成一个
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
// 使用bean名字,bean别名,bean定义构造BeanDefinitionHolder
// 该holder主要是为了持有bean名字和bean别名
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 返回holder
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}

从以上逻辑中可以看出,处理了两部分:

  1. 处理bean的名字和bean的别名
  2. 使用bean名字和bean的元信息源Element进一步解析Bean定义
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
// 将当前解析的bean入栈,保存解析状态,能够定位到当前处理的bean的位置
this.parseState.push(new BeanEntry(beanName));
// 解析bean的class
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
// 解析bean的parent属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
// 使用bean的类名和parent属性创建该bean对应的BeanDefintiion
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 解析bean配置的的属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 设置bean定义的描述
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析bean定义的元元素
parseMetaElements(ele, bd);
// 解析Lookup覆盖方法子元素
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析Replace方法子元素
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析构造参数值
parseConstructorArgElements(ele, bd);
// 解析成员属性值
parsePropertyElements(ele, bd);
// 解析Qualifier元素
parseQualifierElements(ele, bd);
// 设置bean定义属于的资源Resource
bd.setResource(this.readerContext.getResource());
// 设置bean定义的源,即是DOM树中的哪个节点
bd.setSource(extractSource(ele));
返回bean定义对象
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
// 返回前,需要弹出当前bean定义位置,已经解析完毕
this.parseState.pop();
}
return null;
}

读以上的逻辑,可以看出才是Spring中解析XML Bean配置的核心。前面的那些步骤,都是为这里铺垫。

关于Bean定义的解析其实也是十分复杂的,但是可以简化的了解大致的逻辑过程:

  • 解析Bean名字,包括别名的解析
  • 解析Bean的class类型
  • 解析Bean配置的属性值
  • 解析Bean的Lookup覆盖方法
  • 解析Bean的Replace方法
  • 解析Bean的构造参数值
  • 解析Bean的成员属性值
  • 等等...

其中最为关键的是着重部分。这里只分析这三个部分,关于方法的解析,日常开发并不常用。

1.解析Bean配置的属性值

Bean本身有很多属性配置,如:作用域、是否懒惰初始化、装配方式、初始化方法、销毁方法、工厂方法等等。parseBeanDefinitionAttributes就是为了解析Bean的这些自身属性。

 public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
BeanDefinition containingBean, AbstractBeanDefinition bd) {
// 解析bean单例属性,这是spring v1.x版本在使用,升级至scope属性。这里报告错误
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
}
// 解析bean作用属性并设置
else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
// 解析bean的抽象属性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
// 解析设置bean是否懒惰初始化属性
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (DEFAULT_VALUE.equals(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
// 解析设置bean的自动装配方式
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
// 解析并设置bean的依赖检查属性
String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
bd.setDependencyCheck(getDependencyCheck(dependencyCheck));
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
// 解析并设置bean是否为自动装配候选者
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
// 解析并设置bean的primary属性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
// 解析并设置bean的初始化方法
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
if (!"".equals(initMethodName)) {
bd.setInitMethodName(initMethodName);
}
}
else {
if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
}
// 解析并设置bean的销毁方法
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
}
else {
if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
}
// 解析并设置bean的工厂方法
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
// 解析并设置bean的工厂bean属性
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
2.解析Bean的构造参数值

在介绍解析Bean对象的构造参数之前,先看下Spring中对于构造参数的抽象ConstructorArgumentValues

public class ConstructorArgumentValues {
// 被索引的参数值(按索引映射)
private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<Integer, ValueHolder>(0);
// 通用的参数值(参数列表)
private final List<ValueHolder> genericArgumentValues = new LinkedList<ValueHolder>();
}

Spring中使用ConstructorArgumentValues表示Bean定义的参数,其中通过两种方式支持Bean定义参数的解析存储:

  1. 按照索引方式查找参数
  2. 通用泛化方式存储参数,按照类型查找

以上两种方式对应一下两种配置方式

 <!-- 按照索引方式 -->
<bean id="constructorInjectBean" class="com.learn.ioc.beandefinition.register.process.ConstructorInjectBean">
<constructor-arg index="2" value="2"></constructor-arg>
<constructor-arg name="1" value="1"></constructor-arg>
</bean> <!-- 通用泛化的方式 -->
<bean id="constructorInjectBean" class="com.learn.ioc.beandefinition.register.process.ConstructorInjectBean">
<constructor-arg name="arg2" value="2"></constructor-arg>
<constructor-arg name="arg1" value="1"></constructor-arg>
</bean>

ConstructorArgumentValues提供两种查找维护参数的方式:

按照索引方式

  • addIndexedArgumentValue(int index, Object value)
  • ValueHolder getArgumentValue(int index, Class<?> requiredType)

按照类型方式

  • addGenericArgumentValue(Object value)
  • ValueHolder getGenericArgumentValue(Class<?> requiredType)

关于更多细节这里由于篇幅,不再详述。

下面继续看解析构造参数值的逻辑:

public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
// 获取该bean配置Element下的所有子元素
NodeList nl = beanEle.getChildNodes();
// 循环遍历子元素
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果子元素满足默认命名空间,且是构造参数配置子元素("constructor-arg")
if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
// 解析构造参数
parseConstructorArgElement((Element) node, bd);
}
}
} public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
// 获取constructor-arg元素的index属性
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
// 获取constructor-arg元素的type属性
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
// 获取constructor-arg元素的name属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 如果索引属性不为空,则按照索引方式存储
if (StringUtils.hasLength(indexAttr)) {
try {
// 转为int类型
int index = Integer.parseInt(indexAttr);
// 校验索引是否合法
if (index < 0) {
error("'index' cannot be lower than 0", ele);
}
else {
try {
// 入栈,维持当前解析位置
this.parseState.push(new ConstructorArgumentEntry(index));
// 解析该索引位置的属性值,也即是构造方法对应参数的入参值
Object value = parsePropertyValue(ele, bd, null);
// 包装成ValueHolder
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
// 设置类型
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
// 设置name
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
// 检查是否已经设置了参数值
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
error("Ambiguous constructor-arg entries for index " + index, ele);
}
else {
// 按照索引增加参数值
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
}
finally {
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
// 索引为空,按照通用泛化的方式存储
else {
try {
this.parseState.push(new ConstructorArgumentEntry());
// 构造ValueHolder
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
// 设置类型
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
// 设置name
if (StringUtils.hasLength(nameAttr)) {
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
// 按照通用泛化的方式增加参数值
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
}
finally {
// 弹出解析的元素
this.parseState.pop();
}
}
}

需要注意的是在解析过程中一直使用BeanDefinition中的ConstructorArgumentValues增加解析出的构造参数值。BeanDefintion在创建初始化时,已经持有ConstructorArgumentValues。

3.解析Bean的成员属性值

在Spring中成员属性值被抽象成PropertyValue和PropertyValues。PropertyValue代表成员属性名和成员属性值的映射。PropertyValues代表多个PropertyValue的集合,其实现MutablePropertyValues是BeanDefinition中用于解析Bean实例成员属性参数的关键。

public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
// 这里逻辑同解析构造参数值一样
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
} public void parsePropertyElement(Element ele, BeanDefinition bd) {
// 获取成员域属性配置的name属性
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
// 如果name为空,则错误报告
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
// 否则记录当前解析位置
this.parseState.push(new PropertyEntry(propertyName));
try {
// 判断该bean定义成员属性集合是否已经包含该成员属性
// 如果是,错误报告,表明重复配置
if (bd.getPropertyValues().contains(propertyName)) {
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
// 解析成员属性对应的值属性
Object val = parsePropertyValue(ele, bd, propertyName);
// 构造属性值抽象对象PropertyValue
PropertyValue pv = new PropertyValue(propertyName, val);
// 添加到bean定义中
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}

到这里解析Bean定义的部分就已经完结。

六.注册BeanDefinition

本文最后一部分是对上节的解析的BeanDefinition结果做处理,即注册BeanDefinition。因为从XML配置中解析出的BeanDefinition仍然是游离与Spring上下文(BeanFactory)之外的不受Spring管理的BeanDefinition,仍需要将其载入BeanFactory中,交由Spring管理。

这部分逻辑主要在前文中这部分实现:

try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}

BeanDefinitionReaderUtils是spring-beans项目提供的工具类,用于快速的注册Bean定义。需要bean定义注册器和bean定义作为参数,bean定义注册器在创建XmlBeanDefinitionRedader时就已将创建,即DefaultListableBeanFactory。后续再创建XML的解析上下文中时,XmlReaderContext持有解析的整个组件。这里从XmlReaderContext获取BeanDefinitionRegistry。

public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 获取bean名字
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
//使用bean名字注册bean定义
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
// 注册bean定义的所有别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}

再继续DefaultListableBeanFactory中实现的BeanDefinitionRegistry接口:

//---------------------------------------------------------------------
// Implementation of BeanDefinitionRegistry interface
//--------------------------------------------------------------------- @Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 判断bean名字和bean定义非空
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
// 如果是AbstractBeanDefinition,则需要对bean定义进行校验
// 其中主要处理覆盖方法,很少使用
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// DefaultListableBeanFactory中持有beanDefinitionMap,用于存储bean定义
// 这里获取bean定义,判断是否已经存在
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已经存在该名字的bean定义
if (existingDefinition != null) {
// 判断是否允许bean定义覆盖,不允许则抛出异常。默认是允许
// AbstractRefreshableApplicationContext中创建BeanFactory时,自定义了BeanFacotry的这些属性
// {@link AbstractRefreshableApplicationContext.customizeBeanFactory}
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + existingDefinition + "] bound.");
}
// bean定义的角色判断,这里略过
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isWarnEnabled()) {
logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isInfoEnabled()) {
logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 允许覆盖,则再次put,覆盖之前bean名字的bean定义
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 如果不存在
else {
// 判断当前是否已经有Bean正在被创建
// 如果有
if (hasBeanCreationStarted()) {
// 已经在启动创建了,不能并发修改集合,否则会造成并发修改异常
// 所以这里同步进行同步
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
// 如果没有Bean正在被创建
else {
// 仍然在启动注册阶段
// Still in startup registration phase
// 用bean名字存储bean定义
this.beanDefinitionMap.put(beanName, beanDefinition);
// 存储bean名字
this.beanDefinitionNames.add(beanName);
// 移除单例bean名字
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}

以上逻辑根据Bean定义是否存在分为两种情况。存在时,则需要抉择是否可以覆盖bean定义;不存在时需要判断上下文是否已经正在创建Bean。对于hasBeanCreationStarted为true这种情况,大多数是使用ApplicationContext扩展点时,向上下文中注册Bean定义导致。这种情况在后续上下文扩展点中详述。正常情况都是没有重复Bean定义,且没有bean正在创建的情形。

总体而言,注册Bean定义的逻辑,即将Bean定义载入BeanFactory的Map中。

Spring源码系列 — BeanDefinition的更多相关文章

  1. Spring源码系列 — BeanDefinition扩展点

    前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...

  2. Spring源码系列 — 注解原理

    前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...

  3. Ioc容器beanDefinition-Spring 源码系列(1)

    Ioc容器beanDefinition-Spring 源码系列(1) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器 ...

  4. 事件机制-Spring 源码系列(4)

    事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...

  5. Ioc容器依赖注入-Spring 源码系列(2)

    Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...

  6. Ioc容器BeanPostProcessor-Spring 源码系列(3)

    Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...

  7. AOP执行增强-Spring 源码系列(5)

    AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...

  8. Spring源码系列 — Bean生命周期

    前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...

  9. Spring源码系列(二)--bean组件的源码分析

    简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...

随机推荐

  1. MySQL EXPLAIN 语句

    对于 MySQL 在执行时来说,EXPLAIN 功能上与 DESCRIBE 一样.实际运用中,后者多用来获取表的信息,而前者多用于展示 MySQL 会如何执行 SQL 语句(Obtaining Exe ...

  2. WPF MVVM,Prism,Command Binding

    1.添加引用Microsoft.Practices.Prism.Mvvm.dll,Microsoft.Practices.Prism.SharedInterfaces.dll: 2.新建文件夹,Vie ...

  3. Windows Server 2012 R2更新(KB2919355)

    必须按以下顺序安装这些KB:clearcompressionflag.exe,KB2919355,KB2932046,KB2959977,KB2937592,KB2938439和KB2934018. ...

  4. SpringBoot(二):SpringBoot 热部署

    1.配置pom: <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g ...

  5. 从0系统学 Android--1.1认识 Android

    一转眼工作也有几年的时间了,一直想沉下心来,再回过头来重新系统的学习一遍 Android.所以就有了这个读书笔记.俗话说温故而知新,下面就请大家再跟着我系统的学习一篇 Android 吧! 这一系列主 ...

  6. ES 25 - Elasticsearch的分页查询及其深分页问题 (deep paging)

    目录 1 分页查询方法 2 分页查询的deep paging问题 1 分页查询方法 在GET请求中拼接from和size参数 // 查询10条数据, 默认从第0条数据开始 GET book_shop/ ...

  7. python 指定字符串位置查找

    指定字符串位置查找 #指定字符查找 s = 'F:/my_pycharm/pycharm_project/CSV表格/10.csv' print(s.find('/')) # 2, 第一个/在2位置 ...

  8. Matplotlib 日期格式转换

    官网链接:https://matplotlib.org/api/dates_api.html#matplotlib.dates.date2num import numpy as np import d ...

  9. 学习51cto中美团中的小知识点--组件实现按需求加载

    1====>vue.20脚手架的创建 cnpm install --global vue-cli 全局安装脚手架 vue init webpack my-project 创建项目 Use ESL ...

  10. Mybatis环境搭建(二)

    1. 创建Maven Project,选择war,修改pom.xml <properties> <!-- JDK版本 --> <java.version>1.8&l ...