Servlet的历史与规范

Posted 有且仅有

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet的历史与规范相关的知识,希望对你有一定的参考价值。

一、Servlet历史

1. Servlet的由来

  • 背景
    上世纪90年代,随着Internet和浏览器的飞速发展,基于浏览器的B/S模式随之火爆发展起来。
    最初,用户使用浏览器向WEB服务器发送的请求都是请求静态的资源,比如html、css等。
    但是可以想象:根据用户请求的不同动态的处理并返回资源是理所当然必须的要求。

  • CGI
    必须要满足上述需求,所以CGI(Common Gateway Interface)出现了。CGI程序使用C、Shell Script或Perl编写,CGI是为特定操作系统编写的(如UNIX或Windows),不可移植,CGI程序对每个请求产生新的进程去处理。步骤如下:

    1. WEB服务器接收一个用户请求;
    2. WEB服务器将请求转交给CGI程序处理;
    3. CGI程序将处理结果返回给WEB服务器;
    4. WEB服务器把结果送回用户;
      这里写图片描述
  • Java
    与此同时,Java语言也在迅速发展。必然的,Java要支持上述需求。
    Java有两种方案来实现动态需求,它们都属于JavaEE技术的一部分。

    1. applet
      这是纯客户端(浏览器)方案,applet就是浏览器中的Java插件,浏览器通过它就能够解释执行WEB服务器发过来的Java代码,从而实现动态。但是,显然这种方案不好,既需要浏览器必须安装插件,又受限于浏览器,所以Java代码不能太多和太复杂。

      比如,如果安装了JRE,虽然IE浏览器会自动启用Java插件,但是你可以轻易禁止。再比如Chrome还需要你手动去安装插件才行,普通用户连Java是什么都不知道他怎么会去装呢?
      IE如下图:

      这里写图片描述

    2. Servlet
      既然浏览器不方便执行Java代码,那自然还是服务端来执行了,所以Servlet出现了,Servlet就是server端的applet的意思。

2. Servlet的工作原理

其实Servlet的工作原理基本类似上面的CGI,不过Servlet比CGI更好。

  1. WEB服务器接收一个用户请求;

  2. WEB服务器将请求转交给WEB服务器关联的Servlet容器;

  3. Servlet容器找到对应的Servlet并执行这个Servlet;

  4. Servlet容器将处理结果返回给WEB服务器;

  5. WEB服务器把结果送回用户;

3. Servlet的发展

  1. Servlet诞生后,SUN公司很快发现了Servlet编程非常繁琐,这是因为:

    • Servlet代码中有大量冗余代码,每个Servlet都有一模一样的或基本近似的代码,比如out输出你可能就得写成百遍;
    • 开发Servlet必须精通网页前端和美工,你得非常不直观的在Servlet中写前端代码,这使得实现各种页面效果和风格非常困难。
  2. 所以,SUN借鉴了Microsoft的ASP,正式提出JSP(Servlet1.1),已期望能代替Servlet。但是很快,SUN发现JSP也有问题:

    • 前端开发人员需要看JSP中大量的令他困惑的后端代码;
    • 同样,Servlet开发人员也得在复杂的前端代码中找到其能写Servlet代码的地方;
  3. 所以,Servlet1.2出现了,这个版本的Servlet倡导了MVC思想:

    • JSP(V):将后端代码封装在标签中,使用大量的标签,JSP只用来写前端代码而不要有后台代码;
    • Servlet(C):Servlet完成Controller的功能再加上部分代码逻辑;
    • Model(M):Servlet将数据发送给Model,Model包括部分代码逻辑,最主要的Model也代表着被组织好的用于返回的数据。最终,Model数据会被显示在JSP上(V)。

基本上到这里Servlet的大方向已经固定了,随之,成熟的发展至今 - 2016年5月26日…


↑以上,是关于Servlet的历史部分。↓下面来讲一讲Servlet规范中重要知识点。


