在web项目的启动过程中,我们希望知道它的一般流程是什么,这样我们就可以在各个流程中加入相应的功能,或者对于我们排错也有帮助。

  我们知道,当我们启动tomcat容器以后,容器首先初始化一些必要的组件,加载项目所引用到的jar包(分别从jdk,tomcat,还有web-inf中的lib目录下),然后接下来的一步就是去读取web项目的web.xml配置文件。所以web项目里面必须要有web.xml配置文件。

 

  我们来看一份标准的web.xml配置文件,这是我从我的项目中抽取出来的。


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <!-- spring上下文 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:ApplicationContext.xml,
        </param-value>
    </context-param>
    <!-- 加载log4j配置文件 -->
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath:log4j.properties</param-value>
    </context-param>
    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>www.warrior.com</param-value>
    </context-param>
    <!-- 监听器 -->
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener
        </listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    <!-- 字符编码过滤器 -->
    <filter>
        <filter-name>encodingFilter</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>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 初始化filter -->
    <filter>
        <filter-name>startFilter</filter-name>
        <filter-class>com.xdx.filter.StartFilter</filter-class>
    </filter>
    <!-- session过滤器 -->
    <filter>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 以下配置是spring mvc -->
    <servlet>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:ApplicationContext-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <error-page>
        <error-code>404</error-code>
        <location>/404.jsp</location>
    </error-page>
    <session-config>
        <session-timeout>600</session-timeout>
    </session-config>
</web-app>


   

  可以看出web.xml的主要配置项有如下几种。

  a.context-param:上下文参数,通过键值对的方式配置一些上下文信息,如contextConfigLocation,我们给他配置为classpath:ApplicationContext.xml,说明去classpath:ApplicationContext.xml这个地方去寻找spring的主配置文件。

  b.listener:listener就是监听器,他会监听一些变化(比如servlet的初始化),然后就可以触发监听器的代码了。

  c.filter:过滤器,顾名思义,就是对请求进行过滤,filter也会在项目启动的时候被实例化。一般一个filter要对应filter-mapping,用于筛选所要执行过滤器中代码的url路径。如果一个filter没有filter-mapping,那它存在的意义就不大,它在web.xml存在的目的纯粹就是为了在项目启动的时候被实例化,从而执行其内部的代码。上述配置文件中的startFilter就是这个作用。

  d.servlet,servlet的配置与filter类似,就是对请求进行拦截,不同的请求分配到不同的servlet类进行处理。

  为了观察项目中各组件的启动顺序,我在相关的dao,entity类,service类,controllers类均加上了static代码块和无参的构造函数。static代码块是在类加载的时候运行,构造函数在类实例化时候运行,如下所示。

java项目启动即运行的方法_java项目启动即运行的方法

 

  

   运行项目。看看控制台打印出来的日志。

java项目启动即运行的方法_spring_02


  我们可以看到,项目的启动顺序首先是context-param,接着是listener,在接下来是filter,最后才是servlet。


  问题1:改换context-param,listener,filter,servlet配置语句在web.xml中的顺序,其启动顺序是否会变呢?

  答案是否定的,即便是吧关于servlet的配置放在最前面,其加载顺序还是会在最后。但是需要注意的是,在同一类型的配置项中,其在web.xml的顺序会影响其启动的顺序,比如有两个filter,filter1在配置文件中先于filter2,则filter1先于filter2被加载实例化。

  

  问题2:org.springframework.web.context.ContextLoaderListener的作用。

  ContextLoaderListener这个监听器继承自ContextLoader并且实现了ServletContextListener,他的主要作用是去寻找并读取spring主配置文件ApplicationContext.xml(也就是context-param中所定义的contextConfigLocation),然后启动WebApplicationContext,也可叫做web应用上下文,并且最重要的是,它将WebApplicationContext注入到servletContext容器中(作为servletContext的一个attribute,属性),并且在WebApplicationContext中保留了一个servletContext的引用。所以我们可以通过

WebApplicationContext得到servletContext,也可以通过servletContext获取到WebApplicationContext。

  通过WebApplicationContext得到servletContext:

WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
    ServletContext servletContext = webApplicationContext.getServletContext();

  通过servletContext获取WebApplicationContext:

ServletContext servletContext = event.getServletContext();
 WebApplicationContextUtils .getWebApplicationContext(servletContext);


  问题3:webApplicationContext和servletContext是谁先存在呢?

WebApplicationContext容器的一个宿主。

  

  所以:简单来说:web项目启动经过如下步骤。

  1.项目启动,加载依赖的jar包。

  2.web容器(tomcat)先提供一个全局上下文ServletContext.

  3.web容器去读取web.xml文件,并且运行ContextLoaderListener监听器,该监听器因为实现了ServletContextListener接口,所以当发现容器生成了一个ServletContext实例的时候,便会执行ServletContextListener接口的初始化方法,在该初始化方法中根据contextConfigLocation指定的位置去读取spring的主要配置文件,然后生成web应用上下文WebApplicationContext,并且将其作为一个属性注入到ServletContext中。

  4.初始化WebApplicationContext以后,启动了“业务层”的spring容器,并开始加载病初始化applicationContext配置文件中所扫描的类。

  5.然后就是初始化filter,最后初始化servlet。

  所以说作为web项目,WebApplicationContext的生成必须要在web容器存在的情况下才能实现,因为他需要ServletContext,而ServletContext是web容器生成的。

  

  问题4:DispatcherServlet是什么?有什么用。

  简单来说,它就是一个servlet,但是它是一个特殊的servlet,是整个spring mvc框架的核心,他是一个前端servlet,spring mvc经过前端servlet来接受所有的请求,然后再讲具体工作派发给其他的的servlet来具体实现。

同时,再servlet的配置文件中,我们看到名为SpringMvc的读取了contextConfigLocation所定义的配置文件(classpath:ApplicationContext-mvc.xml),启动了web层的spring容器,在这个容器里,我们初始化了所有的controller类。如控制台打印的日志所示。

  

DispatcherServlet伴随着启动spring mvc容器(即上面所说的web层容器),所以需要较长的时间,所以我们希望在项目启动的时候就进行初始化的操作。这也是我们将load-on-startup项设为1的原因。因为这个属性设为正数的表示在项目启动的时候就初始化,数字越小表明越早初始化。如果我们将其设为负数的话。那么在项目启动的时候,将不会启动spring mvc的容器,而是当我们第一次访问某个controller所对应的action的时候才来加载启动容器,这将会造成较长时间的等待,所以我们一般将load-on-startup设为1.