说说 Spring 框架中的 BeanFactory 和 ApplicationContext

Spring 通过一个配置文件来描述 Bean 及 Bean 之间的依赖关系,利用 Java 的反射功能实例化 Bean 并建立 Bean 之间的依赖关系 。Sprig 的 IoC 容器在完成这些底层工作的基础上,还提供了 Bean 实例缓存 、 生命周期管理 、Bean 实例代理 、 事件发布 、 资源装载等高级服务 。

Bean 工厂( com.springframework.beans.factory.BeanFactory )是 Spring 框架中最核心的接口,它提供了高级 IoC 的配置机制 。BeanFactory 使管理不同类型的 Java 对象成为可能。而

应用上下文( com.springframework.context.ApplicationContext )建立在 BeanFactory 基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用 。

我们一般称 BeanFactory 为 IoC 容器,而称 ApplicationContext 为应用上下文或 Spring 容器 。

BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身; ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都可以直接使用 ApplicationContext。

Spring 框架是生成类对象的工厂,而被创建的类对象本身也可能是一个工厂,这就形成了 “创建工厂的工厂”。

1 BeanFactory

BeanFactory 是类的通用工厂,它可以创建并管理各种类的对象,这些类就是 POJO,Spring 称这些被创建并管理的类对象为 Bean。

1.1 类体系结构

BeanFactory 有众多的实现,在 Spring 3.2 之前的版本中,最常用的是 XmlBeanFactory,现已被废弃。建议使用 XmlBeanDefinitionReader 与 DefaultListableBeanFactory。

BeanFactory 类继承体系

BeanFactory 接口位于类结构树的顶端,它最主要的方法就是 getBean(String beanName) ,该方法从容器中返回特定名称的 Bean , BeanFactory 的功能通过其他接口而得到不断扩展。

接口 说明
ListableBeanFactory 该接口定义了访问容器中 Bean 基本信息的若干方法,如:查看 Bean 的个数, 获取某一类型 Bean 的配置名,或查看容器中是否包含某一 Bean。
HierarhicalBeanFactory 父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器。
ConfigurableBeanFactory 该接口增强了IoC容器的可定制性,它定义了设置类装载器、属性 编辑器、容器初始化后置处理器等方法。
AutowireCapableBeanFactory 定义了将容器中的 Bean 按某种规则(如:按名称匹配 、 按类型匹配) 进行自动装配的方法。
SingletonBeanFactory 定义了允许在运行期间向容器注册单实例 Bean 的方法。
BeanDefinitionRegistry Spring 配置文件中每一个 Bean 节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionResgistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。

1.2 初始化

下面我们在 Spring 配置文件中配置 Bean People,然后通过 BeanFactory 加载配置文件,启动 Ioc 容器。

Spring 配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="people" class="net.deniro.springBoot.spring4.IoC.People"
          p:name="deniro"
          p:age="25"
            />
</beans>

然后使用 DefaultListableBeanFactory 与 XmlBeanDefinitionReader 来启动 Ioc 容器:

ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResource("classpath:beans.xml");
System.out.println("getURL:" + resource.getURL());

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);

System.out.println("Bean 工厂已完成初始化");

People people = factory.getBean("people", People.class);
System.out.println("People 已被创建");
System.out.println(people.toString());

XmlBeanDefinitionReader 通过 Resource 来装载 Spring 的配置信息并启动 Ioc 容器,然后就可以通过 BeanFactory#getBean(name) 获取 Bean 咯。Bean 初始化操作发生在第一次调用时。对于单实例的 Bean 来说, BeanFactory 会缓存 Bean 实例, 所以第二次使用 getBean() 方法时就会直接从 IoC 容器的缓存中获取 Bean 的实例。

注意:初始化 BeanFactory 时必须为其提供一种日志框架, 一般是使用 Log4J ,即在类路径下提供 Log4J 的配置文件,这样才能正常启动 Spring 容器。

2 ApplicationContext

ApplicationContext 由 BeanFactory 派生而来,提供了很多实际应用的功能 。 在 BeanFactory 中,很多功能需要以编程的方式实现,而在 ApplicationContext 中则可以通过配置的方式来实现。

2.1 ApplicationContext 类体系结构

ApplicationContext 接口继承体系
接口 说明
ApplicationEventPublisher 让容器拥有了发布应用上下文事件的功能,包括容器启动事件 、关闭事件等。实现了 ApplicationListener 事件监听接口的 Bean 可以接收到容器事件, 并对容器事件进行响应处理。在 ApplicationContext 抽象实现类 AbstractApplicationContext 中存在一个 ApplicationEventMulticaster,它负责保存所有的监听器,以便在容器产生上下文事件时通知这些事件监听者。
MessageSource 为容器提供了 i18n 国际化信息访问的功能。
ResourcePatternResolver 所有 ApplicationContext 实现类都实现了类似于 PathMatchingResourcePatternResolver 的功能, 可以通过带前缀 Ant 风格的资源类文件路径来装载 Spring 的配置文件。
LifeCycle 它提供了 start() 和 stop() 两个方法, 主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean ,以达到管理和控制 JMX、 任务调度等目的。
ConfigurableApplicationContext 它扩展了 ApplicationContext,让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力。上下文关闭时,调用 refresh() 即可启动上下文;如果已经启动,则调用 refresh() 可清除缓存并重新加载配置信息;调用 close() 可关闭应用上下文。
ApplicationContext 类继承体系

