注:文章内容有点多,可以先总体大概浏览一遍,心中有个整体类目关系构图,再详细看,不至于迷失在细节中,最好本地也使用开发工具打开源码对着看。
Spring框架已经成为目前JavaEE企业应用的主流框架,它提供了一个全面的编程和配置模型,适用于任何类型的部署平台。
Spring的一个关键元素是应用程序级别的基础设施支持:Spring专注于企业应用程序的“管道”,这样我们开发人员就可以专注于应用程序的业务逻辑开发,而不用关注于底层对象的管理与环境配置等。
说到Spring,我们基本都能说出它的两大特性:IoC、AOP,但底层的实现原理、启动流程等就不一定有所深入了解。
Spring 特性:
Spring架构:
Spring框架是一个分层架构,总共包含20多个模块,包含一系列的功能要素,由 1300 多个不同的文件构成。所有组件被分别整合在核心容器(Core Container)
、 AOP(Aspect Oriented Programming)
、Aspects(切面)、设备支持(Instrmentation)
、数据访问及集成(Data Access/Integeration)
、 Web
、 报文发送(Messaging)
、 Test 这
8个模块集合中,Spring 架构图如下:
注:篇幅有限,这块内容也不是本文重点,每一个架构模块,可参考其他资料。
下面将结合源码分析Spring的启动流程:
Spring的启动是建立在servlet/web容器(Tomcat、JBoss、Jetty等)之上的,一个常规的Spring应用,在web容器启动时,默认会先去加载/WEB-INF/web.xml,它配置了:servletContext上下文、监听器(Listener)、过滤器(Filter)、Servlet等。
常规的web.xml示例:
contextConfigLocation
/WEB-INF/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
utf-8
CharacterEncodingFilter
/*
dispatchServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/springmvc.xml
1
dispatchServlet
*.do
web.xml 加载顺序为: context-param < listener < filter < servlet
当然,也可以自定义自己的监听器、过滤器、拦截器等。这里只是截取主要的常用配置,下面将结合web.xml详细介绍它的启动流程。
JavaEE标准规定,servlet容器需要在应用项目启动时,给应用程序初始化一个ServletContext作为公共环境容器存放公共信息,ServletContext中的信息都是由容器提供的。servlet规范当中,使用了Listener监听器机制来进行web容器相关组件的生命周期管理以及Event事件监听器来实现组件之间的交互。
其中一个重要的生命周期监听器是 ServletContextListener 。web容器在创建和初始化 ServletContext 的时候,会产生一个ServletContextEvent 事件,其中 ServletContextEvent 包含该 ServletContext 的引用。然后交给在web.xml中配置的,注册到这个ServletContext 的监听器 ServletContextListener。ServletContextListener 在其 contextInitialized 方法中定义处理逻辑,源码如下:
package javax.servlet;
import java.util.EventListener;
public interface ServletContextListener extends EventListener {
void contextInitialized(ServletContextEvent var1);
void contextDestroyed(ServletContextEvent var1);
}
从 contextInitialized 方法的注释可知:通知所有的ServletContextListeners,当前的web应用正在启动,而且这些ServletContextListeners是在Filters和Servlets创建之前接收到通知的。所以在这个时候,web应用还不能接收请求,故可以在这里完成底层处理请求的组件的加载,这样等之后接收请求的Filters和Servlets创建时,则可以使用这些创建好的组件了。spring相关的bean就是这里所说的底层处理请求的组件,如数据库连接池,数据库事务管理器等。
举例:
通过自定义contextListener获取web.xml中配置的参数,需要实现 ServletContextListener 接口。
1.容器启动时,优先会加载web.xml 配置文件
2.然后找到对应的
例如:在web.xml中配置
key
value
com.stwen.spring.listener.MyContextListener
其中,MyContextListener 是自定义实现ServletContextListener 接口的类:
package com.stwen.spring.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyContextListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println("===========销毁自定义的监听器:MyContextListener=============");
}
@Override
public void contextInitialized(ServletContextEvent event) {
System.out.println("===========初始化自定义的监听器:MyContextListener===========");
ServletContext servletContext = event.getServletContext();
System.out.println("key:"+servletContext.getInitParameter("key"));
}
}
web.xml中可以定义两种参数:
param1
avalible in servlet init()
第一种参数在servlet里面可以通过 getServletContext().getInitParameter("context/param") 得到;
第二种参数只能在Servlet的 init() 方法中通过 this.getInitParameter("param1")取得:
package javax.servlet;
import java.io.IOException;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
注:servlet其实是一个接口,接口就是规定了一些规范,使得一些具有某些共性的类都能实现这个接口,从而都遵循某些规范。广义上可以把实现servlet的实现类,也统称为一个servlet。Servlet容器默认是采用单实例多线程的方式处理多个请求的,不熟悉servlet 的读者可以参考其他资料。
spring为我们提供了实现 ServletContextListener 接口的上下文初始化监听器实现类:org.springframework.web.context.ContextLoaderListener
ContextLoaderListener:spring-web包的ContextLoaderListener就是一个ServletContextListener的实现类。ContextLoaderListener主要用来获取spring项目的整体配置信息,并创建对应的WebApplicationContext来保存bean的信息,以及创建这些bean的对象实例。默认去WEB-INF下加载applicationContext.xml配置,如果applicationContext.xml放在其他位置,或者使用其他不同的名称,或者使用多个xml文件,则与指定contextConfigLocation。
如下配置:
contextConfigLocation
/WEB-INF/applicationContext.xml
classpath:spring/spring-mvc.xml,
classpath:spring/spring-redis.xml,
classpath:spring/dubbo-provider.xml
org.springframework.web.context.ContextLoaderListener
Spring的启动其实就是IOC容器的启动初始化过程,如上标签
初始化上下文,然后通过读取
ContextLoaderListener 源码如下,同时可以看到,它是上面 ServletContextListener的一个实现类:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
//初始化ServletContext上下文配置
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
//销毁ServletContext上下文
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
可以看到,contextInitialized()调用了父类ContextLoader
的initWebApplicationContext(event.getServletContext());
方法,很显然,这是对 ApplicationContext 的初始化方法,也就是到这里正式进入了 Spring IOC 的初始化。
接下来具体看 initWebApplicationContext
方法
,由于该方法较长,下面拆分小模块解读,如下:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//1、判断是否已经初始化过了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!");
}
//2、日志记录,显示初始化Spring WebApplicationContext
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
//3、记录启动耗时/ms
long startTime = System.currentTimeMillis();
try {
...
return this.context;
}
catch (RuntimeException | Error ex) {
...
}
}
首先判断是否创建了WebApplicationContext,正常情况下创建了一个WebApplicationContext后,会把context set到ServletContext中,setAttribute的本质其实是往LinkedHashMap中set值:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
如果从servletContext中根据key获得了WebApplicationContext对象,则表示之前已经创建过了根上下文WebApplicationContext,此时抛出异常,提示检查web.xml中的配置,避免重复创建root上下文,保证应用只有一个Spring容器。
然后,再看其中try{ } catch { } 部分逻辑:
try {
// 将上下文存储在本地实例变量中,以确保它在ServletContext关闭时可用
if (this.context == null) {
//(1)
this.context = createWebApplicationContext(servletContext);
}
//(2)
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// 上下文还没有刷新——>提供了诸如设置父上下文、设置应用程序上下文id等服务
if (cwac.getParent() == null) {
// 上下文实例是在没有显式父类的情况下注入的——如果有根web应用程序上下文,则确定它为父类
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//(3)
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
其中,try模块中主要有调用了两个核心方法:
根据上面代码标注的(1)(2)(3),逐步往下看:
(1)createWebApplicationContext()
首先是判断 this.context == null,是则调用 createWebApplicationContext ()方法先创建一个 WebApplicationContext 应用上下文,具体代码如下:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
determineContextClass,字面意思为检测Context的class类型,会读取servletContext的初始化参数contextClass,大部分情况下我们不会配置此参数,在未配置的情况下,Spring会去org.springframework.web.context包中的ContextLoader.properties配置文件读取默认配置XmlWebApplicationContext :
# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
通过Spring提供的ClassUtil进行反射,反射出XmlWebApplicationContext类,再通过BeanUtils进行反射,调用无参构造器,instance出一个WebApplicationContext并返回ConfigurableWebApplicationContext。
注:determineContextClass 详解
在这个方法中,Spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是 XmlWebApplicationContext ,这个就是spring的IoC容器,其对应的Bean定义的配置,就是由上面web.xml中的 context-param 标签指定。
determineContextClass (sc) 其返回的是一个WebApplicationContext的实现类,可以是默认的 XmlWebApplicationContext,也可以是指定一个自定义的类。源码如下:
protected Class> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
首先通过 servletContext.getInitParameter(CONTEXT_CLASS_PARAM) 读取 CONTEXT_CLASS_PARAM 参数,检查是否有用户自定义的 WebApplicationContext 实现类,该属性是在 ContextLoader 类中定义的一个静态常量:
自定义contextClass,其配置方式需要在web.xml中配置
contextClass
com.xxx.yyy.zzzClassName
示例:使用@Configuration 注解的方式代替 XML的配置方式,需要指定 WebApplicationContext 的实现类,如下的AnnotationConfigWebApplicationContext
contextClass
org.springframework.web.context.
support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.stwen.spring.MyConfiguration
@Configuration
public class MyConfiguration {
@Bean
public UserService userService(){
return new UserServiceImpl();
}
...
}
如果没有自定义配置contextClass的话,配置默认的
(2)ConfigurableWebApplicationContext
再来看try 中的第(2)个核心点部分:
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// 上下文还没有刷新——>提供了诸如设置父上下文、设置应用程序上下文id等服务
if (cwac.getParent() == null) {
// 上下文实例是在没有显式父类的情况下注入的——如果有根web应用程序上下文,则确定它为父类
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
第(1)步中获得了 WebApplicationContext 应用上下文后,第(2)步先是判断获得的context是否为 ConfigurableWebApplicationContext的实例,而上面默认返回的XmlWebApplicationContext实现类满足判断条件:
XmlWebApplicationContext --> AbstractRefreshableWebApplicationContext --> ConfigurableWebApplicationContext
然后,判断父上下文是否为active激活状态,如果active为false下(未激活),再判断父上下文是否为null,如果parent上下文为null的情况,则执行loadParentContext()。loadParentContext()方法,是一个默认的是模板实现方法,用于加载/获取ApplicationContext,此context为根WebApplicationContext的父上下文。
(3)configureAndRefreshWebApplicationContext()
紧接着上面(2)中的最后一步,便是调用 configureAndRefreshWebApplicationContext()方法:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// (3.1)
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 应用程序上下文id仍然设置为其原始默认值——>根据可用信息分配一个更有用的id
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// (3.2) 设置sc到wac中,便于Spring获得ServletContext
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// (3.3)
customizeContext(sc, wac);
// (3.4)
wac.refresh();
}
(3.1)将上面创建的XmlWebApplicationContext进行初始化操作,主要创建一个默认id,org.springframework.web.context.WebApplicationContext:
+项目的ContextPath 。
创建好id后,把servletContext放入创建好的XmlWebApplicationContext中,便于spring后续获得servletContext上下文。
(3.2)紧接着会去读取web.xml中
contextConfigLocation
classpath:spring/applicationContext.xml,
classpath:spring/spring-redis.xml,
classpath:spring/dubbo-provider.xml
如果在web.xml中未配置,则会去读取WEB-INF下面的applicationContext.xml配置文件,即 XmlWebApplicationContext中的属性 DEFAULT_CONFIG_LOCATION的默认值:
读取到 contextConfigLocation 相关文件配置路径后,设置到 XmlWebApplicationContext 中,用于加载指定路径的配置文件。
(3.3) customizeContext(sc, wac); 主要用于自定义context相关配置,比如定义bean是否可以循环引用,bean定义是否可以被覆盖等,通常情况下不做任何操作。
(3.4)wac.refresh(); 整个容器启动的最核心方法,在这个refresh()方法中,会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作。
refresh()源码如下:
@Override
public void refresh() throws BeansException, IllegalStateException {
//加锁同步,防止refresh() 还没结束,并发又来个启动或销毁容器的操作
synchronized (this.startupShutdownMonitor) {
// 准备刷新 context上下文:记录下容器的启动时间、标记已激活状态、处理配置文件中的占位符等
prepareRefresh();
// 核心,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
// 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
// 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备bean工厂,以便在此上下文中使用
prepareBeanFactory(beanFactory);
try {
// 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
// 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类
postProcessBeanFactory(beanFactory);
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截bean创建的bean处理器
registerBeanPostProcessors(beanFactory);
// 初始化此上下文的消息源
initMessageSource();
// 初始化此上下文的事件多播程序multicaster事件
initApplicationEventMulticaster();
// 在特定上下文子类中初始化其他特殊bean
onRefresh();
// 检查监听器 Listener beans并注册它们。
registerListeners();
// 实例化所有剩余的(非延迟加载)单例。
finishBeanFactoryInitialization(beanFactory);
// 最后一步:发布相应的事件。
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已经创建的单例,以避免挂起资源
destroyBeans();
// 重置活跃标志
cancelRefresh(ex);
// 将异常传播给调用者
throw ex;
}
finally {
// 重置Spring核心中的公用内部默认缓存,因为我们
// 可能再也不需要单例bean的元数据了……
resetCommonCaches();
}
}
}
注:篇幅有限,该 refresh 方法都可以单独一篇文章介绍。可参考:Spring启动过程(二) - 20岁的King的个人空间 - OSCHINA - 中文开源技术交流社区
总结:
整个初始化Spring应用上下文的方法 initWebApplicationContext(),主要以下3点:
Spring的启动过程,就是其IoC容器的启动过程,本质就是创建和初始化bean的工厂(BeanFactory),BeanFactory其实就是整个Spring IoC的核心,Spring 使用 BeanFactory 来实例化、配置和管理 Bean。
对于web程序,IoC容器启动过程即是建立上下文的过程,在web应用中,web容器会提供一个全局的ServletContext上下文环境,ServletContext上下文为Spring IoC提供了一个宿主环境。
Spring应用在Web容器中启动的整个过程如下:
上面的 contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。
DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
先从ServletContext中获取之前的根上下文 (即WebApplicationContext) 作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。
SpringMVC相关的web.xml配置:
dispatchServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/springmvc.xml
1
dispatchServlet
*.do
其中,
如上类图,便可清晰看到继承关系,而DispatcherServlet 继承 FrameworkServlet 抽象类,FrameworkServlet 又继承 HttpServletBean 类。web容器启动的时候会调用 HttpServletBean
的init方法,这个方法覆盖了GenericServlet中的init方法。源码如下:
public final void init() throws ServletException {
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
this.initServletBean();
}
该初始化方法的主要作用:
将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通过BeanWrapper简化设值过程,方便后续使用;提供给子类初始化扩展点initServletBean()
,该方法由子类 FrameworkServlet
覆写。
FrameworkServlet
继承 HttpServletBean
,通过 initServletBean() 进行Web上下文初始化,该方法主要两个功能点:
protected final void initServletBean() throws ServletException {
this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
} catch (RuntimeException | ServletException var4) {
this.logger.error("Context initialization failed", var4);
throw var4;
}
if (this.logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
}
if (this.logger.isInfoEnabled()) {
this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
DispatcherServlet继承FrameworkServlet,并实现了onRefresh()方法,提供一些前端控制器相关的配置。
整个DispatcherServlet初始化的过程主要做了两件事情:
1、初始化Spring Web MVC使用的Web上下文,并且指定父容器为WebApplicationContext(上面分析的ContextLoaderListener加载了的根上下文,Spring父容器);
2、初始化DispatcherServlet使用的策略,如HandlerMapping、HandlerAdapter等。
DispatcherServlet 部分源码如下:
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
这个DispatcherServlet初始化自己上下文的工作在其 initStrategies 方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是 xmlWebApplicationContext 。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码) 的属性为属性Key,也将其存到 ServletContext 中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
注:spring配置
既然知道了spring的启动流程,那么web容器初始化webApplicationContext时作为公共的上下文环境,只需要将service、dao等的配置信息在这里加载,而servlet自己的上下文环境信息不需要加载。故,在applicationContext.xml中将@Controller注释的组件排除在外,而在dispatcherServlet加载的配置文件中将@Controller注释的组件加载进来,方便dispatcherServlet进行控制和查找。
applicationContext.xml:
spring-mvc.xml:
1. 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring IoC容器提供宿主环境;
2. 其次,在web.xml中会提供有 contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其 contextInitialized 方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是 XmlWebApplicationContext ,这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的 context-param 标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
3. 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。
这个DispatcherServlet初始化自己上下文的工作在其 initStrategies 方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是xmlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
本文来自:CSDN 作者:stwen_gan Spring启动流程(原理)详解--结合web.xml加载配置分析_架构攻城之路的博客-CSDN博客_spring启动加载顺序及原理
觉得有用点个赞再走吧0.0,整理不易,转载请注明出处,谢谢。
参考:
spring官网:https://spring.io/
https://my.oschina.net/klausprince/blog/1791357
●史上最强Tomcat8性能优化
●阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路
●B2B电商平台--ChinaPay银联电子支付功能
●学会Zookeeper分布式锁,让面试官对你刮目相看
●SpringCloud电商秒杀微服务-Redisson分布式锁方案
查看更多好文,进入公众号--撩我--往期精彩
一只 有深度 有灵魂 的公众号0.0