浅析Tomcat工作流程

浅析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?

 

读红薯写Servlet的文章,遇到的问题和探索。

 

底层必读

读许令波文

一段简短的server.xml配置

+自我理解

Tomcat结构

Tomcat工作流程(前半部分)

观察者ContextConfig做了哪些事

Tomcat工作流程(后半部分)

上下文和“场景”

tomcat对静态资源是怎么访问的?

Session和Cookie了解

理解Session和Cookie

测试Session和Cookie

Servlet的编译命令

编译普通的java文件

编译Servlet文件

编译过程编码问题

接下来要读的是

 

 

 

按照大佬给的步骤一顿操作猛如虎,但是自己操作完之后就开始问了自己几个问题,哪里不会问哪里。

 

底层必读

 HttpServlet

 ServetConfig

 ServletContext

 Filter

 FilterConfig

 FilterChain

 RequestDispatcher

 HttpServletRequest

 HttpServletResponse

 HttpSession

 一些 Listenser 类

 

读许令波文

 

一段简短的server.xml配置

<?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属性中设定。

Context元素的属性:

 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。

 

 

+自我理解

 

Tomcat结构

1.Tomcat结构理解

 

1让我理解了Tomcat整体的结构,在整个Tomcat中,被容器的概念贯穿,四层结构Engine,Host,Context,Weapper,从顶层Engine开始,到Host,再到Context,最后是Wrapper,每一层都是一个容器。每一个Context都代表着一个单独的web项目。在Context中为了解耦,将Servlet封装在Tomcat自身的Wrapper容器里,使得Tomcat与Servlet的关联不在紧密。

 

 

 

Tomcat工作流程(前半部分)

2.从启动到完成的整个工作流程

 

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");
}

观察者ContextConfig做了哪些事

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.观察者做的一系列事情

 

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门面类)都完成了。

 

Tomcat工作流程(后半部分)

这时候观察者的任务完成,也就是到了图1的12,13,14已完成。然后在图1的16,17,18,19是创建一个Connector连接器启动http服务初始化MapperListener,当socket连接上服务器后,一个Http请求过来被Connector连接到Tomcat,MapperListener被触发,它会读取这个Http请求的URL地址,这个MapperListener中含有上下文所有的信息,看图4:

4.MapperListener为什么会有上下文所有信息

 

既然MapperListener含有上下文所有的信息,自然也知道Mapper和Wrapper,自然也能知道这个URL请求的是那个web服务的哪个Servlet。看下图5,了解请求过程。

 

5

 

 

看图6了解从Http请求过来到被MapperListener触发,获取URL信息找到映射的Mapping和Wrapper(Servlet)并封装成MappingData向后传递。经过引擎找到Java虚拟机,getHost获取虚拟主机(简单理解:虚拟主机是空间 就是我们做网站时候存放网站程序的地方),getContext拿到上下文,找到对应的Wrapper。

6.映射URL到交给Servlet处理业务

 

上下文和“场景”

在此处插入一段许大佬一段文字和代码,一段让我深有感触的地方:

 

7.Servlet 顶层类关联图

 

从上图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.Servlet体系和Tomcat体系的系列图

8.1

 

下图8.2描述的是request和response在不同模块中会有不同封装。我们在service方法中通常使用的是HttpServletRequest和HttpServletResponse,这二者在"场景"中是ServletRequest 和 ServletResponse,由下图8.2即可知道为什么service方法可以使用HttpServletRequest和HttpServletResponse。

 

8.2

tomcat对静态资源是怎么访问的?

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类了】。

 

 

Mapper对资源调用的七大规则

打开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了解

 

理解Session和Cookie

 

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。

3、Session是基于Cookie工作的。

有三种方式可以让Session正常工作:

 基于 URL Path Parameter,默认就支持

 基于 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 来获取到这个对象,也就达到了状态的保持。

 

测试Session和Cookie

 先配置一下初始环境,Tomcat下的server.xml

<?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>

 项目结构

-- ServletDemo

-- src

-- demo

-- HelloFilter.java

-- HelloServlet.java

-- servlet-api.jar

-- webapp

-- WEB-INF

-- classes

-- demo

-- HelloFilter.class

-- HelloServlet.calss

-- web.xml

 项目基本文件

// 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

 

 

 修改service()方法,在其内部添加

req.getSession();

重新编译,将编译后的class文件覆盖WEB-INF下的class文件,清空浏览器缓存,再次启动Tomcat。

 

对比以上两张图,得出结论,JSESSIONID是服务器调用getSession()才生成的。

要想了解更多,可以看看最开始推荐的那篇关于Cookie的文章。

 

 

最后附上一张Servlet中的Listener图:

 

Servlet的编译命令

 

编译普通的java文件

只需要cd 到jdk的bin目录下,然后执行下面代码即可,它会在和Test.java的同目录下生成Test.calss文件。

javac C:UsersServletDemosrcdemoTest.java

编译Servlet文件

编译Servlet文件,由于Servlet文件依赖servlet-api.jar包,你没有这个包,编译的时候会报错,因为它不认识Servlet.java里的HttpServletRequest等对象,所以,你要想编译的话,必须将servlet-api.jar的路径配置在CLASSPATH的最前面:

,;%TOMCAT_HOME%libservlet-api.jar

然后才能想编译普通文件那样编译Servlet文件

 

编译过程编码问题

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

 

前进时,请别遗忘了身后的脚印。
原文地址:https://www.cnblogs.com/liudaihuablogs/p/13462457.html