浅析Tomcat工作流程
必读20遍好文章:
领悟:https://www.oschina.net/question/12_52027
Tomcat:https://www.ibm.com/developerworks/cn/java/j-lo-servlet/
Tomcat:https://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/
Cookie:https://mp.weixin.qq.com/s/NXrH7R8y2Dqxs9Ekm0u33w?
按照大佬给的步骤一顿操作猛如虎,但是自己操作完之后就开始问了自己几个问题,哪里不会问哪里。
<?xml version='1.0' encoding='utf-8'?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost"> <Context path="" docBase="C:UsersadminDesktopServletBaseServletDemowebapp" reloadable="true"/> </Host> </Engine> </Service> </Server>
Context代表了运行在Host上的单个Web应用,一个Host可以有多个Context元素,每个Web应用必须有唯一的URL路径,这个URL路径在Context中的path属性中设定。
path:指定访问该Web应用的URL入口,web项目的访问路径,不填默认以http://localhost:8080/开始。
docBase:指定Web应用的文件路径,可以给定绝对路径,也可以给定相对于<Host>的appBase属性的相对路径,如果Web应用采用开放目录结构,则指定Web应用的根目录,如果Web应用是个war文件,则指定war文件的路径,你要让tomcat帮你管理Servlet你总要告诉它Servlet在哪的。
reloadable:如果这个属性设为true,tomcat服务器在运行状态下会监视在WEB-INF/classes和WEB-INF/lib目录下class文件的改动,如果监测到有class文件被更新的,服务器会自动重新加载Web应用。在开发阶段将reloadable属性设为true,有助于调试servlet和其它的class文件,但这样用加重服务器运行负荷,建议在Web应用的发存阶段将reloadable设为false。
图1让我理解了Tomcat整体的结构,在整个Tomcat中,被容器的概念贯穿,四层结构Engine,Host,Context,Weapper,从顶层Engine开始,到Host,再到Context,最后是Wrapper,每一层都是一个容器。每一个Context都代表着一个单独的web项目。在Context中为了解耦,将Servlet封装在Tomcat自身的Wrapper容器里,使得Tomcat与Servlet的关联不在紧密。
图2是让我印象最深的一张图,它几乎描述了整个Tomcat的大致工作流程。在理解这张图之前,记得要Tomcat的源码下载下来,我下载的是Tomcat7版本的源码,然后将源码中java文件夹下的javax和org分别导入Eclipse中,这样很方便查看。从最开始我们点击startup.bat,唤起Tomcat,然后Tomcat在启动(org.apache.catalina.startup.Tomcat)过程中会读取server.xml,然后初始化Server,Service,引擎,还有Connector,Server和Service不必讲也都明白,Engine在这里有必要介绍一下,它的责任就是将用户请求分配给一个虚拟机处理,而Connector的作用就是建立一个连接,一个WEB服务器到Tomcat的连接器,在图的最下面可以看到在这个Connector容器里初始化Http服务。
当Tomcat自身基础搭建好之后,开始针对web应用做文章了。做文章之前先启动了自身的服务然后初始化Host容器,启动Host,在Host启动过程中初始化了一个web应用上下文环境(回看上一章节)即StandardContext,到这你要注意了,当StandardContext的状态变为init时(也就是server.xml中的Context节点被读取并初始化时),ContextConfig作为观察者将被通知,你该问?是啥时候给StandardContext添加了这么一个观察者啊?这段代码在Tomcat启动时就已经添加了,我们来看一下这段代码:
// 这是手动启动Tomcat下面examples项目的例子,真实的启动Tomcat也大致类似 Tomcat tomcat = getTomcatInstance(); File appDir = new File(getBuildDirectory(), "webapps/examples"); tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); tomcat.start();
上面只是一段从创建Tomcat实例,到调用addWebapp()的简短过程,我们的主要关注点在addWebapp里:
public Context addWebapp(Host host, String contextPath, String docBase) { LifecycleListener listener = null; try { // 使用获取一个上下文配置即ContextConfig【继承了LifecycleListener接口】 Class<?> clazz = Class.forName(getHost().getConfigClass()); listener = (LifecycleListener) clazz.newInstance(); } catch (Exception e) { // Wrap in IAE since we can't easily change the method signature to // to throw the specific checked exceptions throw new IllegalArgumentException(e); } return addWebapp(host, contextPath, docBase, listener); } public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) { silence(host, contextPath); Context ctx = createContext(host, contextPath); ctx.setPath(contextPath); ctx.setDocBase(docBase); // 返回配置默认JSP处理的侦听器对象。背后是读取全局web.xml,启动JspServlet和DefaultServlet ctx.addLifecycleListener(getDefaultWebXmlListener()); ctx.setConfigFile(getWebappConfigFile(docBase, contextPath)); // 添加一个上下文配置的监听器,当Context变为init时被触发 ctx.addLifecycleListener(config); if (config instanceof ContextConfig) { // prevent it from looking ( if it finds one - it'll have dup error ) ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath()); } if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; }
在上面这段代码的第22行,在Tomcat初始化的时候创建了一个StandardContext,并给StandardCOntext添加了ContextConfig观察者(5,6,29)。
// org.apache.catalina.startup.Tomcat.java // 在Tomcat启动时启动JspServlet和DefaultServlet(只有当load-on-startup的value值>0时才会被启动) public LifecycleListener getDefaultWebXmlListener() { return new DefaultWebXmlListener(); } public static class DefaultWebXmlListener implements LifecycleListener { @Override public void lifecycleEvent(LifecycleEvent event) { if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) { initWebappDefaults((Context) event.getLifecycle()); } } } public static void initWebappDefaults(Context ctx) { // Default servlet,它的load-on-startup值为1 Wrapper servlet = addServlet( ctx, "default", "org.apache.catalina.servlets.DefaultServlet"); servlet.setLoadOnStartup(1); servlet.setOverridable(true); // JSP servlet (by class name - to avoid loading all deps),它的load-on-startup值为3 servlet = addServlet( ctx, "jsp", "org.apache.jasper.servlet.JspServlet"); servlet.addInitParameter("fork", "false"); servlet.setLoadOnStartup(3); servlet.setOverridable(true); // Servlet mappings ctx.addServletMapping("/", "default"); ctx.addServletMapping("*.jsp", "jsp"); ctx.addServletMapping("*.jspx", "jsp"); // Sessions ctx.setSessionTimeout(30); // MIME mappings for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length;) { ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++], DEFAULT_MIME_MAPPINGS[i++]); } // Welcome files ctx.addWelcomeFile("index.html"); ctx.addWelcomeFile("index.htm"); ctx.addWelcomeFile("index.jsp"); }
当StandardContext变为init时,ContextConfig这个观察者被通知,然后这个观察者的lifecycleEvent方法( package org.apache.catalina.startup.ContextConfig.lifecycleEvent() )被触发,lifecycleEvent()->configureStart()->webConfig():
protected void webConfig() { Set<WebXml> defaults = new HashSet<WebXml>(); defaults.add(getDefaultWebXmlFragment()); WebXml webXml = createWebXml(); // Parse context level web.xml InputSource contextWebXml = getContextWebXmlSource(); parseWebXml(contextWebXml, webXml, false); ServletContext sContext = context.getServletContext(); // Ordering is important here // Step 1. Identify all the JARs packaged with the application // If the JARs have a web-fragment.xml it will be parsed at this // point. Map<String,WebXml> fragments = processJarsForWebFragments(webXml); // Step 2. Order the fragments. Set<WebXml> orderedFragments = null; orderedFragments = WebXml.orderWebFragments(webXml, fragments, sContext); // Step 3. Look for ServletContainerInitializer implementations if (ok) { processServletContainerInitializers(); } if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) { // Steps 4 & 5. processClasses(webXml, orderedFragments); } if (!webXml.isMetadataComplete()) { // Step 6. Merge web-fragment.xml files into the main web.xml // file. if (ok) { ok = webXml.merge(orderedFragments); } // Step 7. Apply global defaults // Have to merge defaults before JSP conversion since defaults // provide JSP servlet definition. webXml.merge(defaults); // Step 8. Convert explicitly mentioned jsps to servlets if (ok) { convertJsps(webXml); } // Step 9. Apply merged web.xml to Context if (ok) { webXml.configureContext(context); } } else { webXml.merge(defaults); convertJsps(webXml); webXml.configureContext(context); } // Step 9a. Make the merged web.xml available to other // components, specifically Jasper, to save those components // from having to re-generate it. // TODO Use a ServletContainerInitializer for Jasper String mergedWebXml = webXml.toXml(); sContext.setAttribute( org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML, mergedWebXml); if (context.getLogEffectiveWebXml()) { log.info("web.xml: " + mergedWebXml); } // Always need to look for static resources // Step 10. Look for static resources packaged in JARs if (ok) { // Spec does not define an order. // Use ordered JARs followed by remaining JARs Set<WebXml> resourceJars = new LinkedHashSet<WebXml>(); for (WebXml fragment : orderedFragments) { resourceJars.add(fragment); } for (WebXml fragment : fragments.values()) { if (!resourceJars.contains(fragment)) { resourceJars.add(fragment); } } processResourceJARs(resourceJars); // See also StandardContext.resourcesStart() for // WEB-INF/classes/META-INF/resources configuration } // Step 11. Apply the ServletContainerInitializer config to the // context if (ok) { for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } } }
你可以看到在这个方法里对web.xml进行了读取解析,包括Tomcat全局web.xml(在conf目录下)以及部署在Tomcat里webapps里面WEB-INF下的web.xml。看图3:
图3描述的是当StandardContext状态变为init后通知ContextConfig后,ContextConfig做的一系列事情,可以看出从解析所有的xml到将xml数据存储在StandardContext里,然后就开始包装Servlet了,获取xml里有关Servlet或Jsp的配置,创建Wrapper并设置各种属性包括ServletClass,如果是Jsp就会先去访问一次让其编译成Servlet然后在设置到Wrapper,在将Wrapper加入StandardContext中。然后在将xml配置里的其他信息如servletMapping也设置到Context容器里。都添加完毕了,要想使用Servlet还要去init Servlet(反射获取)【loadOnStartup>0的】,也就是去调用Servlet的init方法初始化Servlet。到此从通知ContextConfig到ContextConfig把web.xml解析创建Wrapper,使用InstanceManager反射原理获取Servlet对象,初始化Servlet并封装StandWrapper(即Wrapper门面类)都完成了。
这时候观察者的任务完成,也就是到了图1的12,13,14已完成。然后在图1的16,17,18,19是创建一个Connector连接器启动http服务初始化MapperListener,当socket连接上服务器后,一个Http请求过来被Connector连接到Tomcat,MapperListener被触发,它会读取这个Http请求的URL地址,这个MapperListener中含有上下文所有的信息,看图4:
既然MapperListener含有上下文所有的信息,自然也知道Mapper和Wrapper,自然也能知道这个URL请求的是那个web服务的哪个Servlet。看下图5,了解请求过程。
看图6了解从Http请求过来到被MapperListener触发,获取URL信息找到映射的Mapping和Wrapper(Servlet)并封装成MappingData向后传递。经过引擎找到Java虚拟机,getHost获取虚拟主机(简单理解:虚拟主机是空间 就是我们做网站时候存放网站程序的地方),getContext拿到上下文,找到对应的Wrapper。
在此处插入一段许大佬一段文字和代码,一段让我深有感触的地方:
从上图7可以看出 Servlet 规范就是基于这几个类运转的,与 Servlet 主动关联的是三个类,分别是 ServletConfig、ServletRequest 和 ServletResponse。这三个类都是通过容器传递给 Servlet 的,其中 ServletConfig 是在 Servlet 初始化时就传给 Servlet 了,而后两个是在请求达到时调用 Servlet 时传递过来的。我们很清楚 ServletRequest 和 ServletResponse 在 Servlet 运行的意义,但是 ServletConfig 和 ServletContext 对 Servlet 有何价值?仔细查看 ServletConfig 接口中声明的方法发现,这些方法都是为了获取这个 Servlet 的一些配置属性,而这些配置属性可能在 Servlet 运行时被用到。而 ServletContext 又是干什么的呢? Servlet 的运行模式是一个典型的“握手型的交互式”运行模式。所谓“握手型的交互式”就是两个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随个这个交易过程直到这个交易完成为止。这个交易场景的初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常就会是一个配置类。所以对号入座,交易场景就由 ServletContext 来描述,而定制的参数集合就由 ServletConfig 来描述。而 ServletRequest 和 ServletResponse 就是要交互的具体对象了,它们通常都是作为运输工具来传递交互结果。【自己对比J2EE API查看ServletContext】
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletToJsp extends HttpServlet { private static final long serialVersionUID = 1L; @Override public void doGet (HttpServletRequest request, HttpServletResponse response) { try { // Set the attribute and Forward to hello.jsp request.setAttribute ("servletName", "servletToJsp"); getServletConfig().getServletContext().getRequestDispatcher( "/jsp/jsptoserv/hello.jsp").forward(request, response); } catch (Exception ex) { ex.printStackTrace (); } } }
在理解“场景”的同时,更要把场景和Tomcat体系联系起来。看图8.1,Servlet被包装成StandardWrapper,而StandardWrapperFacade又是StandardWrapper的门面类,这二者都继承了ServletConfig,在这个"场景"(ServletContext)中使用的不是ServletConfig而是StandardWrapperFacade门面类。在看看ServletContext和StandardContext,一个是提供一个一次交互的场景,一个是上下文环境,在上下文环境里依赖这某一次交互的"场景"。也就是说,当一次交互过来时,在上下文环境中准备一个"场景"即ServletContext,在这个上下文的"场景"中含有着StandardWrapperFacade(一些Servlet的配置和处理逻辑的Servlet),在这个"场景"里交互的对象即是ServletRequest 和 ServletResponse,它们通常都是作为运输工具来传递交互结果。
下图8.2描述的是request和response在不同模块中会有不同封装。我们在service方法中通常使用的是HttpServletRequest和HttpServletResponse,这二者在"场景"中是ServletRequest 和 ServletResponse,由下图8.2即可知道为什么service方法可以使用HttpServletRequest和HttpServletResponse。
tomcat访问所有的资源,都是用Servlet来处理的。三种资源划分:静态资源(js,css,png,jpg),Servlet,JSP。
对于静态资源交给org.apache.catalina.servlets.DefaultServlet来处理(就是全局web.xml里的servlet),在全局web.xml的default servlet上面有这么一句话:
<!-- The default servlet for all web applications, that serves static --> <!-- resources. It processes all requests that are not mapped to other --> <!-- servlets with servlet mappings (defined either here or in your own --> <!-- web.xml file). --> <!-- 所有的Web应用程序的默认servlet,用于处理静态资源。--> <!-- 它处理所有未映射到其他带有servlet映射的servlet(在此处或在您的定义中)。--> <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
从代码标红的那句话来看,也就是说处理逻辑最后才是DefaultServlet。
对于JSP,Tomcat最后会交给全局web.xml里的org.apache.jasper.servlet.JspServlet来处理。
<!-- The JSP page compiler and execution servlet, which is the mechanism --> <!-- used by Tomcat to support JSP pages. Traditionally, this servlet --> <!-- is mapped to the URL pattern "*.jsp". This servlet supports the --> <!-- following initialization parameters (default values are in square --> <!-- brackets): --> <!-- JSP页面编译器和执行servlet,这是由Tomcat用于支持JSP页面的机制。 --> <!-- 传统上,这个servlet映射到URL模式“*.jsp --> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
对于Servlet,Tomcat最后会交给一个叫做InvokerServlet的类来处理。在tomcat7以前的web.xml里有这么一段被注释的配置:
<-- The default servlet-invoking servlet for most web applications, --> used to serve requests to servlets that have not been registered in --> <!-- the web application deployment descriptor.--> <!-- 为大多数Web应用程序调用servlet的默认servlet,用于向尚未在Web应用程序 --> <!-- 部署描述符中注册的servlet提供请求。 --> <!-- <servlet> <servlet-name>invoker</servlet-name> <servlet-class> org.apache.catalina.servlets.InvokerServlet </servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> --> <!-- <servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping> -->
注释:Web Application Server提供了一种默认的访问servlet的方式,即通过http://myWebApp/mypackage.MyServlet的方式直接访问,而不需要定义<servlet>和<servlet-mapping>,这种功能称为Invoker Servlet,但是现在的App Server一般都默认禁用的这个功能。
从上面的学习中并没有出现过Invoker Servlet的影子了,对于web.xml中配置的<servlet>和<servlet-mapping>被读取封装成wrapper放在容器中,在请求时通过Mapping Data去找到某个Wrapper运行,这都是由StandardContext容器来处理的。在tomcat7及其以后的版本里上面这段代码都被移除了【在tomcat源码里已经找不到org.apache.catalina.servlets.InvokerServlet类了】。
打开tomcat源码org.apache.tomcat.util.http.mapper.Mapper搜索internalMapWrapper方法,即可看到在该方法内定义匹配的七大顺序:
Rule 1 -- Exact Match 精确匹配 Servlet
Rule 2 -- Prefix Match 前缀匹配 JSP
Rule 3 -- Extension Match 扩展匹配
Rule 4a -- Welcome resources processing for exact macth
Rule 4b -- Welcome resources processing for prefix match
Rule 4c -- Welcome resources processing for physical folder
Rule 7 -- Default servlet DefaultServlet--静态资源
可见最后匹配的才是DefaultServlet,也就是说当一个HTTP请求过来后先去找Mapping Data中的path去匹配Servlet的url,没有才去按规则2匹配下一个,就这样一直到最后DefaultServlet,如果到最后还没匹配到怎么办?返回前台404,资源未找到。
Session 与 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷。然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的,例如使用 Cookie 来传递信息时,随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也很大,试想假如 Cookie 占用 200 个字节,如果一天的 PV 有几亿的时候,它要占用多少带宽。所以大访问量的时候希望用 Session,但是 Session 的致命弱点是不容易在多台服务器之间共享,所以这也限制了 Session 的使用。
在理解web项目中的Session和cookie时,谨记以下点:
1、Session与Cookie的作用都是为了保持访问用户与后台服务器的交互状态。
2、Session并不是在有客户端访问时被创建的,而是在服务器端调用了HttpServletRequest.getSession(true)时才被创建的,如果他访问的是一个Servlet而且这个Servlet返回的不是Jsp,而是Html或其他格式的页面,那么就需要request.getSession()才会生成Session,如果Servlet返回的是一个Jsp或者直接访问的就是一个Jsp,那么你要知道HttpSession是Jsp的内置对象,当这个Jsp被编译称Servlet时就已经被创建了。总结来说就是Session不是主动生成的,而是需要后端调用getSession()方法时才生成Session。
基于 Cookie,如果你没有修改 Context 容器个 cookies 标识的话,默认也是支持的
基于 SSL,默认不支持,只有 connector.getAttribute("SSLEnabled") 为 TRUE 时才支持
当浏览器不支持Cookie功能时,浏览器会将用户的SessionCookieName重写到用户请求的URL参数中,他的传递格式如/path/Servlet;name=value;name2=value2?name3=value3,其中"Servlet;"后面的K-V就,就是要传递的Path Parameters,服务器会从这个Path Parameters中拿到用户配置的SessionCookieName。关于这个SessionCookieName,如果你在web.xml中配置了session-config配置项的话,其 cookie-config 下的 name 属性就是这个 SessionCookieName 值,如果你没有配置 session-config 配置项,默认的 SessionCookieName 就是大家熟悉的“JSESSIONID”。接着 Request 根据这个 SessionCookieName 到 Parameters 拿到 Session ID 并设置到 request.setRequestedSessionId 中。
请注意如果客户端也支持 Cookie 的话,Tomcat 仍然会解析 Cookie 中的 session id,并会覆盖 URL 中的 Session ID。
如果是第三种情况的话将会根据 javax.servlet.request.ssl_session 属性值设置 Session ID。
有了SessionId服务器就可以创建HttpSession对象了,第一次触发是通过request.getSession()方法,如果当前的Session Id还没有对应的HttpSession对象那么就创建一个新的,并将这个对象添加到org.apache.catalina. Manager 的 sessions 容器中保存,Manager 类将管理所有 Session 的生命周期,Session 过期将被回收,服务器关闭,Session 将被序列化到磁盘等。只要这个 HttpSession 对象存在,用户就可以根据 Session ID 来获取到这个对象,也就达到了状态的保持。
<?xml version='1.0' encoding='utf-8'?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost"> <Context path="" docBase="C:UsersadminDesktopServletDemowebapp" reloadable="true"/> </Host> </Engine> </Service> </Server>
// web.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <filter> <filter-name>helloFilter</filter-name> <filter-class>demo.HelloFilter</filter-class> </filter> <filter-mapping> <filter-name>helloFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>hello_world</servlet-name> <servlet-class>demo.HelloServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello_world</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
package demo; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 最简单的Servlet * @author Winter Lau */ public class HelloServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.getWriter().println("Hello World!"); } }
package demo; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; public class HelloFilter implements Filter { @Override public void init(FilterConfig arg0) throws ServletException { System.out.println("Filter 初始化"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; System.out.println("拦截 URI=" + request.getRequestURI()); chain.doFilter(req, res); } @Override public void destroy() { System.out.println("Filter 结束"); } }
在上面一开始的我们是没有使用Jsp的,也没有在service()里使用request.getSession(),编译一下将生成的class文件覆盖WEB-INF下demo里的class文件,然后启动Tomcat
# 编译命令
javac -encoding utf-8 -classpath C:UsersadminDesktopServletDemosrcdemoservlet-api.jar C:UsersadminDesktopServletDemosrcdemo*.java
req.getSession();
重新编译,将编译后的class文件覆盖WEB-INF下的class文件,清空浏览器缓存,再次启动Tomcat。
对比以上两张图,得出结论,JSESSIONID是服务器调用getSession()才生成的。
要想了解更多,可以看看最开始推荐的那篇关于Cookie的文章。
只需要cd 到jdk的bin目录下,然后执行下面代码即可,它会在和Test.java的同目录下生成Test.calss文件。
javac C:UsersServletDemosrcdemoTest.java
编译Servlet文件,由于Servlet文件依赖servlet-api.jar包,你没有这个包,编译的时候会报错,因为它不认识Servlet.java里的HttpServletRequest等对象,所以,你要想编译的话,必须将servlet-api.jar的路径配置在CLASSPATH的最前面:
,;%TOMCAT_HOME%libservlet-api.jar
Linux下为UTF-8编码,javac编译gbk编码的java文件时,报错:编码UTF8的不可映射字符,解决办法:
javac -encoding gbk ServletTest.java
Windows下为GBK编码,javac编译utf-8编码的java文件时,报错:编码GBK的不可映射字符,解决办法:
javac -encoding utf-8 ServletTest.java
如果没把servlet-api.jar放在classpath里你也可以这样写:
javac -encoding utf-8 -classpath C:Userssrcdemoservlet-api.jar C:Userssrcdemo*.java
深析Tomcat工作流程、Servlet深入、Session、cookie