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程序对每一个要求产生新的进程去处理。步骤以下:Java
与此同时,Java语言也在迅速发展。必定的,Java要支持上述需求。
Java有两种方案来实现动态需求,它们都属于JavaEE技术的1部份。applet
这是纯客户端(阅读器)方案,applet就是阅读器中的Java插件,阅读器通过它就可以够解释履行WEB服务器发过来的Java代码,从而实现动态。但是,明显这类方案不好,既需要阅读器必须安装插件,又受限于阅读器,所以Java代码不能太多和太复杂。比如,如果安装了JRE,虽然IE阅读器会自动启用Java插件,但是你可以轻易制止。再比如Chrome还需要你手动去安装插件才行,普通用户连Java是甚么都不知道他怎样会去装呢?
IE以下图:Servlet
既然阅读器不方便履行Java代码,那自然还是服务端来履行了,所以Servlet出现了,Servlet就是server真个applet的意思。
2. Servlet的工作原理
其实Servlet的工作原理基本类似上面的CGI,不过Servlet比CGI更好。
Servlet容器找到对应的Servlet并履行这个Servlet;
Servlet容器将处理结果返回给WEB服务器;
3. Servlet的发展
Servlet诞生后,SUN公司很快发现了Servlet编程非常繁琐,这是由于:
所以,SUN鉴戒了Microsoft的ASP,正式提出JSP(Servlet1.1),已期望能代替Servlet。但是很快,SUN发现JSP也有问题:
所以,Servlet1.2出现了,这个版本的Servlet提倡了MVC思想:
基本上到这里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有两种意思:
广义上是:基于Java技术的Web组件,被容器托管,用于生成动态内容。
再详细点说,Servlet是JavaEE组件中的 -> Web组件的 -> 1种。
(其它两种是JavaServer Faces和JavaServer Page)狭义上说:是JavaEE API中的1个
interface
,javax.servlet.Servlet
;
Servlet 容器/引擎:
Servlet容器也能够叫引擎,Container/Engine,用于履行
Servlet
。容器本身(不依赖Web服务器)就提供了基于要求/响应发送模型的网络服务,解码基于MIME的要求,格式化基于MIME的响应。
所有容器必须实现HTTP协议的要求/响应模型。其它协议不强求,如HTTPS。
下面开始说1下规范的核心要点。
请注意:我不是要完全的论述Servlet规范,毕竟你可以直接看规范。这里我只是要记录我认为重要的点。
为了方便描写,先声明1些名词:
web.xml
= 部署描写符(Deployment Descriptor )- 容器 = Servlet Container/Engine
2. Servlet Interface
Servlet
生命周期:
Servlet
的生命(周期)是由容器管理的,换句话说,Servlet
程序员不能用代码控制其生命。
加载和实例化:
时机取决于web.xml
的定义,如果有<load-on-startup>x</load-on-startup>
则在容器启动时,反之则在第1次针对这个Servlet的要求产生时。初始化:
实例化后会立马进行初始化。也就是履行init
方法。要求处理:
初始化后,Servlet
就能够接受要求了。终止服务:
3. Request
1. 要求路径元素
Context Path
:
以'/'
开头,但不以'/'
结尾Servlet Path
:
以'/'
开头PathInfo
:
要末为null
要末以'/'
开头例如:
2. 要求编码
要求会以甚么编码情势送给服务器端呢?
HTTP协议没有强迫规定,所以实际上这是由阅读器自己决定的,决定后阅读器可以通过
entity-body
中的Content-Type
项告知服务器自己使用了甚么编码。但是!大部份情况下阅读器不会这么做的。比如说,Get要求是没有entity-body
的,自然也不会使用Content-Type
了。在服务端,我们Servlet规范 规定了如果要求没有指定编码的话,容器必须使用
IOS⑻859⑴
来解码。为了让开发人员知道要求给没给出编码,容器会在没给的情况下通过getCharacterEncoding
返回null
来告知我们。为了在我们明知道不是
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
设计。所有
Filter
的doFilter
方法终究都需要调用chain.doFilter(request,response);
方法来触发调用链的下1个Filter
或如果是最后1个Filter
那末直接访问目标资源。
7. 映照要求到Servlet的规则
用于映照到
Servlet
的路径是:客户端要求的URL
-ServletContext上下文路径
-路径参数
。例如当客户端要求的URL是:
http://www.google.com/testproject/action/servlet1?param1=asd
时,那末需要映照的路径是:/action/servlet1
。
重要!重要!重要!选择映照到的Servlet的规则是,依照以下的顺序查找,如果已选定1个就会匹配成功不会继续往下:
先精确匹配
<url-pattern>
,成功则选择。(精确匹配)递归遍历路径树,选择最长的路径匹配。(最长匹配)
如果URL最后1个部份包括扩大名,比如
/action/servlet1.jsp
,容器将选择专门声明了要处理此扩大名要求的Servlet
。(扩大名匹配)如果123都没有匹配,容器将提供1个后备方案,1般来讲是提供1个
"default" Servlet
。(低保匹配,优先级最低,提供1个最低保障)
映照规范
以
'/'
字符开始,以'/*'
字符结束的字符串:用于路径匹配。(这是1个准确严谨的定义而已)。以
'*.'
开始的字符串用于扩大名映照。空字符串
""
是特殊的URL,精确映照到利用的上下文根,即http://host:port/<context-root>/
,这类情况(""
)相当于<url-pattern>/<url-pattern>
。只包括
'/'
字符的字符串表示利用的"default" Servlet
。
隐式映照
容器可以为1些扩大名定义1些影射映照,比如来1个.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
终究会:default:
org.apache.catalina.servlets.DefaultServlet
这个Servlet
终究会:
8. Session
会话跟踪机制
HTTP协议是无状态的,但是记录状态,也就是说记录来自同1客户真个要求的需求是必须的。所以人们发明了会话跟踪机制。
Servlet规范定义了1个简单的
HttpSession
接口,允许容器使用几种方法来实现会话跟踪,从而使得Web利用开发人员没必要来关心和写这块的代码。(容器只能帮我们实现单机会话跟踪,散布式利用多机状态下,我们需要自己写代码实现Session同步)。几种方法
如何使用Session跟踪机制
9. Web Application
1. WEB-INF
目录:
此目录是1个特殊目录,不能由容器直接提供给客户端访问。可以通过:
2. WEB-INF
目录的内容:
10. Application Lifecycle Events - 利用生命周期事件
Servlet API为ServletContext
、HttpSession
、ServletRequest
这3个对象添加了事件。这可让Servlet开发人员更好的控制上述3个对象生命周期。
ServletContext
事件类型 描写 监听器接口 生命周期 ServletContext刚创建并可用于服务它的第1个要求或行将关闭 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
事件类型 描写 监听器接口 生命周期 1个要求已开始由Web组件处理 javax.servlet.ServletRequestListener 更改属性 已在servlet上添加、移除、替换属性 javax.servlet.ServletRequestAttributeListner 异步事件 超时、连接终止或完成异步操作处理 javax.servlet.AsyncListener
实例化时机:
容器必须在开始履行进入利用的第1个要求之前完成Web利用中所有监听器类的实例化。
11. Deployment Descriptor - web.xml
1. 关于顺序:
Servlet
加载顺序
<load-on-startup>x</load-on-startup>
中的x指定加载顺序,必须不小于0,越小越早加载。Filter
的加载顺序:
应当是没有要求,不会依照根据web.xml
中声明的顺序,但也不是随机的,以某种规则固定,这个不重要。过滤器链构造规则:
规范中6.2.4 节,待验证总结。
Listener
调用顺序
根据在web.xml
中注册的顺序来被调用。例外的,生命周期中的烧毁事件触发的destroy
会被反方向的顺次调用。
2. 关于初始化参数:
ServletContext
的初始化参数:设值:由于1个利用只有1个
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>
取值:只有获得了某1个
Filter
的FilterConfig
对象以后,才能使用此对象的方法getInitParameter(String name)
方法来取值。另外的:当你自己写1个
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)
方法来取值。