目录
1.概念说明
(1)ServletContext
(2)BeanFactory
(3)控制反转(IOC)
(4)依赖注入(DI)
(5)Spring Bean作用域
2. 常见的Spring初始化容器的方式
(1)SSM
(2)ClassPathXmlApplicationContext
3.Spring boot如何进行初始化
(1)Spring boot初始化分为两个部分:
(2)@SpringBootApplication
(3)SpringApplication.run
相信做java开发的同学对“spring容器”一词并不陌生,但是spring容器到底是个什么东西,每个人的理解又不一样,这样对于初学者就比较尴尬了,总是糊里糊涂的。所以,接下来咱们从spring容器如何初始化角度来分析,这样能更好的帮助大家理解什么是spring容器,相信你通过这篇说明会对spring容器有更为清晰的认识。
在分析spring容器如何初始化之前,我们需要有几点说明,因为这将对初始化过程的理解更有帮助。
1.概念说明
(1)ServletContext
在servlet的规范当中,servlet容器或者叫web容器,如tomcat、jboss等中运行的每个应用都由一个ServletContext表示,在web容器中可以包含多个ServletContext,即可以有多个web应用在web容器中运行。如在tomcat的webapp目录下,每个war包都对应一个web应用,tomcat启动时会解压war包,并启动相关的应用。
(2)BeanFactory
BeanFactory是一个顶级接口,是一个最简单的spring容器,给依赖注入提供基础的支持。所以它是面向spring本身,属于基础设置。它最主要的方法就是 getBean(String beanName),该方法从容器中返回特定名称的 Bean。
ApplicationContext是由BeanFactory派生而来的,提供了更多面向实际应用的功能,所以它是面向使用spring框架的开发者。
WebApplicationContext 是专门为 Web 应用准备的,它允许从相对于 Web 根目录的路径中装载配置文件完成初始化工作。从 WebApplicationContext 中可以获得ServletContext 的引用,整个 Web应用上下文对象将作为属性放置到 ServletContext 中,以便 Web 应用环境可以访问 Spring 应用上下文。
他们之间的类继承关系,如下图:
(3)控制反转(IOC)
控制反转(IOC)只是一个概念,正常情况下,开发者都是通过new对象之后,才可以调用对象。但是如果对象太多,或者这些对象的每一个都需要被频繁的使用,那么就会出现对象频繁的被创建和销毁,这对资源的使用及性能的消耗上是极其恐怖的。为了解决这个问题,引入了IOC,即先把对象创建好,缓存在内存上(可以放在某个容器里,比如spring容器),使用的时候直接调用就好了,而且可以频繁被使用,当容器销毁的时候,再把对象销毁掉,这样既解决了资源使用问题,又提升了性能。
(4)依赖注入(DI)
基于IOC的概念,我们可以把一个个对象事先创建好,放在容器里。那么对象之间肯定会有依赖关系,怎么解决呢?
DI可以解决,DI会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection);还有一种是自动装配,比如使用@Autowired、@Resource。具体如何使用,不是这里的重点,可自行搜索。
(5)Spring Bean作用域
Spring Bean作用域的选择,决定了bean的创建和销毁方式。
1> 基本作用域:
Singleton(默认):单例模式(多线程下不安全),Spring IoC 容器(这里是针对一个ApplicationContext而言的,在一个web容器中(即ServletContext)中ApplicationContext可以有多个)中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式,配置为:<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>。
prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
2> Web应用中的作用域
Request:一次 request 一个实例。在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁。配置为:<bean id="loginAction" class="com.cnblogs.Login" scope="request"/>
session:在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求内有效,请求结束,则实例将被销毁。配置为:<bean id="userPreference" class="com.ioc.UserPreference" scope="session"/>
global Session:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。
2. 常见的Spring初始化容器的方式
首先,看下Spring 容器高层视图(如下图),Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配号Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
(1)SSM
在web容器启动的时候,会初始化web应用,即创建ServletContext对象,加载解析web.xml文件。web.xml中的加载顺序:context-param -> listener -> filter -> servlet。其中ContextLoaderListener是属于listener阶段。我们通常需要在项目的web.xml中配置一个DispatcherServlet,并配置拦截包含“/”路径的请求,即拦截所有请求。
ContextLoaderListener和DispatcherServlet两个都会创建各自的spring容器,一般情况下,ContextLoaderListener创建的是父容器,而DispatcherServlet创建的是子容器,在子容器中有个parent属性,指向的就是父容器,而父容器没有任何指向子容器的关系,所以只能子容器调用父容器中的方法,但是反过来不支持,下面以这两个说明下容器初始化的过程。
- ContextLoaderListener
首先看下ContextLoaderListener主要代码,如下
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
} else {
...
try {
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
//判断该新创建的容器是否refresh过,即是否初始化单例bean等,没有的话则进行refresh
if (!cwac.isActive()) {
//判断该新创建的容器有父容器,没有则set
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
//对新创建的容器进行refresh等操作,涉及到单例bean的初始化等工作
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//把新创建的容器作为属性放到ServletContext中,以便web应用环境可以访问该spring 容器
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
...
return this.context;
} catch (RuntimeException var8) {
...
} catch (Error var9) {
...
}
}
}
上面重点的是configureAndRefreshWebApplicationContext(cwac, servletContext)这个操作,里面开始时读取xml配置相关信息,重点是之后的wac.refresh(),代码注解如下:
@Override
public void refresh() throws BeansException, IllegalStateException {
// 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
synchronized (this.startupShutdownMonitor) {
// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
prepareRefresh();
// 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
// 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
// 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 Map)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
// 这块待会会展开说
prepareBeanFactory(beanFactory);
try {
// 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
// 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】
// 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
// 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
postProcessBeanFactory(beanFactory);
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
// 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
// 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
registerBeanPostProcessors(beanFactory);
// 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
initMessageSource();
// 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
initApplicationEventMulticaster();
// 从方法名就可以知道,典型的模板方法(钩子方法),
// 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
onRefresh();
// 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
registerListeners();
// 重点,重点,重点
// 初始化所有的 singleton beans,并将它们放入Spring容器的缓存中(缓存底层为ConcurrentHashMap结构)
//(lazy-init 的除外)
finishBeanFactoryInitialization(beanFactory);
// 最后,广播事件,ApplicationContext 初始化完成
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
// 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
destroyBeans();
// Reset 'active' flag.
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();
}
}
}
- DispatcherServlet
在DispatcherServlet类中并没有找到init方法,继续从其父类中搜索,DispatcherServlet -> FrameworkServlet -> HttpServletBean,最终在其父类HttpServletBean中找到了init方法。整体方法链路如下:
public final void init() throws ServletException {
...
// Let subclasses do whatever initialization they like.
initServletBean();
}
protected final void initServletBean() throws ServletException {
...
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
...
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
相比较ContextLoaderListener而言,DispatcherServlet主要加了一个onRefresh(wac)操作,主要是初始化 HandlerMapping & HandlerAdapter 等spring mvc九大组件工作。具体代码如下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* 初始化策略
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 多文件上传组件
initMultipartResolver(context);
// 初始化本地语言环境
initLocaleResolver(context);
// 初始化模板处理器
initThemeResolver(context);
// 初始化url映射
initHandlerMappings(context);
// 初始化参数适配器
initHandlerAdapters(context);
// 初始化异常拦截器
initHandlerExceptionResolvers(context);
// 初始化视图预处理器
initRequestToViewNameTranslator(context);
// 初始化视图转化器
initViewResolvers(context);
// 初始化FlashMap管理器
initFlashMapManager(context);
}
(2)ClassPathXmlApplicationContext
使用代码示例如下:
private static ApplicationContext ac = new ClassPathXmlApplicationContext(
"classpath*:conf/spring-impl-res.xml",
"classpath*:conf/spring-elasticsearch-sql-impl.xml",
"classpath*:conf/spring/spring-impl-dao.xml",
"classpath*:conf/spring/spring-impl-service.xml"
);
其底层代码实现为:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
而refresh()与ContextLoaderListener中的refresh()是同一方法,所以具体细节看上述即可。
3.Spring boot如何进行初始化
(1)Spring boot初始化分为两个部分:
@SpringBootApplication和执行SpringApplication.run静态方法。
(2)@SpringBootApplication
- @SpringBootConfiguration同@Configuration,表示这是一个JavaConfig配置类,无其他作用。
- @EnableAutoConfiguration
- 类似于提供一个组件,起作用就是可以将所有符合自动配置条件的bean定义加载到IOC容器,比如根据spring-boot-starter-web来判断是否添加webmvc和tomcat,其他spring-boot-starter开头的包均通过该方式自动化配置,借助@import的帮助。
- 注意这里并没有执行自动化配置,真正执行是在SpringApplication.run方法里的refreshContext方法里。
- 该注解的作用是启动自动配置机制,而支持的自动配置类名列表在spring-boot-autoconfigure包下的META-INF/spring.factories文件中的EnableAutoConfiguration中。
- @ComponentScan
- Spring的自动扫描注解,可定义扫描范围,加载到IOC容器,默认扫描SpringApplication.run启动类所在的同级目录下的所有文件,所以最好将该启动类放到跟路径下。
- 注意该注解仅提供扫描配置支持,并未进行扫描,真正执行的是在SpringApplication.run方法里的refreshContext方法里。
- 扫描所有@Component注解,如@Configuration、@Controller、@Service、@Repository底层均是@Component实现。
(3)SpringApplication.run
- SpringApplication这个类主要做一下四件事情:
- 推断应用的类型是普通的项目还是Web项目;
- 查找并加载所有可用初始化器 , 设置到initializers属性中;
- 找出所有的应用程序监听器,设置到listeners属性中;
- 推断并设置main方法的定义类,找到运行的主类;
构造器方法如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
- run方法
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
*
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*
* 运行spring应用,并刷新一个新的 ApplicationContext(Spring的上下文)
* ConfigurableApplicationContext 是 ApplicationContext 接口的子接口。在 ApplicationContext
* 基础上增加了配置上下文的工具。 ConfigurableApplicationContext是容器的高级接口
*/
public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ConfigurableApplicationContext Spring 的上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//从META-INF/spring.factories中获取监听器
//1、获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//2、构造应用上下文环境(读取配置文件,如application.yml等,加载配置文件,构建容器环境)
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的Bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
///3、初始化应用上下文(创建spring容器)
context = createApplicationContext();
//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, context);
//4、刷新应用上下文前的准备阶段(为后面扫描bean、加载做准备)
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//5、刷新应用上下文(底层跟ContextLoaderListener中的refresh()是同一方法)
refreshContext(context);
//刷新应用上下文后的扩展接口
afterRefresh(context, applicationArguments);
//时间记录停止
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//发布容器启动完成事件
listeners.started(context);
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
用一张图更为直观,如下:
以上就是本片所有内容了,其实spring初始化重要的两个过程,
第一,初始化配置,并创建容器;
第二,实例化bean,并放到缓存,重点是refresh方法。