九零后大叔
技术人的点点滴滴

springmvc篇-(四)web.xml文件与ServletContext对象概念说明

前一章节回顾: 前面的章节,我们对spring和springmvc有了比较深入的了解。知道了spring容器时如何启动的,知道了springmvc是如何工作的,但是还是留下了一些疑问。比如说war包丢到webapps路径下后,是怎么到最后启动成功的?本章节和后续的三个章节,我们将进一步探讨更深一步的问题。

首先我们回顾一下web后台服务开发的一些基础用法

[1] 过滤器的基本使用

第一步:创建一个全局过滤器

public class GlobalFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //过滤器初始化方法
        System.out.println("Execute GlobalFilter` init method.");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //过滤器过滤方法
        System.out.println("Execute GlobalFilter` doFilter method.");
        //调用下一个过滤器的过滤方法
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        //过滤器的销毁方法
        System.out.println("Execute GlobalFilter` destroy method.");
    }

}

第二步:在web.xml中配置GlobalFilter拦截器

<filter>
    <filter-name>globalFilter</filter-name>
    <filter-class>com.minesoft.tutorial.filter.GlobalFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>globalFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

通过这两步的配置,基本上就可以使用拦截器做定制化的业务处理了,还是比较简单。

[2] Servlet的基本使用

第一步:创建一个UserServlet类

public class UserServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("doPost ---------------");
        System.out.println(req.getMethod());
        System.out.println(req.getRequestURI());
        System.out.println(req.getServletPath());
        System.out.println(req.getServerPort());
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("doGet ---------------");
        resp.setContentType("text/json;charset=UTF-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();

        User user = new User();
        user.setId(1000L);
        user.setName("张山");
        user.setIdentity("43057818787873628X");
        user.setBankcard("36457736355363");
        user.setMobile("16752652625");
        user.setGender(2);
        user.setAge(18);
        String str = JSONObject.toJSONString(user);
        out.println(str);
        out.flush();
        out.close();
    }
}

第二步:在web.xml文件中配置UserServlet类

<servlet>
    <servlet-name>userServlet</servlet-name>
    <servlet-class>com.minesoft.tutorial.servlet.UserServlet</servlet-class>
    <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>userServlet</servlet-name>
    <url-pattern>/user</url-pattern>
</servlet-mapping>

通过这两步的配置,基本上就可以使用Servlet做定制化的业务处理了,还是比较简单。

[3] 监听器的基本使用

第一步:创建一个GlobalListener监听器类

public class GlobalListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("Execute GlobalListener` contextInitialized method.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Execute GlobalListener` contextDestroyed method.");
    }
}

第二步:在web.xml文件中配置GlobalListener监听器类

<listener>
    <listener-class>com.minesoft.tutorial.listener.GlobalListener</listener-class>
</listener>

通过这两步的配置,基本上就可以使用监听器做一些容器启动后的初始化的业务操作了。

熟悉了解了上面三个web开发的常用功能,其它的标签配置信息就不多做说明了,感兴趣的朋友可以自己学习使用。


1. web.xml的被解析过程简单介绍(也就是后台服务的启动过程)

在spring还没有出现之前,那时的java的web后台服务开发者很多都是基于servlet做开发的。当我们完成一个web后台服务的开发后,我们会将其打成一个war包,然后丢到web容器的指定目录下(tomcat时webapps)。此时web容器会解压该war包,然后读取web.xml文件存放到某个对象中,然后启动这个web后台服务。来我们细致的看看这个过程是怎样的:

[1] tomcat这个web容器会扫描到当前有一个新的web服务的存在,tomcat将这个新的war包解压出来;

[2] tomcat会读取这个war包内的web.xml文件,然后将该文件的所有信息读取,并存放到一个内存对象中;仅仅只是将文件的filter,listener,servlet,session等等标签信息读取到一个对象中,并不会开始解析这些标签信息。

[3] 开始filter标签信息的解析,根据filter标签创建出对应的filter实例对象,并按照web.xml中的配置顺序构建成为一个过滤器链。

[4] 开始listener标签信息的解析,根据listener标签信息创建出对应的listener实例对象,存放到某个内容对象中。

[5] 开始servlet标签信息的解析,根据servlet标签信息创建出对应的servlet实例对象,并按照web.xml中的配置顺序构建url-servlet映射对象,统一存放到某个内存对象中。

[6] 完成所有的web.xml中的标签信息的解析之后,开始调用各个监听器的contextInitialized方法。等所有的监听器的contextInitialized方法被调用完毕,我们的web后台服务也就启动成功了。

另外有些好奇的同学可能在问,这些都只是博主的自己的文字描述,怎么确定在解析的时候,就是按照先解析filter,后解析listener,最后解析servlet呢?先不要着急,本章只是一些概念的基本介绍和引入,详细的解析web.xml文件的过程下一章节会讲述。

另外还有一个问题,就是面试的时候经常问道的一个,就是listner,filter,servlet的执行顺序问题,这个问题其实很简单,就是listner->filter->servlet这个顺序。首先是一定是listner,因为服务刚刚启动起来的时候,就会调用监听器的contextInitialized方法;其次服务启动成功后,后面才有http请求过来,但是http请求不会直接交给我们的servlet,而是要先进行过滤操作,因为像一些未登录的请求,我们可能要拒绝掉;最后所有的过滤器都过滤完毕之后,确定这个请求没有问题之后,才交到servlet进行业务处理。


