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 接口位于类结构树的顶端,它最主要的方法就是 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 配置文件:
然后使用 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 类体系结构
接口 | 说明 |
---|---|
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() 可关闭应用上下文。 |
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:
org.codehaus.groovy
groovy-all
${groovy.version}
然后新增配置文件:
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 扩展了 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();
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:
contextConfigLocation
classpath*:spring-*.xml
org.springframework.web.context.ContextLoaderListener
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 配置信息 。
log4jConfigLocation
/WEB-INF/log4j.properties
org.springframework.web.util.Log4jConfigListener
注意: Log4jConfigListener 必须放置在 ContextLoaderListener 前面,保证前者先启动,完成装载 Log4J 配置文件并初始化 Log4J 引擎的工作,紧接着后者再启动 。
2.3.2 注解方式
也可以使用带注解 @Configuration 的 Java 类来提供配置信息:
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
net.deniro.config1,net.deniro.config2
这时的 ContextLoaderListener 如果发现了 contextClass 上下文参数,就会使用参数所指定的 AnnotationConfigWebApplicationContext 来初始化容器,该实现类会根据 contextConfigLocation 上下文参数指定的 @Configuration 的配置类所提供的配置信息来初始化容器 。
2.3.3 Groovy DSL 方式
也可以使用 Groovy DSL 方式,原理与注解方式类似。
contextClass
org.springframework.web.context.support.GroovyWebApplicationContext
contextConfigLocation
classpath*:spring-*.grovvy
2.4 父子容器
通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean ,但父容器不能访问子容器的 Bean。 在容器内, Bean 的 id 必须是唯一的,但子容器可以拥有一个和父容器 id 相同的 Bean。 父子容器层级体系增强了 Spring 容器架构的扩展性和灵活性,因此第三方可以通过编程的方式,为一个已经存在的容器添加一个或多个特殊用途的子容器,以提供一些额外的功能 。
Spring 使用父子容器的特性实现了很多能力,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中 。 这样,展现层 Bean 就可以引用业务层和持久层的 Bean ,而业务层和持久层的 Bean 则看不到展现层的 Bean。