程序员人生 网站导航

Servlet的历史与规范

栏目:php教程时间:2016-06-13 11:11:56

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

    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服务器接收1个用户要求;

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

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

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

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

3. Servlet的发展

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

    • Servlet代码中有大量冗余代码,每一个Servlet都有1模1样的或基本近似的代码,比如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的历史部份。↓下面来说1讲Servlet规范中重要知识点。


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

2、Servlet规范

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

1. Servlet概述

Servlet有两种意思:

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

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

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

Servlet 容器/引擎:

  1. Servlet容器也能够叫引擎,Container/Engine,用于履行Servlet

  2. 容器是以内嵌附加组件的情势存在于Web服务器或利用服务器中的。

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

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




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

为了方便描写,先声明1些名词:

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



2. Servlet Interface

Servlet生命周期:

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

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

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

  3. 要求处理:
    初始化后,Servlet就能够接受要求了。

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

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

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

  4. 终止服务:

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

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

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

3. Request

1. 要求路径元素

  1. Context Path
    '/'开头,但不以'/'结尾

  2. Servlet Path
    '/'开头

  3. PathInfo
    要末为null要末以'/'开头

    例如:
    这里写图片描述
    这里写图片描述

2. 要求编码

  1. 要求会以甚么编码情势送给服务器端呢?

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

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

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

4. Servlet Context

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

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

  • getResource

  • getResourceAsStream

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

    • ServletContextPath的路径。

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

5. Response

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

6. Filtering

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

其它没甚么好说的,只需要注意的1点是:

  • 匹配的Filter极可能是多个而不是1个,所以Filter是1个FilterChain设计。

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

7. 映照要求到Servlet的规则

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

    • 客户端要求的URL - ServletContext上下文路径 - 路径参数

    • 例如当客户端要求的URL是:http://www.google.com/testproject/action/servlet1?param1=asd时,那末需要映照的路径是:/action/servlet1

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

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

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

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

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

  3. 映照规范

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

    2. '*.'开始的字符串用于扩大名映照。

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

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

  4. 隐式映照
    容器可以为1些扩大名定义1些影射映照,比如来1个.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文件(即1个Servlet);
      • 然后再compile.class
    3. default:org.apache.catalina.servlets.DefaultServlet这个Servlet终究会:

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

8. Session

  1. 会话跟踪机制

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

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

  2. 几种方法

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

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

  3. 如何使用Session跟踪机制

    1. 需要程序员手动调用

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

      这里写图片描述

      这里写图片描述

9. Web Application

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

  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这3个对象添加了事件。这可让Servlet开发人员更好的控制上述3个对象生命周期。

  1. ServletContext

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

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

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

实例化时机:

容器必须在开始履行进入利用的第1个要求之前完成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. Listener调用顺序
    根据在web.xml中注册的顺序来被调用。例外的,生命周期中的烧毁事件触发的destroy会被反方向的顺次调用。

2. 关于初始化参数:

  1. ServletContext的初始化参数:

    1. 设值:由于1个利用只有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. 取值:只有获得了某1个FilterFilterConfig对象以后,才能使用此对象的方法getInitParameter(String name)方法来取值。

    3. 另外的:当你自己写1个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)方法来取值。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