赞
踩
重要的前置知识: ServletContext
在WEB容器(tomcat)启动时,他会为每个WEB应用程序创建一个对应的ServletContext对象,他代表当前的web应用,又名application,对于一个web项目来说,只有全局唯一的一个ServletContext对象
①从当前Servlet对象中获得
ServletContext c1=this.getServletContext()
②从ServletConfig对象中获得
ServletContext c2=this.getServletConfig().getServletContext()
③从会话对象中获得
ServletContext c3=req.getSession().getServletContext()
④从HttpServletRequest请求对象中获得
ServletContext c4 = req.getServletContext()
①获取全局配置文件的参数(全局配置文件参数会保存在servletContext中)
- <!-- 全局配置参数 -->
- <context-param>
- <param-name>listener1</param-name>
- <param-value>WEB-INF/classes/listener.txt</param-value>
- </context-param>
String path = c1.getInitParameter("listener1");
②获取web项目的绝对路径
String realPath = servletContext.getRealPath(path);
③用于获取WebRoot文件夹下的文件(.JSP,.PNG,.html,.doc,)资源
c1.getResourceAsStream("index.jsp");
当一个 Web 应用部署到容器内时(例如 Tomcat),在 Web 应用开
始响应执行用户请求前,以下步骤会被依次执行:
部署描述文件中(例如 Tomcat 的web.xml
)由<listener>
元素标记的事件监听器会被创建和初始化
对于所有事件监听器,如果实现了ServletContextListener
接口,将会执行其实现的contextInitialized()
方法
部署描述文件中由<filter>
元素标记的过滤器会被创建和初始化,并调用其init()
方法
部署描述文件中由<servlet>
元素标记的 Servlet 会根据<load-on-startup>
的权值按顺序创建和初始化,并调用其init()
方法
web应用部署初始化执行流程如下:(图片来自网图)
在 Tomcat 下 Web 应用的初始化流程是,先初始化listener
接着初始化filter
最后初始化servlet
接下来,以一个常见的tomcat的web.xml配置文件为例,进行Spring MVC的启动过程分析:
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
- version="4.0">
-
- <!--全局变量配置-->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <!-- 此处配置文件中的bean是放在根ioc容器中的 -->
- <param-value>classpath:applicationContext-*.xml</param-value>
- </context-param>
-
- <!-- 配置上下文加载监听器 -->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
-
- <!-- 配置编码过滤器 -->
- <filter>
- <filter-name>CharacterEncodingFilter</filter-name>
- <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
- <init-param>
- <param-name>encoding</param-name>
- <param-value>UTF-8</param-value>
- </init-param>
- <init-param>
- <param-name>forceResponseEncoding</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>CharacterEncodingFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
- <!-- 配置前端控制器 -->
- <servlet>
- <servlet-name>DispatcherServlet</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <!-- 此处配置文件中的bean是放在子ioc容器(当前servlet所关联的ioc容器)中的 -->
- <param-value>classpath:springMVC.xml</param-value>
- </init-param>
- </servlet>
- <servlet-mapping>
- <servlet-name>DispatcherServlet</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- </web-app>
1、因为我们定义了<context-param>标签,用于配置全局变量,所以tomcat会先去读取标签中的内容,放入之前创建好的servletcontext(application)中,作为Web应用的全局变量使用,然后再进行上述的初始化启动过程
2、tomcat会解析<listener>标签,创建相应的Listener对象,如果它实现了ServletContextListener
接口,就会调用它的contextInitialized
方法。
ServletContextListener接口
该接口只有两个方法contextInitialized
和contextDestroyed
,这里采用的是观察者模式,也称为为”订阅-发布“模式,实现了该接口的listener
会向发布者进行订阅,当 Web 应用初始化或销毁时会分别调用上述两个方法。
- public interface ServletContextListener extends EventListener {
- // servletContext初始化的时候被调用(tomcat启动)
- void contextInitialized(ServletContextEvent var1);
- // servletContext销毁的时候被调用(tomcat关闭)
- void contextDestroyed(ServletContextEvent var1);
- }
ContextLoaderListener
- public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
- public ContextLoaderListener() {
- }
-
- public ContextLoaderListener(WebApplicationContext context) {
- super(context);
- }
- // 容器初始化时被调用
- public void contextInitialized(ServletContextEvent event) {
- // 初始化web的应用上下文
- this.initWebApplicationContext(event.getServletContext());
- }
-
- public void contextDestroyed(ServletContextEvent event) {
- this.closeWebApplicationContext(event.getServletContext());
- ContextCleanupListener.cleanupAttributes(event.getServletContext());
- }
- }
ContextLoader#initWebApplicationContext
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
- /*
- 首先通过WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
- (WebApplicationContext.class.getName() + ".ROOT")
- 这个String类型的静态变量获取一个根IoC容器,根IoC容器作为全局变量
- 存储在application对象中,如果存在则有且只能有一个
- 如果在初始化根WebApplicationContext即根IoC容器时发现已经存在
- 则直接抛出异常,因此web.xml中只允许存在一个ContextLoader类或其子类的对象
- */
- 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 {
- servletContext.log("Initializing Spring root WebApplicationContext");
- Log logger = LogFactory.getLog(ContextLoader.class);
- if (logger.isInfoEnabled()) {
- logger.info("Root WebApplicationContext: initialization started");
- }
-
- long startTime = System.currentTimeMillis();
-
- try {
- // 如果当前webapplicationcontext不存在就创建一个根webapplication
- if (this.context == null) {
- this.context = this.createWebApplicationContext(servletContext);
- }
-
- if (this.context instanceof ConfigurableWebApplicationContext) {
- ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
- if (!cwac.isActive()) {
- if (cwac.getParent() == null) {
- // 此处会默认返回null
- ApplicationContext parent = this.loadParentContext(servletContext);
- // 设置当前根容器的父容器为null,表明当前容器为根容器
- cwac.setParent(parent);
- }
- // 配置并刷新根webApplicationContext,在这里会进行bean的创建和初始化
- this.configureAndRefreshWebApplicationContext(cwac, servletContext);
- }
- }
- // 如果在进行容器刷新的时候没有抛出异常,会继续来到此处:将当前的根webApplicationcontext设置到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");
- }
- // 至此根webApplicationContext创建完成
- return this.context;
- } catch (Error | RuntimeException var8) {
- logger.error("Context initialization failed", var8);
- // 如果在刷新容器的过程中抛出异常(没有读取到我们的配置文件),会来到此处,将根webapplicationContext设置到servletContext中
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
- throw var8;
- }
- }
- }
ContextLoader#createWebApplicationContext
下面是ApplicationContext的继承关系图:
- protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
- // 决策webapplicationcontext的真实类型
- Class<?> contextClass = this.determineContextClass(sc);
- if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
- throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
- } else {
- // 创建了一个ConfigurableWebApplicationContext类型的根web应用上下文
- return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
- }
- }
-
- protected Class<?> determineContextClass(ServletContext servletContext) {
- // 获取名称为contextClass的全局初始化参数,就是查看我们有没有自定义web应用上下文
- String contextClassName = servletContext.getInitParameter("contextClass");
- if (contextClassName != null) {
- try {
- // 如果我们自定义了应用上下文,就将加载对应的类,返回它的类对象
- return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
- } catch (ClassNotFoundException var4) {
- throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
- }
- } else {
- // 如果没有自己定义,就去默认的策略配置中获取,此时获取到的是类型是XMLWebApplicationContext,
- /**
- ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- **/
- contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
-
- try {
- // 加载类,返回类对象(XMLWebApplicationContext)
- return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
- } catch (ClassNotFoundException var5) {
- throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
- }
- }
- }
defaultStrategies的结构:
ContextLoader#configureAndRefreshWebApplicationContext
- protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
- String configLocationParam;
- if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
- // 给webapplicationcontext设置id,先去全局初始化参数中找,没有配置,就设置id为”类名:项目访问代表路径“,eg:org.springframework.web.context.WebApplicationContext:/springmvc_05_war_exploded
- configLocationParam = sc.getInitParameter("contextId");
- if (configLocationParam != null) {
- wac.setId(configLocationParam);
- } else {
- wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
- }
- }
- // 向根web应用上下文中设置servletContext
- wac.setServletContext(sc);
- // 获取我们配置的全局初始化参数中的key=contextConfigLocation所对应的value
- // 也就是获取springmvc的配置文件的名称(路径)
- configLocationParam = sc.getInitParameter("contextConfigLocation");
- if (configLocationParam != null) {
- // 如果配置了,将其设置到webApplicationContext的配置文件路径数组中configLocations,如果没有配置会使用默认的配置文件路径/WEB-INF/applicationContext.xml
- wac.setConfigLocation(configLocationParam);
- }
-
- ConfigurableEnvironment env = wac.getEnvironment();
- if (env instanceof ConfigurableWebEnvironment) {
- ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
- }
-
- this.customizeContext(sc, wac);
- // 刷新容器,调用Spring中的AbstractApplicationContext的refresh()方法(前面分析过注解版的,现在分析xml文件版的)
- // 1.创建bean工厂
- // 2.加载配置文件
- // 3.进行包扫描,将扫描到的组件注册到bean工厂中--->注册的是beanDefinition
- // 4.注册配置文件中的bean--->注册的是beanDefinition
- // 5.接下来就与注解配置一样了(调用bean工厂的后置处理器(扫描包、注册bean)、注册bean后置处理器、创建bean)
- // 但是,如果我们没有配置contextConfigLocation全局初始化参数,在默认路径下(/WEB-INF/applicationContext.xml)也没有相应的配置文件,创建完bean工厂后会抛出异常
- wac.refresh();
- }
整个ContextLoaderListener
类的启动过程到此就结束了,可以发现,创建ContextLoaderListener
是比较核心的一个步骤,主要工作就是为了创建根IOC 容器并使用特定的key
将其放入到application
对象中,供整个 Web 应用使用,在ContextLoaderListener
类中构造的根IOC 容器配置的 Bean 是全局共享的。
3.tomcat会解析<filter>标签,创建<filter-class>标签中指定的类对象,设置属性。
Web 应用启动的最后一个步骤就是创建和初始化相关 Servlet,在开发中常用的 Servlet 就是DispatcherServlet
前端控制器,前端控制器作为中央控制器是整个 Web 应用的核心,用于获取分发用户请求并返回响应
通过类图可以看出DispatcherServlet
类的间接父类实现了Servlet
接口,因此其本质上依旧是一个Servlet
。DispatcherServlet
类的设计很巧妙,上层父类不同程度的实现了相关接口的部分方法,并留出了相关方法用于子类覆盖,将不变的部分统一实现,将变化的部分预留方法用于子类实现。
DispatcherServlet
类的初始化过程将模板方法使用的淋漓尽致,其父类完成不同的统一的工作,并预留出相关方法用于子类覆盖去完成不同的可变工作。
DispatcherServelt
类的本质是Servlet
,通过文章开始的讲解可知,在 Web 应用部署到容器后进行Servlet
初始化时会调用相关的init(ServletConfig)
方法,因此,DispatchServlet
类的初始化过程也由该方法开始。先看一下整体的调用流程图:
主要的逻辑实现从FrameworkServlet
的initWebApplicationContext()
方法开始
FrameworkServlet#initWebApplicationContext()
- protected WebApplicationContext initWebApplicationContext() {
- // 从servletContext中获取根webApplicationContext,由于我们在初始化监听器的时候创建了根webApplicationContext,所以可以获取到,如果我们没有配置监听器,就不会创建根WebApplicationContext
- WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
- WebApplicationContext wac = null;
- // 当前servlet的webApplicationContext=null,此时我们还没有给Servlet的webApplicationContext属性赋值,如果配置了监听器,创建的根webApplicationContext也只在servletContext中,被全局共享
- if (this.webApplicationContext != null) {
- wac = this.webApplicationContext;
- if (wac instanceof ConfigurableWebApplicationContext) {
- ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
- if (!cwac.isActive()) {
- if (cwac.getParent() == null) {
- cwac.setParent(rootContext);
- }
-
- this.configureAndRefreshWebApplicationContext(cwac);
- }
- }
- }
- // 此时,因为我们还没有给当前servlet设置属性,所偶一也找不到WebApplicationContext
- if (wac == null) {
- wac = this.findWebApplicationContext();
- }
-
- if (wac == null) {
- // 创建webApplicationContext,此处的创建逻辑和之前分析过的ContextLoader中的创建逻辑大体相同
- /**
- 1.获取webApplicationContext类型对象
- 2.创建相对应的实例对象
- 3.将传进去的rootContext设置为它的父容器
- 4.设置配置文件的路径
- 5.配置并刷新容器(读取springMVC.xml配置文件中的bean将其注册到工厂中,创建bean实例...)
- **/
- wac = this.createWebApplicationContext(rootContext);
- }
-
- if (!this.refreshEventReceived) {
- synchronized(this.onRefreshMonitor) {
- this.onRefresh(wac);
- }
- }
-
- if (this.publishContext) {
- String attrName = this.getServletContextAttributeName();
- this.getServletContext().setAttribute(attrName, wac);
- }
- // 至此
- return wac;
- }
从上面createWebApplicationContext(rootContext)
中可以看出,我们给当前的servlet创建了一个IOC容器,并且给它设置父容器,那为什么明明都有一个父容器了,还要创建子容器呢?
父子容器类似于类的继承关系,子类可以访问父类中的成员变量,而父类不可访问子类的成员变量,同样的,子容器可以访问父容器中定义的 Bean,但父容器无法访问子容器定义的 Bean。
根 IoC 容器做为全局共享的 IoC 容器放入 Web 应用需要共享的 Bean,而子 IoC 容器根据需求的不同,放入不同的 Bean,这样能够做到隔离,保证系统的安全性。
当 IoC 子容器构造完成后调用了onRefresh()
方法,该方法的调用与initServletBean()
方法的调用相同,由父类调用但具体实现由子类覆盖,调用onRefresh()
方法时将前文创建的 IoC 子容器作为参数传入,查看DispatcherServletBean
类的onRefresh()
方法源码如下:
- protected void onRefresh(ApplicationContext context) {
- this.initStrategies(context);
- }
-
- // 以下的初始化操作,就是从ioc容器中获取创建好的bean,然后将其设置为DispatcherServlet的属性值
- protected void initStrategies(ApplicationContext context) {
- this.initMultipartResolver(context);
- this.initLocaleResolver(context);
- this.initThemeResolver(context);
- // 初始化处理器映射器,会先从IOC容器中获取,获取不到去找默认的
- this.initHandlerMappings(context);
- // 初始化处理器适配器,会先从IOC容器中获取,获取不到去找默认的
- this.initHandlerAdapters(context);
- this.initHandlerExceptionResolvers(context);
- this.initRequestToViewNameTranslator(context);
- this.initViewResolvers(context);
- this.initFlashMapManager(context);
- }
总结:Spring MVC的初始化流程
1.tomcat容器启动时,会去读取web项目下的web.xml文件
2.解析<context-param>标签中的参数
3.创建一个全局共享对象ServletContext,也叫application,将全局共享的参数放入ServletContext中
4.解析<Listener>标签,实例化Listener监听器对象,一般会使用
ContextLoaderListener
类,如果使用了ContextLoaderListener
类,就会创建一个WebApplicationContext对象,这个对象就是根IOC容器,它会去读取<context-param>标签中设置的key为contextConfigLocationd所对应的value,作为要加载的配置文件,创建bean工厂,读取配置文件,创建bean实例,将根IOC容器放入ServletContext中,但是这个根IOC容器只能访问到自己容器中的bean实例,无法访问其子容器中的bean实例5.解析<filter>标签,创建filter对象
6.解析<Servlet>标签,创建servlet对象,这个servlet通常指前端控制器DispatcherServlet,它间接实现了Servlet接口,所以会调用Servlet接口的init(ServletConfig config)方法,但是DispatcherServlet没有重写此方法,所以会调用它的父类GenericServlet重写的Servlet接口的init()方法,在重写父类的init()方法中又会调用自己的init()方法,但是自己的init()方法是个空方法,所以最终会调用它子类HttpServletBean重写它的init()方法,这样在父类中调用子类重写父类的空方法,最终会调用到FrameWorkServlet的initWenApplicationContext的方法,在此方法中创建一个当前Servlet对相应的子IOC容器,将全局IOC容器设置为它的父容器,读取
<init-param>
配置的xml
文件并加载相关 Bean。7.调用DispatcherServlet的onRefresh()方法,刷新容器,初始化属性,就是获取容器中的bean设置为自己的属性。
如有问题欢迎指正.....
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。