本章内容:
- Bean组件、Context组件解析
- BeanFactory的创建
- 初始化Bean实例之前的操作
Bean组件解析
Spring Bean 的创建是典型的工厂模式, 它的顶级接口是BeanFactory。
Bean工厂的类层次关系图:
4个接口,共同定义了Bean 的集合、Bean 之间的关系和Bean 的行为。
Bean定义的类层次关系图:
Bean 的定义完整地描述了在Spring 的配置文件中你定义的< /bean>节点中所有的信息,包括各种子节点。
Context组件分析
ApplicationContext 继承了BeanFactory,这也说明了Spring 容
器中运行的主体对象是Bean 。另外ApplicationContext 继承了Re sourceLoader 接口,使得ApplicationContext 可以访问到任何外部资源。
ApplicationContext 必须要完成以下几件事情:
- 标识一个应用环境。
- 利用Bean Factory 创建Bean 对象。
- 保存对象关系表。
- 能够捕获各种事件。
创建BeanFactory工厂
refresh方法(位于AbstractApplicationContext),整个Spring Bean加载的核心,构建了Bean关系网(也就是Bean Factory)
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 为刷新准备新的context
prepareRefresh();
// 刷新所有BeanFactory子容器
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//创建BeanFactory
prepareBeanFactory(beanFactory);
try {
//设置BeanFactoy的后置处理
postProcessBeanFactory(beanFactory);
//调用BeanFactory的后处理器,这些后处理器是在Bean定义中向容器注册的
invokeBeanFactoryPostProcessors(beanFactory);
//注册Bean的后处理器,在Bean创建过程中调用。
registerBeanPostProcessors(beanFactory);
//对上下文中的消息源进行初始化
initMessageSource();
//初始化上下文中的事件机制
initApplicationEventMulticaster();
//初始化其他的特殊Bean
onRefresh();
//检查监听Bean并且将这些Bean向容器注册
registerListeners();
//实例化所有的(non-lazy-init)单件
finishBeanFactoryInitialization(beanFactory);
//发布容器事件,结束Refresh过程
finishRefresh();
}
catch (BeansException ex) {
//为防止Bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件Bean
destroyBeans();
// 重置 'active'标志
cancelRefresh(ex);
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
首先refresh()方法有几点是值得我们学习的:
1、方法是加锁的,这么做的原因是避免多线程同时刷新Spring上下文
2、尽管加锁可以看到是针对整个方法体的,但是没有在方法前加synchronized关键字,而使用了对象锁startUpShutdownMonitor,这样做有两个好处:
(1)refresh()方法和close()方法都使用了startUpShutdownMonitor对象锁加锁,这就保证了在调用refresh()方法的时候无法调用close()方法,反之亦然,避免了冲突
(2)另外一个好处不在这个方法中体现,但是提一下,使用对象锁可以减小了同步的范围,只对不能并发的代码块进行加锁,提高了整体代码运行的效率
3、方法里面使用了每个子方法定义了整个refresh()方法的流程,使得整个方法流程清晰易懂。
refresh方法主要包含了以下步骤:
( 1 )构建BeanFactory ,以便于产生所需的”演员” 。
( 2 )注册可能感兴趣的事件。
( 3 )创建Bean 实例对象。
( 4 )触发被监听的事件。
创建和配置BeanFactory的核心代码(在obtainFreshBeanFactory()方法中被调用,位于AbstractRefreshableApplicationContext类中)
protected final void refreshBeanFactory() throws BeansException {
//这里判断,如果已经建立了BeanFactory,则销毁并关闭该BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
//这里是创建并设置持有的DefaultListableBeanFactor的地方同时调用
//loadBeanDefinitions再载入BeanDefinition的信息
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing XML document for "
+ getDisplayName(), ex);
}
}
//这就是在上下文中创建DefaultListableBeanFactory的地方,而getInternalParentBeanFactory()
//的具体实现可以
//参看AbstractApplicationContext中的实现,会根据容器已有的双亲IoC容器的信息来生成
// DefaultListableBeanFactory的双亲IoC容器
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
BeanDefinition的载入和解析(以AbstractXmlApplicationContext为例)
//这里是使用BeanDefinitionReader载入Bean定义的地方,因为允许有多种载入方式,虽然用得
//最多的是XML定义的形式,这里通过一个抽象函数把具体的实现委托给子类来完成
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws IOException, BeansException;
//以AbstractXmlApplicationContext为例
//这里是实现loadBeanDefinitions的地方
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
//创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中去,创建BeanFactory
//的过程可以参考上文对编程式使用IoC容器的相关分析,这里和前面一样,使用的也是
DefaultListableBeanFactory
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinition
Reader(beanFactory);
//这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配
//ResourceLoader,因为DefaultResourceLoader是父类,所以this可以直接被使用
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//这是启动Bean定义信息载入的过程
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
接着就是loadBeanDefinitions调用的地方,首先得到BeanDefinition信息的Resource定位,然后直接调用 XmlBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader完成的。因为这里的BeanDefinition是通过XML文件定义的,所以这里使用XmlBeanDefinitionReader来载入BeanDefinition到容器中。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
getConfigLocations会先去找父类中定义的默认配置文件,如果没有,则调用实现类中的getDefaultConfigLocations()方法
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
上面的DEFAULT_CONFIG_LOCATION就是/WEB-INF/applicationContext.xml
public int loadBeanDefinitions(String location, Set actualResources) throws
BeanDefinitionStoreException {
//这里取得 ResourceLoader,使用的是DefaultResourceLoader
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]:
no ResourceLoader available");
}
//这里对Resource的路径模式进行解析,比如我们设定的各种Ant格式的路径定义,得到需要的
//Resource集合,这些Resource集合指向我们已经定义好的BeanDefinition信息,可以是多个文件
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//调用DefaultResourceLoader的getResource完成具体的Resource定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).
getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (int i = 0; i < resources.length; i++) {
actualResources.add(resources[i]);
}
}
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 {
// 调用DefaultResourceLoader的getResource完成具体的Resource定位
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location
[" + location + "]");
}
return loadCount;
}
}
Resource的定位
//对于取得Resource的具体过程,我们可以看看DefaultResourceLoader是怎样完成的
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//这里处理带有classpath标识的Resource
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_
URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 这里处理URL标识的Resource定位
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
//如果既不是classpath,也不是URL标识的Resource定位,则把getResource的
//重任交给getResourceByPath,这个方法是一个protected方法,默认的实现是得到
//一个ClassPathContextResource,这个方法常常会用子类来实现
return getResourceByPath(location);
}
}
}
getResourceByPath会被子类FileSystemXmlApplicationContext实现,这个方法返回的是一个 FileSystemResource对象,通过这个对象,Spring可以进行相关的I/O操作,完成BeanDefinition的定位。
Spring的BeanDefinion是怎样按照Spring的Bean语义要求进行解析并转化为容器内部数据结构的?
这个过程是在registerBeanDefinitions(doc, resource)中完成的。具体的过程是由BeanDefinitionDocumentReader来完成的,这个registerBeanDefinition还对载入的Bean的数量进行了统计。
具体的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate(是一个类)中完成的。这个类里包含了对各种Spring Bean定义规则的处理。解析完成以后,会把解析结果放到BeanDefinition对象中并设置到BeanDefinitionHolder中去。
beanClass、description、lazyInit这些属性都是在配置bean时经常碰到的,都集中在这里。这个BeanDefinition是IoC容器体系中非常重要的核心数据结构。通过解析以后,这些数据已经做好在IoC容器里大显身手的准备了。
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
//这里只读取定义的<bean>中设置的class名字,然后载入到BeanDefinition中去,只是做个
//记录,并不涉及对象的实例化过程,对象的实例化实际上是在依赖注入时完成的
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//这里生成需要的BeanDefinition对象,为Bean定义信息的载入做准备
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//这里对当前的Bean元素进行属性解析,并设置description的信息
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele,
DESCRIPTION_ELEMENT));
//从名字可以清楚地看到,这里是对各种<bean>元素的信息进行解析的地方
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析<bean>的构造函数设置
parseConstructorArgElements(ele, bd);
//解析<bean>的property设置
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
//下面这些异常是在配置Bean出现问题时经常会看到的,原来是在这里抛出的这些检查是在
//createBeanDefinition时进行的,会检查Bean的class设置是否正确,比如这个类是否能找到
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 {
this.parseState.pop();
}
return null;
}
BeanDefinition在IoC容器中的注册
在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的,这个HashMap的定义在DefaultListableBeanFactory中可以看到,如下所示。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
有用到synchronized关键字,当有Bean正在创建,对beanDefinitionMap加锁,再进行bean定义写入
完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在使用的IoC容器DefaultListableBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用了,它们都在beanDefinitionMap里被检索和使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器建立依赖反转的基础,有了这些基础数据,下面我们看一下在IoC容器中,依赖注入是怎样完成的。
创建bean实例之前的一些操作
PrepareBeanFactory方法:
如果自定义的bean中没有名为”systemProperties”和”systemEnvironment”的Bean,则注册两个Bean,Key为”systemProperties”和”systemEnvironment”,Value为Map,这两个Bean就是一些系统配置和系统环境信息。
invokeBeanFactoryPostProcessors方法:
这个是整个Spring流程中非常重要的一部分,是Spring留给用户的一个非常有用的扩展点,BeanPostProcessor接口针对的是每个Bean初始化前后做的操作而BeanFactoryPostProcessor接口针对的是所有Bean实例化前的操作,注意用词,初始化只是实例化的一部分,表示的是调用Bean的初始化方法,BeanFactoryPostProcessor接口方法调用时机是任意一个自定义的Bean被反射生成出来前。
通常用法是在BeaFactory完成依赖注入后做一些后续处理工作。(http://blog.youkuaiyun.com/wubai250/article/details/8239921)
- 有一些选项在设定好后通常就不会去变更,而有一些选项可能得随时调整,这时候如果能提供一个更简洁的设定,提供一些常用选项在其中随时更改,这样的程序在使用时会更有弹性
- 一种情况是,XML定义档中定义了一些低权限程序使用人员可以设定的选项,然而高权限的程序管理员可以透过属性文件的设定,来推翻低权限程序使用人员的设定,以完成高权限管理的统一性。
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
List<String> orderedPostProcessorNames = new ArrayList<String>();
List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
这里分出了三个List,表示开发者可以自定义BeanFactoryPostProcessor的调用顺序,具体为调用顺序为:
- 如果BeanFactoryPostProcessor实现了PriorityOrdered接口(PriorityOrdered接口是Ordered的子接口,没有自己的接口方法定义,只是做一个标记,表示调用优先级高于Ordered接口的子接口),是优先级最高的调用,调用顺序是按照接口方法getOrder()的实现,对返回的int值从小到大进行排序,进行调用
- 如果BeanFactoryPostProcessor实现了Ordered接口,是优先级次高的调用,将在所有实现PriorityOrdered接口的BeanFactoryPostProcessor调用完毕之后,依据getOrder()的实现对返回的int值从小到大排序,进行调用
- 不实现Ordered接口的BeanFactoryPostProcessor在上面的BeanFactoryPostProcessor调用全部完毕之后进行调用,调用顺序就是Bean定义的顺序
registerBeanPostProcessors方法
整体代码思路和invokeBeanFactoryPostProcessors方法类似,但是这里不会调用BeanPostProcessor接口的方法,而是把每一个BeanPostProcessor接口实现类实例化出来并按照顺序放入一个List中,到时候按顺序进行调用。
具体代码思路可以参考invokeBeanFactoryPostProcessors,这里就根据代码总结一下BeanPostProcessor接口的调用顺序:
- 优先调用PriorityOrdered接口的子接口,调用顺序依照接口方法getOrder的返回值从小到大排序
- 其次调用Ordered接口的子接口,调用顺序依照接口方法getOrder的返回值从小到大排序
- 接着按照BeanPostProcessor实现类在配置文件中定义的顺序进行调用
- 最后调用MergedBeanDefinitionPostProcessor接口的实现Bean,同样按照在配置文件中定义的顺序进行调用
initMessageSource方法
initMessageSource方法用于初始化MessageSource,MessageSource是Spring定义的用于实现访问国际化的接口
initApplicationEventMulticaster方法
初始化上下文事件广播器
onRefresh方法
一个模板方法,重写它的作用是添加特殊上下文刷新的工作,在特殊Bean的初始化时、初始化之前被调用。
把loc 容器比作一个箱子,在这个箱子里有若干个球的模子,可以用这些模子来造很多种不同的球,还有一个造这些球模的机器,这个机器可以产生球模。那么它们的对应关系就是BeanFactory ,即那个造球模的机器;球模就是Bean ,而球模造出来的球就是Bean
的实例。前面所说的几个扩展点又在什么地方呢?
BeanFactoryPostProcessor 对应到当造球模被造出来时, 此时你将有机会对其做出适当的修正,也就是它可以帮你修改球模。
而InitializingBean 和DisposableBean 是在球模造球的开始和结束阶段,你可以完成一些预备和扫尾工作。
BeanPostProcessor 可以让你对球模造出来的球做出适当的修正。最后还有一个FactoryBean ,它可是一个神奇的球模。这个球模不是预先就定型的,而是由你来确定它的形状。既然你可以确定这个球模型的形状,那么它造出来的球肯定就是你想要的球了,这样在这个箱子里面可以发现所有你想要的球。