第九章 Servlet工作原理解析

  • 9.1 从Servlet容器说起

Servlet 容器的启动过程

Tomcat tomcat = getTomcatInstance();//创建Tomcat实例
        File appDir = new File(getBuildDirectory(), "webapps/examples");//新增web应用
        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
        tomcat.start();//启动
        ByteChunk res = getUrl("http://localhost:" + getPort() + "/examples/servlets/servlet/HelloWorldExample");
        assertTrue((res.toString).indexOf("<h1>Hello World</h1>") > 0);//调servlet
public Context addWebapp(Host host, String url, String path) { 
        silence(url); 
        //添加一个 Web 应用时将会创建一个 StandardContext 容器,并且给这个 Context 容器设置必要的参数
        Context ctx = new StandardContext(); 
        ctx.setPath( url ); 
        ctx.setDocBase(path); //url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这个应用实际的物理路径
        if (defaultRealm == null) { 
            initSimpleAuth(); 
        } 
        ctx.setRealm(defaultRealm); 
        ctx.addLifecycleListener(new DefaultWebXmlListener()); 
        ContextConfig ctxCfg = new ContextConfig(); //这个类将会负责整个 Web 应用配置的解析工作
        ctx.addLifecycleListener(ctxCfg); //最后将这个 Context 容器加到父容器 Host 中。
        ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); 
        if (host == null) { 
            getHost().addChild(ctx); 
        } else { 
            host.addChild(ctx); 
        } 
        return ctx; 
 }

Tomcat 主要启动类的时序图

 Web 应用的初始化工作

Web 应用的初始化工作是在ContextConfig的configureStart方法中实现的,应用的初始化主要是解析web.xml文件,这个文件描述Web应用的关键信息,也是Web应用的入口。

    for (ServletDef servlet : servlets.values()) { 
        Wrapper wrapper = context.createWrapper(); 
        String jspFile = servlet.getJspFile(); 
        if (jspFile != null) { 
            wrapper.setJspFile(jspFile); 
        } 
        if (servlet.getLoadOnStartup() != null) { 
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); 
        } 
        if (servlet.getEnabled() != null) { 
            wrapper.setEnabled(servlet.getEnabled().booleanValue()); 
        } 
        wrapper.setName(servlet.getServletName()); 
        Map<String,String> params = servlet.getParameterMap(); 
        for (Entry<String, String> entry : params.entrySet()) { 
            wrapper.addInitParameter(entry.getKey(), entry.getValue()); 
        } 
        wrapper.setRunAs(servlet.getRunAs()); 
        Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); 
        for (SecurityRoleRef roleRef : roleRefs) { 
            wrapper.addSecurityReference( 
                    roleRef.getName(), roleRef.getLink()); 
        } 
        wrapper.setServletClass(servlet.getServletClass()); 
        MultipartDef multipartdef = servlet.getMultipartDef(); 
        if (multipartdef != null) { 
            if (multipartdef.getMaxFileSize() != null && 
                    multipartdef.getMaxRequestSize()!= null && 
                    multipartdef.getFileSizeThreshold() != null) { 
                wrapper.setMultipartConfigElement(new MultipartConfigElement( 
                        multipartdef.getLocation(), 
                        Long.parseLong(multipartdef.getMaxFileSize()), 
                        Long.parseLong(multipartdef.getMaxRequestSize()), 
                        Integer.parseInt( 
                                multipartdef.getFileSizeThreshold()))); 
            } else { 
                wrapper.setMultipartConfigElement(new MultipartConfigElement( 
                        multipartdef.getLocation())); 
            } 
        } 
        if (servlet.getAsyncSupported() != null) { 
            wrapper.setAsyncSupported( 
                    servlet.getAsyncSupported().booleanValue()); 
        } 
        context.addChild(wrapper); 
  }

这段代码清楚的描述了如何将 Servlet 包装成 Context 容器中的 StandardWrapper。为什么要将 Servlet 包装成 StandardWrapper 而不直接是 Servlet 对象。这里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 为了一个独立的 web 开发标准,不应该强耦合在 Tomcat 中。

 

  • 9.2 创建Servlet实例

创建Servlet对象

如果Servlet的load-on-startup配置项大于0,那么在Context容器启动时就会被实例化,在解析配置文件时会读取默认的globalWebXml,在conf下的web.xml文件中定义了一些默认的配置项,其中定义了两个Servlet,分别是org.apache.catalina.servlets.DefaultServlet和org.apache.jasper.servlet.JspServlet。它们的load-on-startup分别是1和3,也就是当Tomcat启动时这两个Servlet就会被启动。