2. ServletContext的深入理解

首先我们运行下面一段的代码:

private void analysisMethod(HttpServletRequest req) {

    try {

        //1.从ServletContext对象中拿到contextConfigLocation标签所解析出来的对象
        ServletContext servletContext = req.getServletContext();
        Enumeration<String> namesEnum = servletContext.getInitParameterNames();
        while(namesEnum.hasMoreElements()){
            String key = namesEnum.nextElement();
            String value = servletContext.getInitParameter(key);
            System. out.println(key + " : " + value);
        }

        //2.从ServletContext对象中拿到Servlet标签解析出来的UserServlet对象
        ApplicationContextFacade contextFacade = (ApplicationContextFacade) servletContext;
        ServletRegistration registration = contextFacade.getServletRegistration("userServlet");
        ApplicationServletRegistration servletRegistration = (ApplicationServletRegistration) registration;
        Field field = servletRegistration.getClass().getDeclaredField("wrapper");
        field.setAccessible(true);
        Wrapper wrapper = (Wrapper) field.get(servletRegistration);
        HttpServlet userServlet = (HttpServlet) wrapper.getServlet();
        if (userServlet != null) {
            System.out.println(userServlet.getClass().getSimpleName());
        }

        //3.从ServletContext对象中拿到过滤器标签解析出来的GlobalFilter对象
        FilterRegistration filterRegistration = contextFacade.getFilterRegistration("globalFilter");
        ApplicationFilterRegistration applicationFilterRegistration = (ApplicationFilterRegistration) filterRegistration;
        field = applicationFilterRegistration.getClass().getDeclaredField("context");
        field.setAccessible(true);
        StandardContext standardContext = (StandardContext) field.get(applicationFilterRegistration);
        field = standardContext.getClass().getDeclaredField("filterConfigs");
        field.setAccessible(true);
        Map<String, ApplicationFilterConfig> filterConfigs = (Map<String, ApplicationFilterConfig>) field.get(standardContext);
        ApplicationFilterConfig filterConfig = filterConfigs.get("globalFilter");
        field = filterConfig.getClass().getDeclaredField("filter");
        field.setAccessible(true);
        GlobalFilter globalFilter = (GlobalFilter) field.get(filterConfig);
        if (globalFilter != null) {
            System.out.println(globalFilter.getClass().getSimpleName());
        }

        //4.从ServletContext对象中拿到监听器标签解析出来的GlobalListener对象
        field = standardContext.getClass().getDeclaredField("applicationLifecycleListenersObjects");
        field.setAccessible(true);
        Object applicationLifecycleListenersObjects[] = (Object[]) field.get(standardContext);
        ServletContextListener listener = (ServletContextListener) applicationLifecycleListenersObjects[0];
        GlobalListener globalListener = (GlobalListener) listener;
        if (globalListener != null) {
            System.out.println(globalListener.getClass().getSimpleName());
        }

    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}

我们看看控制台的打印如下:

contextConfigLocation : classpath:applicationContext*.xml
UserServlet
GlobalFilter
GlobalListener


从上面的代码,我们发现了一个事实,那就是通过ServletContext对象,我们可以拿到globalFilter过滤器对象,我们可以拿到UserServlet业务对象,我们甚至可以拿到监听器对象,我们可以拿到配置的context-param标签配置的数据。如果我们的UserServlet持有service对象,而service对象又持有mapper对象,那么我们通过ServletContext对象,也就可以拿到这些service和mapper对象,开发者可以通过ServletContext对象拿到一个web后台服务的任何一个实例对象。由上述的这些分析,我们可以得出一个结论,我们可以把ServletContext对象看作是一个web后台实例。我们的整个后台服务在内存中是一颗对象树,而ServletContext对象是这颗对象树是根节点。每当一个url请求到达web容器的时候,web容器会根据配置的url信息,将这个请求转交给对应的ServletContext对象,然后再由ServletContext对象,转交给对应的过滤器对象和servlet对象做业务处理。


3. 基本概念的总结

[1] web.xml文件是什么?
就是一个还未运行的后台服务实例核心对象的前身,也就是ServletContext对象的前身,它记录和承载了一个web后台服务的核心信息。

[2] ServletContext对象是什么?
当我们的web容器启动我们的web后台应用的时候,会把web.xml文件读取解析成为一个ServletContext对象,这个对象也就可以看成是一个运行中的后台服务实例。

[3] web容器能否持有多个ServletContext对象?
如果我们在web容器中部署了多个后台服务war包,web容器会依次启动这几个war包,依次解析war包中的web.xml文件,这样一个web容器就会持有多个这样的ServletContext对象,每个不同的ServletContext对象都是一个web后台服务。

[4] web容器和web服务实例的关系?
一个web容器(tomcat容器或者jetty容器)持有多个web服务实例(在一个web容器下部署多个war包)。

更多知识请关注公众号

赞(0) 赏一下
未经允许不得转载:九零后大叔的技术博客 » springmvc篇-(四)web.xml文件与ServletContext对象概念说明
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

九零后大叔的技术博客

联系我们联系我们

感谢您的支持

支付宝扫一扫打赏

微信扫一扫打赏