Spring上下文和容器
Core Container模块是Spring整个架构的根基,其核心概念是BeanFactory,也正是这个概念让Spring成为一个容器,帮助Spring管理Bean,并提供DI(依赖注入)功能来实现对Bean的依赖管理,使用配置方式来达到与业务代码及框架代码的分离。
Context模块即Spring上下文模块(也叫Spring Context模块),是Core Container模块的子模块,它让Spring真正成为一个可执行框架。这个模块扩展实现了BeanFactory,为Spring的扩展和架构继承提供了非常多的可能,比如校验框架、调度框架、缓存框架、模版渲染框架 等等。
一、Spring上下文的设计
Spring Context模式是Spring Core Container模块中的子模块。下面说说核心抽象类的职责。
(1)ApplicationContext是整个容器的基本功能定义类,继承了BeanFactory,这说明容器也是工厂的多态实现。其实它利用了代理的设计方法,内部持有一个BeanFactory实例,这个实例替它执行BeanFactory接口定义的功能。
(2)AbstractApplicationContext是整个容器的核心处理类,是真正的Spring容器的执行者,在内部使用了模版方法,实现了高复用
高扩展,实现了Spring的启动、停止、刷新、事件推送、BeanFactory方法的默认实现及虚拟机回调的注册等。
(3)GenericApplicationContext是Spring Context模块中最容易构建Spring环境的实体类,涵盖了Spring Context的核心功能,在不需要特殊定制的场景下可以实现开箱即用。AnnotationConfigApplicationContext完美利用了GenericApplicationContext的封装性和对外简单性,如果想扩展适合自己业务的轻量级Spring容器,使用GenericApplicationContext这个基类则会非常容易上手。AnnotationConfigApplicationContext的构造方法先传入一个class数组,在创建一个可执行的上下文实例来构造一个可运行的Spring运行环境,使用起来非常简便。
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
// 实例化注解Bean定义读取实例,并按照class路径扫描Bean实例
this();
// 注册当前这个class数组,解析并添加这个Bean的描述到BeanFactory中
register(annotatedClasses);
// 启动Spring容器
refresh();
}
4)AbstractRefreshableApplicationContext是XmlWebApplicationContext的核心父类,如果当前上下文持有BeanFactory,则关闭当前BeanFactory,然后为上下文生命周期的下一个阶段初始化一个新的BeanFactory,并且在创建新容器时仍然保持对其父容器的引用。
二、Spring容器BeanFactory的设计
Spring的核心功能就是实现对Bean的管理,比如Bean的注册、注入、依赖等。而Spring容器提供了依赖注入这个特征,以实现Spring容器对Bean的管理,而且使用IoC实现了对Bean的配置与实际应用代码的隔离。其中,Core Container模块的核心概念就是BeanFactory,它是所有Spring应用的核心。因为Spring的核心模型就是Bean模型,所以需要在管理Spring Bean的基础上保证Spring应用的运行。
BeanFactory接口是Bean容器设计中基本的职责定义接口,定义了按照名称、参数、类型等几个维度获取、判断Bean实例的职能。
三、Spring父子上下文与容器
从ApplicationContext中可以看出,Spring提供了为当前BeanFactory和ApplicationContext设置父子引用的功能方法,BeanFactory像一个单向链表节点一样支持Spring的多容器场景。
/**
* Return the parent context, or {@code null} if there is no parent
* and this is the root of the context hierarchy.
* @return the parent context, or {@code null} if there is no parent
*/
@Nullable
ApplicationContext getParent();
ApplicationContext接口对外提供获取父上下文的方法,既然能对外获取父上下文,那么肯定有上下文属性的设置方法或者初始化方法。最常用的是用构造方法和set方法手工指定。
/**
* Create a new AbstractApplicationContext with the given parent context.
* @param parent the parent context
*/
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
在SpringMVC环境中存在Spring父子容器时,子容器可以复用父容器的Bean实例从而避免重复创建。
在使用SpringMVC时,如下配置会出现在web.xml中。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>Hello</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value>
</init-param>
<!-- 服务器已启动就加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
由于在web.xml中<listener-class>标签的加载早于<servlet>标签的加载,所以ContextLoaderListener在启动后会先创建一个Spring容器,之后在Dispatcher启动时还会实例化一个容器。
HttpServletBean是HttpServlet的子类,它重写了init方法,调用如下方法进行初始化。
@Override
protected final void initServletBean() throws ServletException {
// ..省略..
try {
// 创建Spring Web容器
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
// ..省略..
}
创建Spring Web容器:
protected WebApplicationContext initWebApplicationContext() {
// 从Servlet上下文中属性中获取Listener中的容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
/* 如果父容器是一个web容器并且没有启动,
则此时运行当前容器并且设置它的父容器,
也就是说在XML中配置了两个Spring Servlet,仍然可以互相引用bean */
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
// 创建当前Servlet创建的web容器,并且把父容器创建在其中
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// 实例化当前容器
onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
AbstractRefreshableApplicationContext是Spring Web容器的核心基类,在SpringAbstractAplicationContext启动时调用refreshBeanFactory方法。
@Override
protected final void refreshBeanFactory() throws BeansException {
//如果当前已经存在工厂,则销毁工厂中的Bean,关闭当前BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建Bean工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//设置当前工厂Bean是否允许Bean定义重写覆盖
//设置当前BeanFactory是否允许Bean循环引用
customizeBeanFactory(beanFactory);
//按照指定的配置把Bean定义加载到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);
}
}
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
@Nullable
protected BeanFactory getInternalParentBeanFactory() {
//把父容器中的工厂作为父工厂放在当前容器工厂中
return (getParent() instanceof ConfigurableApplicationContext ?
((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent());
}
由于SpringMVC中的容器之间存在关联(也就是父子容器),所以容器之间可以互相访问,子容器也可以共用父容器的Bean。但父容器不能共用子容器的Bean,这是因为当父容器已经启动时,子容器还没有实例化启动,这时如果父容器引用子容器的Bean,则是不可能正常运行的。