背景
上世纪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的意思。
其实Servlet的工作原理基本类似上面的CGI,不过Servlet比CGI更好。
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规范等文档。
下载地址:
Servlet规范官方地址:JSR 340: Java Servlet 3.1 Specification(中文版网上有人翻译了,可以自己搜索找找)
可以自己下载浏览,终究版final是2013年5月28发布的Servlet3.1。
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
生命周期:
Servlet
的生命(周期)是由容器管理的,换句话说,Servlet
程序员不能用代码控制其生命。
加载和实例化:
时机取决于web.xml
的定义,如果有<load-on-startup>x</load-on-startup>
则在容器启动时,反之则在第1次针对这个Servlet的要求产生时。
初始化:
实例化后会立马进行初始化。也就是履行init
方法。
要求处理:
初始化后,Servlet
就能够接受要求了。
基本方式是履行Servlet
接口中的service
方法。
固然,API也提供了HttpServlet
抽象类,其中有doGet
、doPost
等特殊方法。
注意:任意的容器依照规范必须实现上述几种方法,所以你的代码写在这几个方法中都可以。
终止服务:
容器会在适合的时候烧毁某个Servlet
对象,这个策略取决于容器的开发者/商。
在容器关闭的时候Servlet
对象1定会被烧毁。
当1或2产生时,也就是Servlet
对象被烧毁时,destroy
方法会被调用。
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);
1个Web利用对应1个ServletContext
接口的实例。
1. 获得资源:
ServletContext
接口提供了直接访问Web利用中静态内容(意思是说你获得jsp
返回就是jsp源码
)层次结构的文件的方法。
getResource
getResourceAsStream
这两个方法需要的String参数必须是以'/'
开头的,这个'/'
代表相对:
ServletContextPath
的路径。
或WEB-INF/lib
中的jar
中的METE-INF/resources
路径。
代表容器的响应,没有特别需要注意的。
过滤器,是Java中1种代码重用技术,通过拦截要求改变HTTP要求的内容、响应、Header信息。
其它没甚么好说的,只需要注意的1点是:
匹配的Filter
极可能是多个而不是1个,所以Filter
是1个FilterChain
设计。
所有Filter
的doFilter
方法终究都需要调用chain.doFilter(request, response);
方法来触发调用链的下1个Filter
或如果是最后1个Filter
那末直接访问目标资源。
用于映照到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
终究会:
.jsp
文件变成java
文件(即1个Servlet
);compile
成.class
;default:org.apache.catalina.servlets.DefaultServlet
这个Servlet
终究会:
会话跟踪机制
HTTP协议是无状态的,但是记录状态,也就是说记录来自同1客户真个要求的需求是必须的。所以人们发明了会话跟踪机制。
Servlet规范定义了1个简单的HttpSession
接口,允许容器使用几种方法来实现会话跟踪,从而使得Web利用开发人员没必要来关心和写这块的代码。(容器只能帮我们实现单机会话跟踪,散布式利用多机状态下,我们需要自己写代码实现Session同步)。
几种方法
Cookies
是最经常使用的机制,且所有Servlet容器必须支持。容器有能力(但是需要程序员显式调用)向客户端发送1个cookie
,用来记录会话,标准名字必须是JSESSIONID
。
URL重写
比如:http://www.sss.com/aa/bb.html;jessionid=1234
要注意,使用的是分号';'
,这个叫路径参数,区分于查询参数。
如何使用Session跟踪机制
需要程序员手动调用
//a. 有session返回,没有新生成1个
request.getSession();
request.getSession(true);
//b. 有session返回,没有返回null
request.getSession(false);
如果使用的a方式,再加上使用cookie
机制的情况下,容器的第1次响应(Response)会向客户端写1个JSESSIONID
,客户端从第2次要求(Request)开始会带着JSESSIONID
,以下图:
1. WEB-INF
目录:
此目录是1个特殊目录,不能由容器直接提供给客户端访问。可以通过:
调用ServletContext
的getResource
和getResourceAsStream
来访问。
还可以通过RequestDispatcher
来调用从而公然这些内容。
2. WEB-INF
目录的内容:
/WEB-INF/web.xml
部署描写文件。
Servlet
和其它类的目录/WEB-INF/classes/
。
Java归档文件(jar)区域/WEB-INF/lib/*.jar
。
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利用中所有监听器类的实例化。
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)
方法来取值。