声明:以下内容归纳自官方Servlet规范和JavaEE规范等文档。

二、Servlet规范

下载地址
Servlet规范官方地址:JSR 340: Java Servlet 3.1 Specification(中文版网上有人翻译了,可以自己搜索找找)
可以自己下载阅读,最终版final是2013年5月28发布的Servlet3.1。

1. Servlet概述

Servlet有两种意思:

  1. 广义上是:基于Java技术的Web组件,被容器托管,用于生成动态内容。

    再详细点说,Servlet是JavaEE组件中的 -> Web组件的 -> 一种。
    (其它两种是JavaServer Faces和JavaServer Page)

  2. 狭义上说:是JavaEE API中的一个interfacejavax.servlet.Servlet

Servlet 容器/引擎:

  1. Servlet容器也可以叫引擎,Container/Engine,用于执行Servlet

  2. 容器是以内嵌或者附加组件的形式存在于Web服务器或者应用服务器中的。

  3. 容器本身(不依赖Web服务器)就提供了基于请求/响应发送模型的网络服务,解码基于MIME的请求,格式化基于MIME的响应。

  4. 所有容器必须实现HTTP协议的请求/响应模型。其它协议不强求,如HTTPS。




下面开始说一下规范的核心要点。
请注意:我不是要完整的阐述Servlet规范,毕竟你可以直接看规范。这里我只是要记录我认为重要的点。

为了方便描述,先声明一些名词:

  • web.xml = 部署描述符(Deployment Descriptor )
  • 容器 = Servlet Container/Engine



2. Servlet Interface

Servlet生命周期:

Servlet的生命(周期)是由容器管理的,换句话说,Servlet程序员不能用代码控制其生命。

  1. 加载和实例化:
    时机取决于web.xml的定义,如果有<load-on-startup>x</load-on-startup>则在容器启动时,反之则在第一次针对这个Servlet的请求发生时。

  2. 初始化:
    实例化后会立马进行初始化。也就是执行init方法。

  3. 请求处理:
    初始化后,Servlet就可以接受请求了。

    • 基本方式是执行Servlet接口中的service方法。

    • 当然,API也提供了HttpServlet抽象类,其中有doGetdoPost等特殊方法。

    • 注意:任意的容器按照规范必须实现上述几种方法,所以你的代码写在这几个方法中都可以。

  4. 终止服务:

    1. 容器会在合适的时候销毁某个Servlet对象,这个策略取决于容器的开发者/商。

    2. 在容器关闭的时候Servlet对象一定会被销毁。

    3. 当1或2发生时,也就是Servlet对象被销毁时,destroy方法会被调用。

3. Request

1. 请求路径元素

  1. Context Path

    • 通常以'/'开头,但不以'/'结尾;
    • 如果是容器默认Context,则为空字符串:""
  2. Servlet Path

    • 首先,这与自己配置的<url-pattern>有关(具体理解可看下图);
    • 其次,通常以'/'开头;
    • 最后,如果是匹配"/*"""的话则为空字符串"";
  3. PathInfo

    • 取决于Servlet Path划走了多少;
    • 通常要么为null要么以'/'开头;

    例如:下图首先展示了1个Context Path和其下3个Servlet的配置,然后给出了3个Request Path的例子来具体分析划分情况

    这里写图片描述
    这里写图片描述

2. 请求编码

  1. 请求会以什么编码形式送给服务器端呢?

    HTTP协议没有强制规定,所以实际上这是由浏览器自己决定的,决定后浏览器可以通过entity-body中的Content-Type项告诉服务器自己使用了什么编码。但是!大部分情况下浏览器不会这么做的。比如说,Get请求是没有entity-body的,自然也不会使用Content-Type了。

  2. 在服务端,我们Servlet规范 规定了如果请求没有指定编码的话,容器必须使用ios-8859-1来解码。为了让开发人员知道请求给没给出编码,容器会在没给的情况下通过getCharacterEncoding 返回null来告诉我们。

  3. 为了在我们明知道不是ISO-8859-1编码的情况下给我们自主权,ServletRequest提供了setCharacterEncoding(String enc);