2.1.1 XML 配置

初始化 ApplicationContext 时,根据配置文件的所在路径,来选择 ApplicationContext 的实现类。

ApplicationContext 的主要实现类是:

  • ClassPathXmlApplicationContext - 从类路径加载配置文件。
  • FileSystemXmlApplicationContext - 从文件系统加载配置文件。
//从类路径加载配置文件
ApplicationContext context1 = new ClassPathXmlApplicationContext("beans.xml");
//从文件系统加载配置文件
ApplicationContext context2 = new FileSystemXmlApplicationContext("d:/beans.xml");

还可以指定一组的配置文件,Spring 会自动将多个配置文件中的内容整合起来:

//指定一组的配置文件
ApplicationContext context3 = new ClassPathXmlApplicationContext(new
        String[]{"beans.xml", "beans2.xml"});

注意:ClassPathXmlApplicationContext 与 FileSystemXmlApplicationContext 也可以显式指定带资源类型前缀的路径。

ApplicationContext 与 BeanFactory 初始化之间的区别:

  • ApplicationContext - 在初始化应用上下文时,会实例化所有单实例的 Bean,所以相对来说,初始化时间会比 BeanFactory 稍长,不过稍后的调用没有 “第一次惩罚” 的问题。
  • BeanFactory - 在初始化容器时,并未实例化 Bean。直到 Bean 被访问时,才会被实例化。

2.1.2 类注解配置

Spring 支持基于类注解的配置方式,主要功能来自于 Spring 的一个名为 JavaConfig 子项目,目前 JavaConfig 已经升级为 Spring 核心框架的一部分 。 一个标注 @Configuration 注解的 POJO 即可提供 Spring 所需的 Bean 配置信息 。

@Configuration//表示这个类包含配置信息
public class Beans {

    //定义 Bean
    @Bean(name = "people")
    public People build() {
        People people = new People();
        people.setAge(25);
        people.setName("deniro");
        return people;
    }
}

与 XML 的配置方式相比,基于类注解的配置方式可以让开发者控制 Bean 的初始化过程,所以更加灵活。

基于类注解的 Bean 配置,需要使用 AnnotationConfigApplicationContext 来启动 Spring 容器:

ApplicationContext context = new AnnotationConfigApplicationContext(Beans.class);
People people = context.getBean("people", People.class);
Assert.assertNotNull(people);

2.1.3 Groovy DSL 配置

Spring 4.x 支持使用 Groovy DSL 对 Bean 进行配置,通过它可以实现复杂、灵活的配置逻辑。

首先在 pom.xml 中引入 Groovy:

<!-- groovy -->
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>${groovy.version}</version>
</dependency>

然后新增配置文件:

package net.deniro.springBoot.spring4.factory

import net.deniro.springBoot.spring4.IoC.People

beans {
    people(People) {//格式:名字(类型)
        name = "deniro"//注入属性
        age = 25
    }
}

最后使用 GenericGroovyApplicationContext 来启动容器:

ApplicationContext context = new GenericGroovyApplicationContext("classpath:groovy-beans.groovy");
People people = (People) context.getBean("people");
Assert.assertNotNull(people);
Assert.assertEquals(people.getName(), "deniro");

2.2 WebApplicationContext 类体系结构

WebApplicationContext 专用于 web 应用 , 它允许从相对于 web 根目录的路径中装载配置文件完成初始化工作,从 WebApplicationContext 中可以获得 ServletContext 的引用,整个 Web 应用上下文对象将作为属性放置在 ServletContext 中,以便 web 应用可以访问 spring 上下文 ,spring 中提供 WebApplicationContextUtils 的 getWebApplicationContext(ServletContext src) 方法从 ServletContext 中获取 WebApplicationContext 实例。

非 Web 应用环境中,Bean 只有 singleton 与 prototype 两种作用域。而在 WebApplicationContext 中,它为 Bean 添加了另外三种作用域:request、session 与 global session。

WebApplicationContext 类继承体系

WebApplicationContext 扩展了 ApplicationContext。WebApplicationContext 定义了一个常量 ROOT_WEB_APPLICATION_ CONTEXT_ATTRIBUTE ,在上下文启动时, WebApplicationContext 实例以这个常量为键,放置在 ServletContext 的属性列表中,因此我们可以直接通过以下语句从 Web 容器中获取 WebApplicationContext :

