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程序对每个请求产生新的进程去处理。步骤如下:- WEB服务器接收一个用户请求;
- WEB服务器将请求转交给CGI程序处理;
- CGI程序将处理结果返回给WEB服务器;
- WEB服务器把结果送回用户;
Java
与此同时,Java语言也在迅速发展。必然的,Java要支持上述需求。
Java有两种方案来实现动态需求,它们都属于JavaEE技术的一部分。applet
这是纯客户端(浏览器)方案,applet就是浏览器中的Java插件,浏览器通过它就能够解释执行WEB服务器发过来的Java代码,从而实现动态。但是,显然这种方案不好,既需要浏览器必须安装插件,又受限于浏览器,所以Java代码不能太多和太复杂。比如,如果安装了JRE,虽然IE浏览器会自动启用Java插件,但是你可以轻易禁止。再比如Chrome还需要你手动去安装插件才行,普通用户连Java是什么都不知道他怎么会去装呢?
IE如下图:Servlet
既然浏览器不方便执行Java代码,那自然还是服务端来执行了,所以Servlet出现了,Servlet就是server端的applet的意思。
2. Servlet的工作原理
其实Servlet的工作原理基本类似上面的CGI,不过Servlet比CGI更好。
WEB服务器接收一个用户请求;
WEB服务器将请求转交给WEB服务器关联的Servlet容器;
Servlet容器找到对应的Servlet并执行这个Servlet;
Servlet容器将处理结果返回给WEB服务器;
WEB服务器把结果送回用户;
3. Servlet的发展
Servlet诞生后,SUN公司很快发现了Servlet编程非常繁琐,这是因为:
- Servlet代码中有大量冗余代码,每个Servlet都有一模一样的或基本近似的代码,比如out输出你可能就得写成百遍;
- 开发Servlet必须精通网页前端和美工,你得非常不直观的在Servlet中写前端代码,这使得实现各种页面效果和风格非常困难。
所以,SUN借鉴了Microsoft的ASP,正式提出JSP(Servlet1.1),已期望能代替Servlet。但是很快,SUN发现JSP也有问题:
- 前端开发人员需要看JSP中大量的令他困惑的后端代码;
- 同样,Servlet开发人员也得在复杂的前端代码中找到其能写Servlet代码的地方;
所以,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有两种意思:
广义上是:基于Java技术的Web组件,被容器托管,用于生成动态内容。
再详细点说,Servlet是JavaEE组件中的 -> Web组件的 -> 一种。
(其它两种是JavaServer Faces和JavaServer Page)狭义上说:是JavaEE API中的一个
interface
,javax.servlet.Servlet
;
Servlet 容器/引擎:
Servlet容器也可以叫引擎,Container/Engine,用于执行
Servlet
。容器是以内嵌或者附加组件的形式存在于Web服务器或者应用服务器中的。
容器本身(不依赖Web服务器)就提供了基于请求/响应发送模型的网络服务,解码基于MIME的请求,格式化基于MIME的响应。
所有容器必须实现HTTP协议的请求/响应模型。其它协议不强求,如HTTPS。
下面开始说一下规范的核心要点。
请注意:我不是要完整的阐述Servlet规范,毕竟你可以直接看规范。这里我只是要记录我认为重要的点。
为了方便描述,先声明一些名词:
web.xml
= 部署描述符(Deployment Descriptor )- 容器 = Servlet Container/Engine
2. Servlet Interface
Servlet
生命周期:
Servlet
的生命(周期)是由容器管理的,换句话说,Servlet
程序员不能用代码控制其生命。
加载和实例化:
时机取决于web.xml
的定义,如果有<load-on-startup>x</load-on-startup>
则在容器启动时,反之则在第一次针对这个Servlet的请求发生时。初始化:
实例化后会立马进行初始化。也就是执行init
方法。请求处理:
初始化后,Servlet
就可以接受请求了。基本方式是执行
Servlet
接口中的service
方法。当然,API也提供了
HttpServlet
抽象类,其中有doGet
、doPost
等特殊方法。注意:任意的容器按照规范必须实现上述几种方法,所以你的代码写在这几个方法中都可以。
终止服务:
容器会在合适的时候销毁某个
Servlet
对象,这个策略取决于容器的开发者/商。在容器关闭的时候
Servlet
对象一定会被销毁。当1或2发生时,也就是
Servlet
对象被销毁时,destroy
方法会被调用。
3. Request
1. 请求路径元素
Context Path
:- 通常以
'/'
开头,但不以'/'
结尾; - 如果是容器默认
Context
,则为空字符串:""
;
- 通常以
Servlet Path
:- 首先,这与自己配置的
<url-pattern>
有关(具体理解可看下图); - 其次,通常以
'/'
开头; - 最后,如果是匹配
"/*"
或""
的话则为空字符串""
;
- 首先,这与自己配置的
PathInfo
:- 取决于
Servlet Path
划走了多少; - 通常要么为
null
要么以'/'
开头;
例如:下图首先展示了1个
Context Path
和其下3个Servlet
的配置,然后给出了3个Request Path
的例子来具体分析划分情况
- 取决于
2. 请求编码
请求会以什么编码形式送给服务器端呢?
HTTP协议没有强制规定,所以实际上这是由浏览器自己决定的,决定后浏览器可以通过
entity-body
中的Content-Type
项告诉服务器自己使用了什么编码。但是!大部分情况下浏览器不会这么做的。比如说,Get请求是没有entity-body
的,自然也不会使用Content-Type
了。在服务端,我们Servlet规范 规定了如果请求没有指定编码的话,容器必须使用
ios-8859-1
来解码。为了让开发人员知道请求给没给出编码,容器会在没给的情况下通过getCharacterEncoding
返回null
来告诉我们。为了在我们明知道不是
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信息。
实例化、初始化:
容器启动时,自上而下的实例化并初始化
Filter
。责任链
匹配的
Filter
很可能是多个而不是一个,所以Filter
是一个FilterChain
设计。所有
Filter
的doFilter(request, response, chain);
方法最终都需要调用chain.doFilter(request, response);
方法来触发调用链的下一个Filter
或者如果是最后一个Filter
那么直接访问目标资源。
7. 映射请求到Servlet的规则
用于映射到
Servlet
的路径是:用于映射到Servlet的路径 = URL - (ServletContext + 路径参数)
例如当客户端请求的URL是:
http://www.google.com/testproject/action/servlet1?param1=asd
时,那么映射到的Servlet路径是:/action/servlet1
重要!重要!重要!选择映射到的Servlet的规则是,按照如下的顺序查找,如果已经选定一个就会匹配成功不会继续往下:
先精确匹配
<url-pattern>
,成功则选择。(精确匹配)递归遍历路径树,选择最长的路径匹配。(最长匹配)
如果URL最后一个部分包括扩展名,比如
/action/servlet1.jsp
,容器将选择专门声明了要处理此扩展名请求的Servlet
。(扩展名匹配)如果123都没有匹配,容器将提供一个后备方案,一般来说是提供一个
"default" Servlet
。(低保匹配,优先级最低,提供一个最低保障)
映射规范
以
'/'
字符开始,以'/*'
字符结束的字符串:用于路径匹配。(这是一个准确严谨的定义而已)以
'*.'
开始的字符串用于扩展名映射空字符串
""
是特殊的URL,精确映射到应用的上下文根,即http://host:port/<context-root>/
,这种情况(""
)相当于<url-pattern>/<url-pattern>
。只包含
'/'
字符的字符串表示应用的"default" Servlet
此时,
Servlet Path
=请求URL - Context Path
,且Path Info = null
;Spring MVC
配置front Controller(DispatcherServlet
)时,<url-pattern>/<url-pattern>
就可以这样做;需要注意:这个
"/"
不代表匹配所有,只是代表当没有其它Servlet
匹配这个请求时,"default" Servlet
去擦屁股。
隐式映射
容器可以为一些扩展名定义一些隐射映射,比如来一个
.jsp
的,那么如果上面的显示映射没有拦截.jsp
,此时这里应该发挥作用。tomcat中是怎么做的(我看了Jetty也是差不多)
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规范。
jsp:
org.apache.jasper.servlet.JspServlet
这个Servlet
最终会:- 先将
.jsp
文件变为java
文件(即一个Servlet
); - 然后再
compile
成.class
;
- 先将
default:
org.apache.catalina.servlets.DefaultServlet
这个Servlet
最终会:- 为所有没有被匹配到的URL做匹配,即”低保”匹配;
- 如果找到相应的资源了就返回,没有找到或者发生一些异常就返回400/404等状态码及其页面,长相如下:
8. Session
会话跟踪机制
HTTP协议是无状态的,但是记录状态,也就是说记录来自同一客户端的请求的需求是必须的。所以人们发明了会话跟踪机制。
Servlet规范定义了一个简单的
HttpSession
接口,允许容器使用几种方法来实现会话跟踪,从而使得Web应用开发人员不必来关心和写这块的代码。(容器只能帮我们实现单机会话跟踪,分布式应用多机状态下,我们需要自己写代码实现Session同步)。几种方法
Cookies
是最常用的机制,且所有Servlet容器必须支持。容器有能力(但是需要程序员显式调用)向客户端发送一个cookie
,用来记录会话,标准名字必须是JSESSIONID
。URL重写
比如:http://www.sss.com/aa/bb.html;jessionid=1234
要注意,使用的是分号';'
,这个叫路径参数,区别于查询参数。
如何使用Session跟踪机制
需要程序员手动调用
//a. 有session返回,没有新生成一个 request.getSession(); request.getSession(true); //b. 有session返回,没有返回null request.getSession(false);
如果使用的a方式,再加上使用
cookie
机制的情况下,容器的第1次响应(Response)会向客户端写一个JSESSIONID
,客户端从第2次请求(Request)开始会带着JSESSIONID
,如下图:
9. Web Application
1. WEB-INF
目录:
此目录是一个特殊目录,不能由容器直接提供给客户端访问。可以通过:
调用
ServletContext
的getResource
和getResourceAsStream
来访问。还可以通过
RequestDispatcher
来调用从而公开这些内容。
2. WEB-INF
目录的内容:
/WEB-INF/web.xml
部署描述文件。Servlet
和其它类的目录/WEB-INF/classes/
。Java归档文件(jar)区域
/WEB-INF/lib/*.jar
。
10. Application Lifecycle Events - 应用生命周期事件
Servlet API为ServletContext
、HttpSession
、ServletRequest
这三个对象添加了事件。这可以让Servlet开发人员更好的控制上述3个对象生命周期。
ServletContext
事件类型 描述 监听器接口 生命周期 ServletContext刚创建并可用于服务它的第一个请求或即将关闭 javax.servlet.ServletContextListener 更改属性 ServletContext的属性已添加、已删除、已替换 javax.servlet.ServletContextAttributeListener 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 ServletRequest
事件类型 描述 监听器接口 生命周期 一个请求已经开始由Web组件处理 javax.servlet.ServletRequestListener 更改属性 已在servlet上添加、移除、替换属性 javax.servlet.ServletRequestAttributeListner 异步事件 超时、连接终止或完成异步操作处理 javax.servlet.AsyncListener
实例化时机:
容器必须在开始执行进入应用的第一个请求之前完成Web应用中所有监听器类的实例化。
11. Deployment Descriptor - web.xml
1. 关于顺序:
Servlet
初始化顺序通过
<load-on-startup>x</load-on-startup>
中的x
来指定初始化顺序,必须不小于0,越小越早加载。Filter
的初始化顺序:
按照
web.xml
中声明的顺序自上而下的初始化。过滤器链构造规则:
规范中6.2.4 节,待验证总结。
过滤顺序
如果匹配了多个
url-pattern
,按照自上而下的顺序。
Listener
调用顺序根据在
web.xml
中注册的顺序来被调用。而销毁事件触发的destroy
会被反方向的依次调用。
2. 关于初始化参数:
ServletContext
的初始化参数:设值:因为一个应用只有一个
ServletContext
,所以是直接声明在根<web-app>
下的:<context-param> <param-name>contextParam1</param-name> <param-value>11context11</param-value> </context-param>
取值/设值:只要获取到了
ServletContext
对象,就可以使用其getInitParameter(String name)
方法来取值,同时也可以使用setInitParameter(String name, String value);
来设值,所以很多地方都可以取值/设值。
Listener
的初始化参数:设值:监听器没有自己独立的初始化参数配置,想要使用的话可以借助将参数配置在
ServletContext
的初始化参数位置。取值/设值:如上。
Filter
的初始化参数:设值:只能在
<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>
取值:只有获取了某一个
Filter
的FilterConfig
对象之后,才能使用此对象的方法getInitParameter(String name)
方法来取值。另外的:当你自己写一个
Filter
的时候,除了实现Filter
接口之外,你还可以选择学习类似GenericServlet
的方式,额外的实现FilterConfig
接口并覆盖其getInitParameter(String name)
方法,从而可以在自己的Filter
的任何方法中都能调用取值方法而无须显示获取FilterConfig
对象。像下面这样:public class FirstFilter implements Filter, FilterConfig{ ... }
Servlet
的初始化参数:设值:只能在
<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>
取值:跟
Filter
类似,Servlet
有ServletConfig
对象,只要获取到它就可以使用其getInitParameter(String name)
方法来取值。另外的:通常我们不会直接实现
Servlet
接口而是使用继承GenericServlet
/HttpServlet
的方式,那么(如我在上面Filter
“另外的”部分所说)我们就可以利用它们的实现直接使用getInitParameter(String name)
方法来取值。
转载注明出处:http://blog.csdn.net/u010297957/article/details/51498018
以上是关于Servlet的历史与规范的主要内容,如果未能解决你的问题,请参考以下文章
Servlet—Cookie(显示用户上次访问时间显示商品浏览历史)
Web app ------ 从Servlet读取Json数据并显示,生成历史数据曲线图