4. Servlet Context

一个Web应用对应一个ServletContext接口的实例。

1. 获取资源:
ServletContext接口提供了直接访问Web应用中静态内容(意思是说你获取jsp返回就是jsp源码)层次结构的文件的方法。

  • getResource

  • getResourceAsStream

    这两个方法需要的String参数必须是以'/'开头的,这个'/'代表相对于:

    • ServletContextPath的路径。

    • 或者WEB-INF/lib中的jar中的METE-INF/resources路径。

5. Response

代表容器的响应,没有特别需要注意的。

6. Filtering

过滤器,是Java中一种代码重用技术,通过拦截请求改变HTTP请求的内容、响应、Header信息。

  1. 实例化、初始化:

    容器启动时,自上而下的实例化并初始化Filter

  2. 责任链

    匹配的Filter很可能是多个而不是一个,所以Filter是一个FilterChain设计。

    所有FilterdoFilter(request, response, chain);方法最终都需要调用chain.doFilter(request, response);方法来触发调用链的下一个Filter或者如果是最后一个Filter那么直接访问目标资源。

7. 映射请求到Servlet的规则

  1. 用于映射到Servlet的路径是:

    用于映射到Servlet的路径 = URL - (ServletContext + 路径参数)

    例如当客户端请求的URL是:http://www.google.com/testproject/action/servlet1?param1=asd时,那么映射到的Servlet路径是:/action/servlet1

  2. 重要!重要!重要!选择映射到的Servlet的规则是,按照如下的顺序查找,如果已经选定一个就会匹配成功不会继续往下:

    1. 先精确匹配<url-pattern>,成功则选择。(精确匹配

    2. 递归遍历路径树,选择最长的路径匹配。(最长匹配

    3. 如果URL最后一个部分包括扩展名,比如/action/servlet1.jsp,容器将选择专门声明了要处理此扩展名请求的Servlet。(扩展名匹配

    4. 如果123都没有匹配,容器将提供一个后备方案,一般来说是提供一个"default" Servlet。(低保匹配,优先级最低,提供一个最低保障)

  3. 映射规范

    1. '/'字符开始,以'/*'字符结束的字符串:用于路径匹配。(这是一个准确严谨的定义而已)

    2. '*.'开始的字符串用于扩展名映射

    3. 空字符串""是特殊的URL,精确映射到应用的上下文根,即http://host:port/<context-root>/,这种情况("")相当于<url-pattern>/<url-pattern>

    4. 只包含'/'字符的字符串表示应用的"default" Servlet

      • 此时,Servlet Path = 请求URL - Context Path,且Path Info = null

      • Spring MVC 配置front Controller(DispatcherServlet)时,<url-pattern>/<url-pattern>就可以这样做;

      • 需要注意:这个"/"不代表匹配所有,只是代表当没有其它Servlet匹配这个请求时,"default" Servlet去擦屁股。

  4. 隐式映射

    容器可以为一些扩展名定义一些隐射映射,比如来一个.jsp的,那么如果上面的显示映射没有拦截.jsp,此时这里应该发挥作用。

  5. tomcat中是怎么做的(我看了Jetty也是差不多)

    1. tomcat在其顶级的web.xml中定义了且开放了2个<servlet>(这么说是因为其实不只2个,不过那几个是注释状态)

      <servlet>
          <servlet-name>default</servlet-name>
          <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
          ...
          <load-on-startup>1</load-on-startup>
      </servlet>
      
      <servlet>
          <servlet-name>jsp</servlet-name>
          <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
          ...
          <load-on-startup>3</load-on-startup>
      </servlet>
      
      <!-- default servlet mapping -->
      <servlet-mapping>
          <servlet-name>default</servlet-name>
          <url-pattern>/</url-pattern>
      </servlet-mapping>
      
      <!-- The mappings for the JSP servlet -->
      <servlet-mapping>
          <servlet-name>jsp</servlet-name>
          <url-pattern>*.jsp</url-pattern>
          <url-pattern>*.jspx</url-pattern>
      </servlet-mapping>

      可以看到tomcat践行了如上所述的Servlet规范。

    2. jsp:org.apache.jasper.servlet.JspServlet这个Servlet最终会:

      • 先将.jsp文件变为java文件(即一个Servlet);
      • 然后再compile.class
    3. default:org.apache.catalina.servlets.DefaultServlet这个Servlet最终会:

      • 为所有没有被匹配到的URL做匹配,即”低保”匹配;
      • 如果找到相应的资源了就返回,没有找到或者发生一些异常就返回400/404等状态码及其页面,长相如下:
        这里写图片描述

8. Session

  1. 会话跟踪机制

    HTTP协议是无状态的,但是记录状态,也就是说记录来自同一客户端的请求的需求是必须的。所以人们发明了会话跟踪机制。

    Servlet规范定义了一个简单的HttpSession接口,允许容器使用几种方法来实现会话跟踪,从而使得Web应用开发人员不必来关心和写这块的代码。(容器只能帮我们实现单机会话跟踪,分布式应用多机状态下,我们需要自己写代码实现Session同步)。

  2. 几种方法

    1. Cookies
      是最常用的机制,且所有Servlet容器必须支持。容器有能力(但是需要程序员显式调用)向客户端发送一个cookie,用来记录会话,标准名字必须是JSESSIONID

    2. URL重写
      比如:http://www.sss.com/aa/bb.html;jessionid=1234
      要注意,使用的是分号';',这个叫路径参数,区别于查询参数。

  3. 如何使用Session跟踪机制

    1. 需要程序员手动调用

      //a. 有session返回,没有新生成一个
      request.getSession();
      request.getSession(true);
      //b. 有session返回,没有返回null
      request.getSession(false);
    2. 如果使用的a方式,再加上使用cookie机制的情况下,容器的第1次响应(Response)会向客户端写一个JSESSIONID,客户端从第2次请求(Request)开始会带着JSESSIONID,如下图:

      这里写图片描述

      这里写图片描述

9. Web Application

1. WEB-INF目录:
此目录是一个特殊目录,不能由容器直接提供给客户端访问。可以通过:

  1. 调用ServletContextgetResourcegetResourceAsStream来访问。

  2. 还可以通过RequestDispatcher来调用从而公开这些内容。

2. WEB-INF目录的内容:

  1. /WEB-INF/web.xml部署描述文件。

  2. Servlet和其它类的目录/WEB-INF/classes/

  3. Java归档文件(jar)区域/WEB-INF/lib/*.jar

10. Application Lifecycle Events - 应用生命周期事件

Servlet API为ServletContextHttpSessionServletRequest这三个对象添加了事件。这可以让Servlet开发人员更好的控制上述3个对象生命周期。

  1. ServletContext

    事件类型描述监听器接口
    生命周期ServletContext刚创建并可用于服务它的第一个请求或即将关闭javax.servlet.ServletContextListener
    更改属性ServletContext的属性已添加、已删除、已替换javax.servlet.ServletContextAttributeListener
  2. HttpSession

    事件类型描述监听器接口
    生命周期会话已创建、销毁、超时javax.servlet.http.HttpSessionListener
    更改属性HttpSession的属性已添加、已删除、已替换javax.servlet.http.HttpSessionAttributeListener
    改变IDHttpSession的ID将被改变javax.servlet.http.HttpSessionIdListener
    会话迁移HttpSession已被激活或钝化javax.servlet.http.HttpSessionActivationListener
    对象绑定对象已经从HttpSession绑定或解绑javax.servlet.http.HttpSeesionBindingListener
  3. ServletRequest

    事件类型描述监听器接口
    生命周期一个请求已经开始由Web组件处理javax.servlet.ServletRequestListener
    更改属性已在servlet上添加、移除、替换属性javax.servlet.ServletRequestAttributeListner
    异步事件超时、连接终止或完成异步操作处理javax.servlet.AsyncListener

实例化时机:

容器必须在开始执行进入应用的第一个请求之前完成Web应用中所有监听器类的实例化。

11. Deployment Descriptor - web.xml

1. 关于顺序

  1. Servlet初始化顺序

    通过<load-on-startup>x</load-on-startup>中的x来指定初始化顺序,必须不小于0,越小越早加载。

  2. Filter

    1. 初始化顺序:

      按照web.xml中声明的顺序自上而下的初始化。

    2. 过滤器链构造规则:

      规范中6.2.4 节,待验证总结。

    3. 过滤顺序

      如果匹配了多个url-pattern,按照自上而下的顺序。

  3. Listener调用顺序

    根据在web.xml中注册的顺序来被调用。而销毁事件触发的destroy会被反方向的依次调用。

2. 关于初始化参数:

  1. ServletContext的初始化参数:

    1. 设值:因为一个应用只有一个ServletContext,所以是直接声明在根<web-app>下的:

      <context-param>
         <param-name>contextParam1</param-name>
         <param-value>11context11</param-value>
      </context-param>
    2. 取值/设值:只要获取到了ServletContext对象,就可以使用其getInitParameter(String name)方法来取值,同时也可以使用setInitParameter(String name, String value);来设值,所以很多地方都可以取值/设值。

  2. Listener的初始化参数:

    1. 设值:监听器没有自己独立的初始化参数配置,想要使用的话可以借助将参数配置在ServletContext的初始化参数位置。

    2. 取值/设值:如上。

  3. Filter的初始化参数:

    1. 设值:只能在<filter>中使用<init-param>来设置值:

      <filter>
          <filter-name>FirstFilter</filter-name>
          <filter-class>filter.FirstFilter</filter-class>
          <init-param>
              <param-name>firstFilterParam1</param-name>
              <param-value>11filter11</param-value>
          </init-param>
      </filter>
    2. 取值:只有获取了某一个FilterFilterConfig对象之后,才能使用此对象的方法getInitParameter(String name)方法来取值。

    3. 另外的:当你自己写一个Filter的时候,除了实现Filter接口之外,你还可以选择学习类似GenericServlet的方式,额外的实现FilterConfig接口并覆盖其getInitParameter(String name)方法,从而可以在自己的Filter的任何方法中都能调用取值方法而无须显示获取FilterConfig对象。像下面这样:

      public class FirstFilter implements Filter, FilterConfig{
      ...
      }
  4. Servlet的初始化参数:

    1. 设值:只能在<servlet>中使用<init-param>来设置值:

      <servlet>
          <servlet-name>FirstServlet</servlet-name>
          <servlet-class>servlet.FirstServlet</servlet-class>
          <init-param>
              <param-name>FirstServletParam1</param-name>
              <param-value>11FirstServlet11</param-value>
          </init-param>
      </servlet>
    2. 取值:跟Filter类似,ServletServletConfig对象,只要获取到它就可以使用其getInitParameter(String name)方法来取值。

    3. 另外的:通常我们不会直接实现Servlet接口而是使用继承GenericServlet/HttpServlet的方式,那么(如我在上面Filter“另外的”部分所说)我们就可以利用它们的实现直接使用getInitParameter(String name)方法来取值。


转载注明出处:http://blog.csdn.net/u010297957/article/details/51498018

以上是关于Servlet的历史与规范的主要内容,如果未能解决你的问题,请参考以下文章

Servlet—Cookie(显示用户上次访问时间显示商品浏览历史)

JavaWeb Filter

Web app ------ 从Servlet读取Json数据并显示,生成历史数据曲线图

Web app ------ 从Servlet读取Json数据并显示,生成历史数据曲线图

servlet3.0初体验

Servlet 3.0笔记之异步请求Comet推送iFrame示范