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

     

    前进时,请别遗忘了身后的脚印。
  • 相关阅读:
    Upgrading to MySQL 5.7---focusing on temporal types
    mysqldump备份7
    mysqldump原理5
    mysqldump原理4
    mysqldump原理3
    mysqldump原理2
    mysqldump原理1
    MySQL复制中slave延迟监控
    赵浮云的blog 关注IT运维,开源硬件。
    爱维帮---LVS
  • 原文地址:https://www.cnblogs.com/liudaihuablogs/p/13462457.html
Copyright © 2020-2023  润新知