文章目录
-
- 了解Ioc容器
- BeanFactory
-
- BeanFactory 的类体系结构
- 初始化 BeanFactory
- ApplicationContext
-
- ApplicationContext 类体系结构
- ApplicationContext 初始化
-
- 基于 Xml 配置实现
- 基于 JavaConfig 配置实现
- WebApplicationContext
-
- WebApplicationContext 类体系结构
- WebApplicationContext 初始化
-
- 使用 ContextLoaderListener 启动 WebApplicationContext
- 使用 SpringServletContainerInitializer 启动 WebApplicationContext
- 父子容器
- 参考
了解Ioc容器
- Spring 通过一个配置文件描述 Bean 与 Bean 之间的依赖关系,利用反射实例化 Bean 并且建立 Bean 之间的依赖关系。Spring Ioc 容器还提供了 Bean 实例缓存、生命周期管理、Bean 实例代理、事件发布、资源装载等服务
- BeanFactory(Bean 工厂)是 Spring 框架最核心的接口,提供了高级 Ioc 的配置机制;ApplicationContext(应用上下文)建立在 BeanFactory 的基础之上,提供更多的功能,如国际化、框架事件体系,更简单的创建实际应用。一般来说,称 BeanFactory 为 ico 容器,而 ApplicationContext 为应用上下文,其实本质上两者都是 Spring 的 Ioc 容器
- BeanFactory 是 Spring 框架的基础,面向 Spring 本身;ApplicationContext 面向使用 Spring 的开发者,建议在应用场合直接使用 ApplicationContext
BeanFactory
BeanFactory 的类体系结构
- BeanFactory 接口是基础接口,主要方法为 getBean(),从容器获取Bean 对象。BeanFactory 的功能通过其他接口得到扩展。
类名 | 描述 |
---|---|
ListableBeanFactory | 该接口定义了访问容器中 Bean 基本信息的若干方法,如查看 Bean 的个数(getBeanDefinitionCount),获取某一类型 Bean 的配置名称(getBeanNamesForType) |
HierarchicalBeanFactory | 父子层级关联的容器体系,子容器可以通过接口方法访问父容器。如 Spring MVC 展现层位于子容器中,业务层和持久层位于父容器中 |
ConfigurableBeanFactory | 这个一个重要接口。增强了Ioc 容器的可定制。定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法 |
AutowireCapableBeanFactory | 定义了兼容其中的 Bean 自动装配方法 |
SingletonBeanRegistry | 定义了允许在运行期向容器注册单实例 Bean 的方法 |
BeanDefinitionRegistry | spring 配置文件中每一个 节点元素在 spring 容器里都通过一个 BeanDefinition 对象表示,它描述了Bean的配置信息。而 BeanDefinitionRegistry 接口提供向先容器手工注册 BeanDefinition 对象的方法 |
- BeanFactory 接口类图
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiMDOzQjMwYDOzADN2EzM1IWNxQTYhZ2NkVGMwImZkBTN9kXZLVmchh2cmQWYvxmb39GZ9Q2boRXZt9zN5IkR0IDR4UkNyQzNxkjQ3YTO0ATRDljR3gDRFNjRE9CXlxWam9CXsFmbvNnclB3LclGch9CXzdXevwVbvNmLvFGZ19WeuUGdv52Lc9CX6MHc0RHaiojIsJye.jpg)
初始化 BeanFactory
- 初始化 BeanFactory 容器:通过 DefaultListableBeanFactory 、XmlBeanDefinitionReader 实现。
- BeanFactory 容器初始化的时候,不会实例化 bean实例,第一次访问 Bean 才实例化
- 对于单例(singleton)的 Bean 来说,BeanFactory 会做缓存,所以第二次 getBean 的时候直接从容器中获取。Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例(singleton)的缓存器,是 Map 实现的,以 Bean 的 beanName 为键进行保存
- 示例
- Bean 对象类
public interface BeanContainer {
void done();
}
class BeanContainerImpl implements BeanContainer {
public BeanContainerImpl() {
System.out.println("BeanContainerImpl 被实例化了");
}
@Override
public void done() {
System.out.println("BeanContainerImpl.do() 方法被调用~~");
}
}
- 配置文件中,定义一个bean:
<bean name="beanContainer" class="com.learning.spring.ioc.container.BeanContainerImpl"/>
- BeanFactory 初始化代码:
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 使用 ClassPathContextResource
Resource resource = resolver.getResource("spring-context.xml");
// XmlBeanFactory factory = new XmlBeanFactory(resource); 废弃,不用
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
System.out.println("BeanFactory 容器初始化完成,但是没有实例化bean");
BeanContainer beanContainer = factory.getBean("beanContainer", BeanContainer.class);
beanContainer.done();
// 对于单例(singleton)的Bean来说,会做缓存,锁着第二次 getBean 的时候直接从容器中获取
// 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例(singleton)的缓存器,是Map实现的。
beanContainer = factory.getBean("beanContainer", BeanContainer.class);
beanContainer.done();
输出:
BeanFactory 容器初始化完成,但是没有实例化bean
BeanContainerImpl 被实例化了
BeanContainerImpl.do() 方法被调用~~
BeanContainerImpl.do() 方法被调用~~
ApplicationContext
ApplicationContext 类体系结构
- 继承了 ListableBeanFactory、HierarchicalBeanFactory接口,又扩展了 BeanFactory 功能,这些扩展的功能接口如下:
接口 | 描述 |
---|---|
ApplicationEventPublisher | 让容器拥有发布上下文事件的功能,包括容器的启动事件、关闭事件。(具体见spring 事件监听相关博文) |
ResourcePatternResolver | 让所有的 ApplicationContext 实现了类似 PathMatchingResourcePatternResolver 的功能。通过带前缀的 Ant 风格的资源文件路径装载 spring的配置文件 |
MessageSource | 为应用提供 i18n 国际化 |
Lifecycle | 该接口提供start()、stop() 两个方法,主要用于控制异步处理过程。该接口被 ConfigurableApplicationContext 实现,会将start\stop的信息传递给容器中所有实现了该接口的Bean,以达到管理与控制JMX、任务调度等目的 |
- ApplicationContext 类继承体系
接口与类 | 描述 |
---|---|
ConfigurableApplicationContext | 该接口扩展了 ApplicationContext,新增了两个主要的方法,refresh()和close(),让ApplicationContext 具有启动、刷新、关闭应用上下文的功能。 |
ClassPathXmlApplicationContext | Xml(Sechma)配置实现的容器扩展,默认配置文件放置在类路径下, 可以省略。如果配置文件放置在文件系统中,需要显示的使用带资源类型前缀的路径,或者直接使用 FileSystemXmlApplicationContext |
AnnotationConfigApplicationContext | Java Config (Java-based)实现的容器扩展 |
ApplicationContext 初始化
- ApplicationContext 在初始化应用上下文时就实例化所有单实例(singleton)的 Bean,因此初始化的时间会比 beanFactory 长
- Spring 支持基于注解的配置方式,是基于 JavaConfig 实现。使用 @Configuration、 @Bean 注解实现,比 xml 文件配置更加灵活(详细的见相关博文)
基于 Xml 配置实现
- Bean 对象类
public interface BeanContainer {
void done();
}
class BeanContainerImpl implements BeanContainer {
public BeanContainerImpl() {
System.out.println("BeanContainerImpl 被实例化了");
}
@Override
public void done() {
System.out.println("BeanContainerImpl.do() 方法被调用~~");
}
}
- Xml 配置:定义一个bean
<bean name="beanContainer" class="com.learning.spring.ioc.container.BeanContainerImpl"/>
- 容器初始化:与 BeanFactory 的初始化输出可以做个对比
// 一般xml配置,在classpath下优先使用 ClassPathXmlApplicationContext,spring-context.xml 等同于 classpath:spring-context.xml
// 初始化的容器会实例化bean,所以一开始启动会比 BeanFactory 慢一点
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
System.out.println("初始化 ApplicationContext 完成,同时实例化 bean");
BeanContainer beanContainer = context.getBean("beanContainer", BeanContainer.class);
beanContainer.done();
((ClassPathXmlApplicationContext) context).close();
输出:
BeanContainerImpl 被实例化了
初始化 ApplicationContext 完成,同时实例化 bean
BeanContainerImpl.do() 方法被调用~~
基于 JavaConfig 配置实现
- Bean 对象类
public interface BeanContainer {
void done();
}
class BeanContainerImpl implements BeanContainer {
public BeanContainerImpl() {
System.out.println("BeanContainerImpl 被实例化了");
}
@Override
public void done() {
System.out.println("BeanContainerImpl.do() 方法被调用~~");
}
}
- JavaConfig 配置:使用 @Configuration( 标签)、@Bean(标签)
@Configuration
public class AnnotationConfig {
@Bean(name = "annotationBean")
public BeanContainer beanContainer() {
return new BeanContainerImpl();
}
}
- 容器初始化
// AnnotationConfig 配置类,相当于<beans> 标签
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class);
System.out.println("初始化 AnnotationConfigApplicationContext 完成, 同时实例化 bean");
BeanContainer beanContainer = context.getBean("annotationBean", BeanContainer.class);
beanContainer.done();
context.close();
输出:
BeanContainerImpl 被实例化了
初始化 AnnotationConfigApplicationContext 完成
BeanContainerImpl.do() 方法被调用~~
WebApplicationContext
WebApplicationContext 类体系结构
- WebApplicationContext 是专门为 Web 应用准备,扩展了 ApplicationContext 的功能,实现了与 ServletContext 的相互访问
功能 | 描述 |
---|---|
加载配置路径 | 默认从Web根目录下加载配置文件,可以显示使用资源文件前缀,如 classpath:XXX |
增加 Bean作用域 | 在非 web 环境,Bean 只有singleton、prototype,新增 request、session、global session、application(具体见Scope 相关博文) |
支持获取 ServletContext | 定义了一个常量 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE ,在上下文启动时,WebApplicationContext 实例以此键放置在 ServletContext 的属性列表中,通过 getServletContext() 方法获取 ServletContext 对象 |
支持在 ServletContext 中获取WebApplicationContext | 工具类 WebApplicationContextUtils.getWebApplicationContext() 方法可以从 servletContext 中获取 WebApplicationContext 实例,内部就是通过 getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) 方法获取的 |
- WebApplicationContext 类继承体系
类名 | 描述 |
---|---|
ConfigurableWebApplicationContext | 扩展了 WebApplicationContext,允许通过配置的方式实例化 WebApplicationContext。setServletContext 为Spring设置 Web 应用上下文;setConfigLocation 设置spring 配置文件地址,一般情况下,配置文件地址是相对于 Web 根目录的地址,如/WEB-INF/XXX。可以使用带资源类型前缀的地址,classpath:XXX 指定 |
XmlWebApplicationContext | 支持使用 Xml 配置实现 |
AnnotationConfigWebApplicationContext | 支持使用 JavaConfig 实现 |
WebApplicationContext 初始化
- WebApplicationContext 初始化,需要ServletContext 实例,就需要Web 容器支持。需要在 Web.xml 中配置web容器监听器(ServletContextListener)
- Spring 提供了用于启动 WebApplicationContext 的 Web 容器监听器:
org.springframework.web.context.ContextLoaderListener
使用 ContextLoaderListener 启动 WebApplicationContext
- Xml 配置实现的web配置
- 通过获取 contextConfigLocation 上下文参数,得到 spring 配置文件的位置。多个配置文件可以使用逗号、空格等分隔。不显示指定资源类型前缀,默认从Web的根路径获取
// 指定配置文件
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring/spring-context.xml</param-value>
</context-param>
// 监听器
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- 使用注解 @Configuration实现的Java类提供配置信息,web.xml 配置如下:
- 配置 contextClass 参数,指定Spring 使用 AnnotationConfigWebApplicationContext 上下文替代 XmlWebApplicationContext
- ContextLoaderListener 获取参数 contextClass ,如果不为空,
方法会创建 AnnotationConfigWebApplicationContext 上下文,并且解析 contextConfigLocation 获取的标注了@Configuration 的类createWebApplicationContext()
// JavaConfig 配置,配置 contextClass 参数,指定Spring 使用 AnnotationConfigWebApplicationContext 上下文
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
// 指定配置类:配置标注了@Configuration 的类
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>cn.com.sinosoft.smp.web.sys.config.SysConfig</param-value>
</context-param>
// 监听器,会根据上面配置使用 AnnotationConfigWebApplicationContext 根据 contextConfigLocation 指定的配置类启动容器
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
使用 SpringServletContainerInitializer 启动 WebApplicationContext
- Servlet3.x 新增 ServletContext 的性能增强:(具体的新功能见博文 Servlet笔记系列(12):Servlet3.X版本新特性)
- 支持在运行时动态部署监听器、过滤器等
- 使用web容器初始化接口
取代web.xml配置ServletContainerInitializer
-
使用ServletContainerInitializer
-
文件中配置实现类的全路径类名/META-INF/services/javax.servlet.ServletContainerInitializer
- 实现类需要使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类
-
- Spring 中
实现了org.springframework.web.SpringServletContainerInitializer
接口:ServletContainerInitializer
- 在 spring-web 模块中有如下配置:
,内容为/META-INF/services/javax.servlet.ServletContainerInitializer
org.springframework.web.SpringServletContainerInitializer
- 并且通过 @HandlesTypes 注解指定
来处理具体的业务org.springframework.web.WebApplicationInitializer
- 实现方法:
- 实现 WebApplicationInitializer,重写 onStartup 方法,依次添加各种配置,如spring的context上下文、Spring Listener。
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 注册上下文与配置,这边使用 Xml 实现或者 JavaConfig 实现都可以
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MvcConfig.class);
ctx.setServletContext(servletContext);
// 添加 Spring Listener
servletContext.addListener(new ContextLoaderListener(ctx));
// 添加 CharacterEncodingFilter
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("utf-8");
FilterRegistration.Dynamic registration = servletContext.addFilter("characterEncodingFilter", characterEncodingFilter);
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForUrlPatterns(getDispatcherTypes(), false, "/*");
// 配置spring mvc,这边可以配置servlet的配置
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
private boolean isAsyncSupported() {
return true;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
}
}
- 默认 WebApplicationInitializer 有一个实现类 AbstractContextLoaderInitializer,可以实现他,重写
即可。createRootApplicationContext()
- Spring-mvc 模块中有 AbstractDispatcherServletInitializer、AbstractAnnotationConfigDispatcherServletInitializer 类实现了
接口,使用 springmvc 可以直接继承,比较简单WebApplicationInitializer
public class WebInitializer2 extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{MvcConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
父子容器
- 通过 HierarchicalBeanFactory 接口,Spring 容器可以实现父子层级关联的容器体系,子容器可以通过接口方法访问父容器中的 Bean,但是父容器不能访问子容器中的 Bean
- 在容器中 Bean 的id 必须是唯一的,但是子容器可以拥有一个和父容器id相同的 Bean
- 在 Spring MVC 中,展现层 Bean 位于子容器中,业务层和持久层 Bean 位于父容器中,这样展现层 Bean 可以引用业务层和持久层 Bean,反之就不可以
参考
- 源码地址
- Servlet笔记系列(12):Servlet3.X版本新特性