//从 servletContext 中获取 WebApplicationContext
WebApplicationContext wac= (WebApplicationContext) servletContext.getAttribute(WebApplicationContext
        .ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

//从 WebApplicationContext 中获取 servletContext
ServletContext sc=wac.getServletContext();
Spring 的 web 应用上下文与 web 容器上下文之间实现互访

ConfigurableWebApplicationContext 扩展了 WebApplicationContext ,它允许通过配置的方式实例化 WebApplicationContext ,它定义了两个重要的方法:

方法名 说明
setServletContext(ServletContext servletContext) 为 Spring 设置 Web 应用上下文,以便两者整合
setConfigLocations(String[] configLocations) 设置 Spring 配置文件地址,一般情况下,配置文件地址是相对于 Web 根目录的地址,形如 /WEB-INF/config.xml 。 但用户也可以使用带资源类型前缀的地址,形如 classpath:net/deniro/beans.xml。

2.3 初始化 WebApplicationContext

WebApplicationContext 需要 ServletContext 实例,它必须在拥有 Web 容器的前提下才能完成启动工作 。 我们可以在 web.xml 中配置自启动的 Servlet 或定义 Web 容器监听器( ServletContextListener ),借助这两者中之一,就可以启动 Spring Web 应用上下文 。


注意:所有版本的 Web 容器都支持自启动的 Servlet ,但只有 Servlet 2.3 及以上版本的 Web 容器才支持 Web 容器监听器 。 但也有些 Web 容器是例外,比如 Weblogic 8.1、WebSphere 5.x、Oracle OC4J 9.0 等。


Spring 提供了用于启动 WebApplicationContext 的 Servlet 和 Web 容器监听器:

  • org.springframework.web.context.ContextLoaderServlet。
  • org.springframework.web.context.ContextLoaderListener。

2.3.1 XML 方式

下面使用 ContextLoaderListener 启动 WebApplicationContext:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <!-- 指定 spring 配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:spring-*.xml</param-value>
    </context-param>

    <!-- web 容器监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


</web-app>

ContextLoaderListener 通过 Web 容器上下文参数 contextConfigLocation 获取 Spring 配置文件的所在位置 。 可以指定多个配置文件,任意选用逗号 、空格或冒号来分隔它们。 对于未带资源类型前缀的文件路径, WebApplicationContext 会默认这些路径相对于 Web 的部署根路径 。 当然,也可以采用带资源类型前缀的路径配置,比如这里的 classpath*:spring-*.xml

如果在不支持容器监听器的低版本 Web 容器中,我们可采用 ContextLoaderServlet 完成启动工作,因为 Spring4 已经不再支持这个类,所以我们也就不再累述咯。

因为 WebApplicationContext 需要使用到日志,所以我们把 Log4J 的配置文件放置到类路径 WEB-INF/classes 下,这样 Log4J 引擎即可顺利启动 。 如果 Log4J 配置文件放置在其他位置,就必须在 web.xml 指定 Log4J 配置文件位置 。Spring 为启用 Log4J 引擎提供了两个类似于启动 WebApplicationContext 的实现类: Log4jConfigServlet(Spring4 中不再支持) 和 Log4jConfigListener,不管采用哪种方式都必须保证能够在装载 Spring 配置文件前先装载 Log4J 配置信息 。

<!-- 指定 log4j 配置文件-->
<context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<!-- Log4j 监听器-->
<listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

注意: Log4jConfigListener 必须放置在 ContextLoaderListener 前面,保证前者先启动,完成装载 Log4J 配置文件并初始化 Log4J 引擎的工作,紧接着后者再启动 。

2.3.2 注解方式

也可以使用带注解 @Configuration 的 Java 类来提供配置信息:

<!-- 使用带注解 @Configuration 的 Java 类来提供配置信息-->
<!-- 指定 AnnotationConfigWebApplicationContext 来启动容器-->
<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>net.deniro.config1,net.deniro.config2</param-value>
</context-param>

这时的 ContextLoaderListener 如果发现了 contextClass 上下文参数,就会使用参数所指定的 AnnotationConfigWebApplicationContext 来初始化容器,该实现类会根据 contextConfigLocation 上下文参数指定的 @Configuration 的配置类所提供的配置信息来初始化容器 。

2.3.3 Groovy DSL 方式

也可以使用 Groovy DSL 方式,原理与注解方式类似。

<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.GroovyWebApplicationContext</param-value>
</context-param>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:spring-*.grovvy</param-value>
</context-param>

2.4 父子容器

通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean ,但父容器不能访问子容器的 Bean。 在容器内, Bean 的 id 必须是唯一的,但子容器可以拥有一个和父容器 id 相同的 Bean。 父子容器层级体系增强了 Spring 容器架构的扩展性和灵活性,因此第三方可以通过编程的方式,为一个已经存在的容器添加一个或多个特殊用途的子容器,以提供一些额外的功能 。

Spring 使用父子容器的特性实现了很多能力,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中 。 这样,展现层 Bean 就可以引用业务层和持久层的 Bean ,而业务层和持久层的 Bean 则看不到展现层的 Bean。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容