第九章 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匹配

浙公网安备 33010602011771号