创建Servlet实例的方法是从Wrapper.loadServlet开始的。loadServlet要获取servletClass,然后把它交给InstanceManager去创建一个基于servletClass.class的对象。

初始化Servlet

初始化Servlet在StandardWrapper的initServlet方法中,就是调用servlet的init()方法,同时把包装了StandardWrapper对象的StandardWrapperFacade作为ServletConfig传给Servlet。

 

  • 9.3 Servlet体系结构

Servlet顶层类关联图

ServletConfig接口的方法为了获取这个servlet的一些配置属性。ServletContext设定交易场景,ServletConfig配置定制参数集合,ServletRequest和ServletResponse是要交互的具体对象,他们通常作为运输工具来传递交互结果。

ServletConfig和ServletContext类关系图

StandardWrapperFacade是StandardWrapper门面类-封装容器中的数据,传给Servlet的是StandardWrapperFacade对象,这个类保证从StandardWrapper中拿到ServletConfig所规定的数据,而又不把ServletConfig不关心的数据给Servlet。

Request相关类结构图

Request和Response的转变过程

 

 

  • 9.4 Servlet如何工作

用户从浏览器向服务器发出请求通常包含如下信息:http://hostname:port/contextpath/servletpath,hostname和port用来与服务器建立连接,后面的URL用来选择服务器中哪个子容器服务用户请求。URL到达容器:映射工作由一个专门的类org.apache.tomcat.util.http.mapper来完成,这类保存了Tomcat的Container容器中的所有子容器的信息,org.apache.catalina.connector.Request类在进入Container容器前,Mapper会根据这次请求的hostname和contextpath将host和context容器设置到Request的mappingData属性中。

Request的Mapper类关系图

//这段代码作用将MapperListener类作为一个监听者加到整个Container容器的每个子容器中
public
void init() { findDefaultHost(); Engine engine = (Engine) connector.getService().getContainer(); engine.addContainerListener(this); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { host.addLifecycleListener(this); registerHost(host); } } }

Request在容器中的路由

 

  • 9.5 Servlet中的Listener

掌握这些Listener的使用方法,会让我们程序设计的更加灵活。

如Spring的org.springframework.web.context.ContextLoadListener就实现了一个ServletContextListener,当容器加载时启动Spring容器。ContextLoaderListener在contextInitialized方法中初始化Spring容器,有几种方法可以加载Spring容器,通过在web.xml的<context-param>标签中配置Spring的applicationContext.xml路径,文件名可以任意取,如果没有配置,将在/WEB-INF/路径下查找默认的applicationContext.xml文件。ContextLoaderListener的contextInitialized方法如下:

    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = creatContextLoader();
        if(this.contextLoader == null) {
            this.contextLoader == this;
        }
        this.contextLoader.initWebApplicationContext(event.getServlet-Context());
    }
  •  9.6 Filter如何工作

Filter类中的三个接口方法:

1、init(FilterConfig):初始化接口,在用户自定义的Filter初始化时被调用,除了能取到容器的环境类ServletContext对象外,还能获取在<ffilter>下配置<init-param>参数值

2、doFilter(ServletRequest,ServletResponse,FilterChain):每个用户请求进来时这个方法都会被调用,这个方法在Servlet的service方法前被调用。FilterChain代表当前的整个请求链,所以通过调用FilterChain.doFilter可以将请求继续下去。如果想拦截这个请求,可以不调用FilterChain.doFilter,那么这个请求就直接返回了。所以Filter是一种责任链设计模式

3、destroy:当Filter对象被销毁时,调用此方法。注意,当Web容器调用这个方法后,容器会再一次调用doFilter方法

Filter类的核心是传递FilterChain对象,这个对象保存了到最终Servlet对象的所有Filter对象,这些对象都保存在ApplicationFilterChain对象的filters数组中。FilterChain链上每执行一个Filer对象,数组的当前计数加1,知道计数等于数组的长度,当FilterChain上所有的Filter对象执行完后,就会执行最终的Servlet。所以ApplicationFilterChain对象会持有Servlet对象的引用。

 

  • 9.7 Servlet中url-pattern

web.xml中<servlet-mapping>和<filter-mapping>都有url-pattern配置项,作用是匹配一次请求是否执行这个Servlet或者Filter。

Servlet匹配:在一个请求被创建时就匹配了

Filter匹配:在创建ApplicationFilterChain对象进行时,就把所有定义的Filter的url-pattern与当前URL匹配

 

posted @ 2016-03-17 14:09  水底的土豆  阅读(250)  评论(1)    收藏  举报