Servlet
Servlet
没有 main 方法,不能够独立的运行,它的运行需要容器的支持,Tomcat 是最常用的 JSP/Servlet 容器。
Servlet
运行在 Servlet 容器中,并由容器管理从创建到销毁的整个过程。
Servlet 的生命周期
(1) 加载和实例化
Servlet
容器装载和实例化一个 Servlet。创建出该 Servlet 类的一个实例。
(2)
初始化
在 Servlet 实例化完成之后,容器负责调用该Servlet 实例的 init() 方法,在处理用户请求之前,来做一些额外的初始化工作。
(3)
处理请求
当 Servlet 容器接收到一个 Servlet 请求时,便运行与之对应的 Servlet 实例的 service() 方法,service() 方法再派遣运行与请求相对应的
doXX(doGet
,doPost) 方法来处理用户请求。
(4)
销毁
当 Servlet 容器决定将一个 Servlet 从服务器中移除时 ( 如 Servlet 文件被更新 ),便调用该 Servlet 实例的 destroy() 方法,在销毁该 Servlet 实例之前,
来做一些其他的工作。
其中,(1)(2)(4) 在 Servlet 的整个生命周期中只会被执行一次。
Servlet 的工作原理
结合右边给出的流程图:
当客户端浏览器向服务器请求一个 Servlet 时,服务器收到该请求后,首先到容器中检索与请求
匹配的 Servlet 实例是否已经存在。若不存在,则Servlet 容器负责加载并实例化出该类 Servlet的一个实例对象,接着容器框架负责调用该实例的 init() 方法来对实例做一些初始化工作,然后
Servlet
容器运行该实例的 service() 方法。
若 Servlet 实例已经存在,则容器框架直接调用该实例的service() 方法。
service()
方法在运行时,自动派遣运行与用户请求相对应的 doXX() 方法来响应用户发起的请求。
通常,每个 Servlet 类在容器中只存在一个实例,每当请求到来时,则分配一条线程来处理该请求。
JSP 工作原理
结合右边给出的流程图:
当客户端浏览器向服务器请求一个 JSP 页面时,服务器收到该请求后,首先检查所请求的这个
JSP
文件内容 ( 代码 ) 是否已经被更新,或者是否是 JSP 文件创建后的第一次被访问,如果是,
那么,这个 JSP 文件就会在服务器端的 JSP 引擎作用下转化为一个 Servlet 类的 Java 源代码
文件。紧接着,这个 Servlet 类会在 Java 编译器的作用下被编译成一个字节码文件,并装载
到 jvm 解释执行。剩下的就等同于 Servlet 的处理过程了。
如果被请求的 JSP 文件内容 ( 代码 ) 没有被修改,那么它的处理过程也等同于一个 Servlet 的
处理过程。即直接由服务器检索出与之对应的 Servlet 实例来处理。
需要注意的是,JSP 文件不是在服务器启动的时候转换成Servlet 类的。而是在被客户端访问
的时候才可能发生转换的 ( 如 JSP 文件内容没有被更新等,就不再发生 Servlet 转换 )。
就 Tomcat 而言,打开目录%Tomcat%/work/%您的工程文件目录%,然后会看到里面有 3个子目录:org/apache/jsp,若没有这 3 个目录,说明项目的 JSP 文件还没有被访问过,
打开进到 jsp 目录下,会看到一些 *_jsp.java 和 *_jsp.class 文件,这就是 JSP 文件被转换成
Servlet
类的源文件和字节码文件了。
有兴趣的话,可以使用浏览器访问服务器中的 JSP,然后观察JSP 转换 Servlet 以及编译的时机。
Servlet 与 JSP
JSP
本质是一个 Servlet,它的运行也需要容器的支持。
在 JSP 和 Servlet 文件中都可以编写 Java 和 HTML 代码,不同的是,
Servlet
虽然也可以动态的生成页面内容,但更加偏向于逻辑的控制。
JSP
最终被转换成 Servlet 在 jvm 中解释执行,在 JSP 中虽然也可以编写 Java 代码,但它更加偏向于页面视图的展现。
在 MVC 架构模式中,就 JSP 和 Servlet 而言,C 通常由Servlet 充当,V 通常由 JSP 来充当。
. 了解 Servlet 和Filter
好了,我已经把环境搭起来了,接下来该干嘛呢?
前面的步骤为的是搭建一个测试的环境,然后让你了解一个最基本的 Java Web 项目的结构。
一个最基本的 Java Web 项目所需的 jar 包只需要一个 servlet-api.jar ,这个 jar 包中的类大部分都是接口,还有一些工具类,共有 2 个包,分别是 javax.servlet 和 javax.servlet.http。我把这个jar包放到了 webapp 目录外的一个独立 packages 文件夹里,这是因为所有的 Servlet 容器都带有这个包,你无需再放到Web项目里,我们放到这里只不过是编译的需要,运行是不需要的。如果你硬是把 servlet-api.jar 放到 webapp/WEB-INF/lib 目录下,那么 Tomcat 启动时还会报一个警告信息。
Java Web 项目还需要一个非常重要的配置文件 web.xml ,在这个项目中已经被我最小化了,只保留有用的信息:
hello_world demo.HelloServlet 1 hello_world /hello
Servlet 规范里还有另外一个非常重要而且非常有用的接口那就是 Filter 过滤器。 每个 servlet 都必须在 web.xml 中定义并进行 URL 映射配置,早期 Java 开发Web 在没有框架满天飞的时候,这个文件会定义了大量的 servlet,或者有人为了省事干脆来一个 /servlet/* 来通过类名直接调用 Servlet。 |
下面是一个最简单的 Filter 类以及相应的定义方法:
01 | package demo; | |
02 |
|
|
03 | import java.io.IOException; | |
04 |
|
|
05 | import javax.servlet.Filter; |
|
06 | import javax.servlet.FilterChain; |
07 | import javax.servlet.FilterConfig; |
|
08 | import javax.servlet.ServletException; |
09 | import javax.servlet.ServletRequest; |
|
10 | import javax.servlet.ServletResponse; |
11 | import javax.servlet.http.HttpServletRequest; | |
12 |
|
|
13 | public class HelloFilter implements Filter { | |
14 |
|
|
15 |
|
|
16 | public void init(FilterConfig arg0) throws ServletException { |
17 | System.out.println("Filter 初始化"); | |
18 | } |
|
19 |
|
|
20 |
|
21 | public void doFilter(ServletRequest req, ServletResponse res, |
22 | FilterChain chain) throws IOException, ServletException { |
23 | HttpServletRequest request = (HttpServletRequest)req; |
24 | System.out.println("拦截 URI="+request.getRequestURI()); |
25 | chain.doFilter(req, res); | |
26 | } |
|
27 |
|
|
28 |
|
29 | public void destroy() { |
|
30 | System.out.println("Filter 结束"); |
31 | } | |
32 | } |
|
在web.xml 中的配置必须放在 Servlet 的前面:
01 | <?xml version="1.0" encoding="UTF-8"?> |
|
02 | <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" |
03 | ""> | ||
04 | <web-app> |
| |
|
|
|
|
05 |
|
|
06 | <filter> |
07 | <filter-name>helloFilter</filter-name> |
|
08 | <filter-class>demo.HelloFilter</filter-class> |
09 | </filter> | |
10 |
|
|
11 | <filter-mapping> |
|
12 | <filter-name>helloFilter</filter-name> |
13 | <url-pattern>/*</url-pattern> | |
14 | </filter-mapping> |
|
15 |
|
|
16 | <servlet> |
17 | <servlet-name>hello_world</servlet-name> |
|
18 | <servlet-class>demo.HelloServlet</servlet-class> |
19 | <load-on-startup>1</load-on-startup> | |
20 | </servlet> |
|
21 |
|
|
22 | <servlet-mapping> |
23 | <servlet-name>hello_world</servlet-name> | |
24 | <url-pattern>/hello</url-pattern> |
|
25 | </servlet-mapping> | |
26 |
|
|
27 | </web-app> |
访问 时看看 Tomcat 控制台有何输出信息。
4. Servlet 和 HTTP 的对应关系
Servlet 是 J2EE 最重要的一部分,有了 Servlet 你就是 J2EE 了,J2EE 的其他方面的内容择需采用。而 Servlet 规范你需要掌握的就是 servlet 和 filter 这两项技术。绝大多数框架不是基于 servlet 就是基于 filter,如果它要在 Servlet 容器上运行,就永远也脱离不开这个模型。
为什么 Servlet 规范会有两个包,javax.servlet 和 javax.servlet.http ,早先设计该规范的人认为 Servlet 是一种服务模型,不一定是依赖某种网络协议之上,因此就抽象出了一个 javax.servlet ,同时在提供一个基于 HTTP 协议上的接口扩展。但是从实际运行这么多年来看,似乎没有发现有在其他协议上实现的 Servlet 技术。
javax.servlet 和javax.servlet.http 这两个包总共加起来也不过是三十四个接口和类。你需要通过 熟知每个类和接口的具体意思。特别是下面几个接口必须熟知每个方法的意思和用途:
- HttpServlet
- ServetConfig
- ServletContext
- Filter
- FilterConfig
- FilterChain
- RequestDispatcher
- HttpServletRequest
- HttpServletResponse
- HttpSession
- 一些 Listenser 类
再次强调 HttpServletRequest 和 HttpServletResponse 这两个接口更应该是烂熟于心。
如果你从字面上无法理解某个方法的意思,你可以在前面那个项目的基础上做实验看看其输出,再不行你可以到讨论区提问,这样的提问非常明确,很多人都可以帮到你。
为什么我这么强调 HttpServletRequest 和 HttpServletResponse 这两个接口,因为 Web 开发是离不开 HTTP 协议的,而 Servlet 规范其实就是对 HTTP 协议做面向对象的封装,HTTP协议中的请求和响应就是对应了 HttpServletRequest 和 HttpServletResponse 这两个接口。
你可以通过 HttpServletRequest 来获取所有请求相关的信息,包括 URI、Cookie、Header、请求参数等等,别无它路。因此当你使用某个框架时,你想获取HTTP请求的相关信息,只要拿到 HttpServletRequest 实例即可。
而HttpServletResponse接口是用来生产 HTTP 回应,包含 Cookie、Header 以及回应的内容等等。
5. 再谈谈 Session
HTTP 协议里是没有关于 Session 会话的定义,Session 是各种编程语言根据 HTTP 协议的无状态这种特点而产生的。其实现无非就是服务器端的一个哈希表,哈希表的Key就是传递给浏览器的名为 jsessionid 的 Cookie 值。
当需要将某个值保存到 session 时,容器会执行如下几步:
a.
获取 jsessionid 值,没有的话就生成一个,也就是 request.getSession() 这个方法
b.
拿到的 HttpSession 对象实例就相当于一个哈希表,你可以往哈希表里存放数据
(setAttribute)
c.
你也可以通过 getAttribute 来获取某个值
而这个名为 jsessionid 的 Cookie 在浏览器关闭时会自动删除。把 Cookie 的 MaxAge 值设为-1 就能达到浏览器关闭自动删除的效果。
6. 关于 JSP
首先我已经不用 JSP 很多年了,现在一直是使用 模板引擎。
任何一个 JSP 页面在执行的时候都会编译成一个 Servlet 类文件,如果是 Tomcat 的话,这些生成的 java 文件会放置在 {TOMCAT}/work 目录下对应项目的子目录中,例如 Tomcat 生成的类文件如下:
01 | package org.apache.jsp; | |
02 |
|
|
03 | import javax.servlet.*; |
|
04 | import javax.servlet.http.*; |
05 | import javax.servlet.jsp.*; | |
06 | import java.util.*; |
|
07 |
|
|
08 | public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase |
09 | implements org.apache.jasper.runtime.JspSourceDependent { | |
10 |
|
|
11 | private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory(); | |
12 |
|
|
13 | private static java.util.List<String> _jspx_dependants; | |
14 |
|
|
15 | private javax.el.ExpressionFactory _el_expressionfactory; |
|
16 | private org.apache.tomcat.InstanceManager _jsp_instancemanager; |
17 |
|
|
18 | public java.util.List<String> getDependants() { |
19 | return _jspx_dependants; | |
20 | } |
|
21 |
|
|
22 | public void _jspInit() { |
23 | _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); | |
24 | _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); | |
|
|
|
25 | } | |
26 |
|
|
27 | public void _jspDestroy() { | |
28 | } |
|
29 |
|
|
30 | public void _jspService(final HttpServletRequest request, final HttpServletResponse response) |
31 | throws java.io.IOException, ServletException { | |
32 |
|
|
33 | final PageContext pageContext; | |
34 | HttpSession session = null; |
|
35 | final ServletContext application; | |
36 | final ServletConfig config; |
|
37 | JspWriter out = null; |
|
38 | final Object page = this; |
39 | JspWriter _jspx_out = null; |
|
40 | PageContext _jspx_page_context = null; |
41 |
|
42 |
|
43 | try { |
|
44 | response.setContentType("text/html;charset=utf-8"); |
45 | pageContext = _jspxFactory.getPageContext(this, request, response, | |
46 | null, true, 8192, true); |
|
47 | _jspx_page_context = pageContext; |
|
48 | application = pageContext.getServletContext(); |
49 | config = pageContext.getServletConfig(); | |
50 | session = pageContext.getSession(); |
|
51 | out = pageContext.getOut(); | |
52 | _jspx_out = out; |
|
53 |
|
|
54 | out.write("\r\n"); |
55 | out.write("<html>\r\n"); |
|
56 | out.write(" <title>Test</title>\r\n"); |
57 | out.write(" <style>\r\n"); |
|
58 | out.write(" </style> \r\n"); |
59 | out.write(" <body>\r\n"); |
|
60 | out.write("<h1>Test Demo (oschina)</h1>\r\n"); |
61 | out.write("<table cellspacing=\"1\" cellpadding=\"5\">\r\n"); | |
62 |
|
|
63 | Enumeration Names=request.getHeaderNames(); | |
64 | while(Names.hasMoreElements()) |
|
65 | {String name=(String)Names.nextElement(); | |
66 | String value=request.getHeader(name); |
|
67 |
|
|
68 | out.write("\r\n"); |
69 | out.write(" <tr>\r\n"); | |
70 | out.write(" <td>"); |
|
71 | out.print(name); |
|
72 | out.write("</td>\r\n"); |
73 | out.write(" <td>"); | |
74 | out.print(value); |
|
75 | out.write("</td>\r\n"); | |
76 | out.write(" \r\n"); |
|
77 | out.write(" </tr>\r\n"); | |
78 | out.write(" "); |
|
79 |
|
|
80 | } |
81 |
|
|
82 | out.write("\r\n"); |
83 | out.write("</table>\r\n"); |
|
84 | out.write(" </body>\r\n"); |
85 | out.write("</html>"); | |
86 | } catch (Throwable t) { |
|
87 | if (!(t instanceof SkipPageException)){ | |
88 | out = _jspx_out; |
|
89 | if (out != null && out.getBufferSize() != 0) |
|
90 | try { out.clearBuffer(); } catch (java.io.IOException e) {} |
91 | if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); | |
92 | } |
|
93 | } finally { |
|
94 | _jspxFactory.releasePageContext(_jspx_page_context); |
95 | } | |
96 | } |
|
97 | } |
在servlet 中有一个包 javax.servlet.jsp 是跟 JSP 相关的一些接口规范定义。JSP 比 Servlet 方便的地方在于可直接修改立即生效,不像 Servlet 修改后必须重启容器才能生效。
因此 JSP 适合用来做视图,而 Servlet 则适合做控制层。
7. 总结
罗哩罗嗦一大堆,归纳一下就是下面几点:
- 熟知 Servlet 规范之前,请不要学习任何框架
- 使用最简单的工具,不要任何向导和可视化
- 熟知 HTTP 协议
等你真的掌握了 Servlet 规范再去看框架,便会觉得一些都小菜。总之一点:不要被框架牵着鼻子走,框架是你的工具,它应该听你的!
红薯乱弹,随时准备挨喷。
Java servlets 是一项被普遍接受的技术,用于构建基于web 应用程序的动态内容;Servlet 3.0 规范早期草案版本的发行让该技术在特性和应用程序接口(Application Program Interface,API)方面得到了极大增强。Java Specification Request(JSR)已经以 JSR 315 的形式得到了批准,并计划成为 Java Enterprise Edition 6(JSR 316)或更高版本的组成部分。与之前仅仅是维护发行版(maintenance releases)的一些版本规范不同,Servlet 3.0 规范随带了许多 web 开发新时代所需的最令人兴奋的特性。在本文中,我们将研究新版 Java servlets 中引入的主要特性。值得注意的是,本规范仍处于草案版本阶段,因此在本文中所讨论的技术细节可能会发生变化。
新规范主要交付了以下新特性:
开发的简易性
可插拔性和可扩展性
异步支持
安全性增强
其他杂项变化
很明显,与其他技术相比,servlets 在 Java Enterprise Edition 家族中有着更广泛的应用。Servlets 保留了其简洁性和能够处理 HTTP 请求并向 web 客户机传回响应的优点。Servlets 可以用于实现简单和小型应用程序的业务逻辑。在 web 框架中,servlets 作为所有传入请求的入口点(即 controller servlet);因此,所有流行框架都是在原始的servlets 上建立的。Servlet 3.0 中的新增特性旨简化 servlet 应用程序的开发,并让 servlet 开发人员和框架开发人员从中受益。在以下章节中,我们将详细介绍每个新增特性,并讨论如何使用它们来开发更优秀的应用程序。
开发的简易性
开发的简易性是任何技术成功的关键因素。Servlet 3.0 API 通过使用 JSR 175 注释 集中解决开发简易性问题,允许开发人员采用声明式的编程方式。这意味着您可以通过使用像 或者 @ServletFilter 这样的适当注释对类进行注释来快速开发一个 servlet 或者过滤器类。注释不仅使 servlet、过滤器和侦听器类的编码更容易,而且,即使应用程序存档可能有 servlet、过滤器或者上下文侦听器类也可以选择用于 web 应用程序的开发部署描述符。Web 容器负责处理各种注释,其位置在 WEB-INF/classes 目录下的各个类中、WEB-INF/lib 目录下的 .jar 文件中、或者应用程序类路径中任何可以找到的类中。
注释与部署描述符
值得注意的是,部署描述符优先于注释。换句话说,部署描述符覆盖通过注释机制所规定的配置信息。Web 部署描述符的 3.0 版本在 web-app 元素上包含一种名为 metadata-complete 的新属性。该属性定义了 web 描述符是否完整,或者 web 应用程序的类文件是否针对指定部署信息的注释而进行检查。如果该属性被设置为 true,则部署工具必须忽略类文件中所存在的任何 servlet 注释,并只使用描述符中所提及的配置细节。否则,如果没有指定该值或者该值被设置为 false,容器必须针对注释而扫描应用程序的所有类文件。这个属性提供了在应用程序启动阶段启用或者禁用注释扫描以及对注释的处理。
在 Servlet 3.0 中所引入的所有注释都可以在javax.servlet.http.annotation 和javax.servlet.http.annotation.jaxrs 软件包中找到。以下章节阐述Servlet 3.0 中注释的完整集合:
@Servlet:javax.servlet.http.annotation.Servlet 是一个类级别的注释,确认经过注释的类为一个 servlet 并保存关于所声明的 servlet 的元数据。urlMappings 属性是指定 URL 模式(调用该 servlet)的 @Servlet 的强制属性。当接收到了一个请求时,容器将请求中的 URL 与 servlet 的 urlMappings 进行匹配,且如果 URL 模式匹配,则调用相应的 servlet 以响应该项请求。该注释的所有其他属性都是可选的,并带有合理的默认值。Servlet 类中必须有一种使用像 GET、PUT、POST、HEAD 或者 DELETE 这样的 HttpMethod 注释进行注释的方法。这些方法应将 HttpServletRequest 和 HttpServletResponse 作为方法参数。与以前的版本相反,servlets 3.0 的版本可以作为简单传统 Java 对象(Plain Old Java Objects,POJOs)而实现;也就是 servlets 不必再扩展像HTTPServlet 或者 GenericServlet 这样的基础 servlet 实现类。
为了进行比较,在此给出了使用传统 Servlet 2.5 API 编写的 Java servlet 代码片段,如下所示。在 Servlet 2.5 中,只要在部署描述符中配置了 servlet 的详细信息,web 容器就将初始化 servlet。
public class MyServlet extends HttpServlet { public void doGet (HttpServletRequest req, HttpServletResponse res) { .... } } |
</web-app>
Deployment descriptor (web.xml)
<web-app> <servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>samples.MyServlet</servlet-class> </servlet>
<servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/MyApp</url-pattern> </servlet-mapping> ...
</web-app> |
这里给出的是使用 Servlet 3.0 API 编写的较为简化的版本。当 MyServlet 使用 @Servlet 注释而被注释为一个 servlet 时,则在 web 容器的启动期间对其初始化。注意,在这种情况下部署描述符是可选的。
@Servlet(urlMappings={"/MyApp"}) public class MyServlet { @GET public void handleGet(HttpServletRequest req, HttpServletResponse res) { .... } } Deployment descriptor (web.xml)
optional |
@ServletFilter 和 @FilterMapping:您可以使用 javax.servlet.http.annotation.ServletFilter 注释来注释过滤器类,从而轻松创建一个 servlet 过滤器。该注释封装正被声明的过滤器的有关元数据。在过滤器类上具有@FilterMapping 注释也是强制性的。@FilterMapping 注释定义用于过滤器的 URL 模式。@ServletFilter 的所有其他属性都是可选的,并带有合理的默认值。V3.0 过滤器类现在类似 POJO 类,并且没有用于这些类所需的 Filter 接口或者非参数公用构造器。以下给出了使用 Servlet v2.5 API 的过滤器类的代码片段:
public class MyFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { ...... } }
Deployment descriptor (web.xml)
<web-app> <filter> <filter-name>My Filter</filter-name> <filter-class>samples.MyFilter</filter-class> </filter>
<filter-mapping> <filter-name>My Filter</filter-name> <url-pattern>/foo</url-pattern> </filter-mapping> ... </web-app> |
使用 Servlet 3.0 编写的一个示例过滤器类如下所示。因为该类使用 ServletFilter 注释,所以容器将 MyFilter 标记为一个过滤器类。MyFilter 截取所有收到的请求,其中该请求的 URL 匹配模式 /foo。Servlet 3.0 为过滤器配置提供了可选的部署描述符。
@ServletFilter @FilterMapping("/foo") public class MyFilter { public void doFilter(HttpServletRequest req, HttpServletResponse res) { ..... } }
Deployment descriptor (web.xml)
optional |
@InitParam:该注释可以用来定义必须传递给 servlet 或者过滤器类的任意初始化参数。它是 @Servlet 和 @ServletFilter 注释的一个属性。以下代码示例解释了如何将具有 english 值、称作 lang 的初始化参数传递给一个 servlet 类。
@Servlet(urlMappings={"/MyApp"}, initParams ={@InitParam(name="lang", value="english")}) public class MyServlet { @GET public void handleGet(HttpServletRequest req, HttpServletResponse res) { .... } } |
@ServletContextListener:javax.servlet.http.annotation.ServletContextListener 注释将该类声明为一个 servlet 上下文侦听器。当 web 容器创建或者销毁 ServletContext 时,该上下文侦听器接收注释。上下文侦听器是一个 POJO 类,且不必实现 ServletContextListener 接口。使用 Servlet 2.5 API 编写的侦听器类如下所示。当且仅当您在部署描述符中配置了该侦听器类,容器才识别它。
public class MyListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) {
} ..... }
Deployment Descriptor (web.xml)
<web-app> <listener> <listener-class>samples.MyListener</listener-class> </listener> .... </web-app> |
</web-app>
使用 Servlet 3.0 API 编写的一个得到极大简化的侦听器类,如下所示。
@ServletContextListener
public class MyListener {
public void contextInitialized (ServletContextEvent sce) {
}
.....
}
Deployment Descriptor (web.xml)
optional
如今,像 Struts、JSF 和 Springweb 这样的 web 框架已经被普遍接受,并已经确立成为构建 web 应用程序的技术。将这些框架集成为一个 web 应用程序并不是那么容易,因为这一过程涉及将不同的片段同化在一起,之后编辑一个描述如何将所有这些片段配合在一起的单独描述符文件。绝大多数框架强制您 在应用程序的部署描述符文件中配置像 servlet 类(通常为 控制器 Servlet)、过滤器类或者侦听器类这样的框架细节。这种必备配置的主要原因是当今的 web 应用程序只支持一个单独的整体部署描述符,其中定义了所有的部署信息。当应用程序的大小增加时,外部框架的依赖关系可能也增加,从而生成复杂的部署描述符 文件。正如您可能知道的,复杂描述的维护始终存在争议。
为了解决这些问题,Servlet 3.0 最为显着的概念之一是 web 片段 或者 模块 web.xml 的思想。Web 片段是将 web 应用程序逻辑分区为 servlet、servlet-mapping、servlet-filter、filter-mapping、servlet-listener 之类的元素及其子元素。框架开发人员可以利用该功能以定义存在于框架内自己的 web 片段,开发人员可以在不修改现有的部署描述符的情况下仅仅通过将库文件包含到应用程序的类路径中来插入越来越多的框架。简言之,该功能旨在当用框架或者库 进行工作时进行零配置。
对该部署描述符已经进行了更改以包含保存 web 片段细节的<web-fragment>这一新元素。如果该片段被打包为一个 .jar 文件且以部署描述符的形式具备元数据信息,则 web.xml 文件必须包含在 .jar 文件的 META-INF 目录下。在部署时,器扫描应用程序的类路径,查找所有 web 片段并加以处理。前面讨论过的 metadata-complete 标志在应用程序启动期间控制 web 片段的扫描。以下显示了一个示例 web 片段:
<web-fragment>
<web-fragment> <servlet> <servlet-name>myservlet</servlet-name> <servlet-class>samples.MyServlet</servlet-class> </servlet> <listener> <listener-class>samples.MyListener</listener-class> </listener> </web-fragment> |
<servlet><servlet-name></listener></web-fragment>
为了提供增强的可插拔性,Servlet 3.0 借助添加到ServletContext 的 API 为增加servlets 和过滤器类的编程提供了非常需要的支持。这些新的 API 使您能通过编程来声明 servlets、过滤器类及其 URL 映射。这些类在应用程序启动或者运行期间进行初始化。最重要的是,您只能通过 ServletContext 的 contextInitialized 方调用这些 API。有关这些 API 的更多信息,请参阅 Servlet 3.0 API 文档。通过编程添加 servlet 和过滤器类的代码示例如下所示:
@ServletContextListener public class MyListener { public void contextInitialized (ServletContextEvent sce) { ServletContext sc = sce.getServletContext();
//Declare servlet and servlet mapping sc.addServlet("myServlet", "Sample servlet", "samples.MyServlet", null, -1); sc.addServletMapping("myServlet", new String[] {"/urlpattern/*"});
//Declare filter and filter mapping sc.addFilter("myFilter", "Sample Filter", " samples.MyFilter", null); sc.addFilterMapping("myFilter", new String[] {"/urlpattern/*"}, "myServlet", DispatcherType.REQUEST, false); } } |
异步支持
大家应该都遇到过 servlets 运行缓慢的问题,特别是servlets 必须等待来自 web 服务、JDBC 连接、JMS 消息等的响应。在当前情况下,servlet 在生成响应前需要等待进程完成,这会导致低效率阻塞操作消耗容器的线程或者其他有限的资源。在采用 JDBC 连接时的另一个不利影响是,数据库可能存在许多等待访问的阻塞线程。这种情况最终将导致整个 web 容器的线程不足和服务 质量下降。
为了克服上述缺点,Servlet 3.0 添加了对挂起和恢复请求处理的支持,使 servlet 以异步、非阻塞的方式响应请求(这就是编程的 Comet 样式)。当一项请求被挂起时,处理请求的线程将在不生成任何响应的情况下返回给容器并准备执行其他任务。处理请求的 resume 方法恢复请求处理。只要当所请求的资源可用时,处理事件的线程才恢复被挂起的请并进行处理以生成响应。以下列举的是异步 servlets 的一些功能:
即使数据到达缓慢(非阻塞输入),也能在没有阻塞事件的情况下从客户机接收数据。
即使客户机或者网络运行缓慢(非阻塞输出),也能在不发生阻塞的情况下向客户机发送数据。
能处理被延迟的请求。如果在响应请求前必须获得远程/缓慢的资源,或者如果需要抑制访问特定的资源以防止过多的同步访问,对被延迟事件的处理是 非常有用的。
能处理被延迟的响应关闭;也就是响应将保持打开以允许在发生异步事件时发送其他数据。
能通知阻塞或者非阻塞事件。
将新的 API 添加到 ServletRequest 和 ServletResponse,用于挂起、恢复和查询请求的状况、启用禁用和查询响应的状况。开发人员可以分别通过 requestSuspended(), requestResumed() 和requestCompleted() 方法使用请求的 resume、suspend 和 complete 方法通知事件。有关这些方法的详细信息请参阅 Servlet 3.0 API。涉及使用异步 servlets 从远程 web 服务获取数据的事件顺序在图 1 中进行了图解说明。
图 1. 使用异步 servlet 处理请求
安全性
在 3.0 规范的早期草案中没有考虑安全性问题,因为专家团仍未就此问题达成一致。不过,方案建议提供通过编程实现登录和注销功能。 HTTPServletRequest 中添加的新 API 可以启用这项功能。HTTPServletRequest 的 login 方法使应用程序或者框架强制进行以容器为中介的验证。HTTPServletRequest 和 HTTPSession 的 logout 方法允许应用程序重置请求的验证状态。
其他杂项变化
下面列举了一些为简化数据检索和简化应用程序开发而进行的功能增强。
HttpOnly Cookie:Servlet 3.0 规范允许将 cookie 标记为 HttpOnly cookie。由于向客户端脚本代码公开了 HttpOnly cookie,因而防止了某些种类的跨站点脚本攻击。 大多数现代浏览器支持该项功能。以下列举了添加到 Cookie 类中以支持 HTTPOnly cookie 的方法:
void setHttpOnly(boolean isHttpOnly) boolean isHttpOnly() |
API 的变化: 将以下新增方法添加到 ServletRequest 中,以便轻松获得与请求对象相关的 ServletResponse 和 ServletContext 实例。
ServletResponse getServletResponse() ServletContext getServletContext() |
路线图
3.0 规范的早期草案于 2008 年 6 月完成了审查。由于 Servlet 3.0 的目标是用于 Java EE 6 平台或者更高版本,因此该规范推荐的最终发行版也与 Java EE 6 的发行版匹配。但是,规范的早期测试版本将通过 GlassFish 来提供,后者是 Java EE 的参考实现。预计 Servlet 3.0 的参考实现将被整合到 GlassFish 的版本 3 中。
JSR 草案在审查过程中确定了一些变化,正在编制的新方案将处理来自社区的建议和反馈意见。新方案中的主要变化如下:
@Servlet 被重新命名为 @WebServlet,而 @ServletContextListener 被重新命名为@WebServletContextListener.
新方案建议 @WebServlet 必须扩展 HTTPServlet 类。因此,如前所述,servlet 类可以不是 POJO 类。
类似地,@ServletFilter 类和@WebServletContextListener 类需要分别实现 Filter 和 ServletContextListener 接口。
为启用和禁用 Servlet 提供选项。
要了解更多详细信息,请参见在本文 参考资料 章节中提供的 更新方案。还需注意,这些新增特性会因本方案的最终批准而发生变更。
结束语
在本文中,我们讨论了在 Servlet 3.0 的早期草案规范中提出的激动人心的特性。该规范已经开始在社区引起强烈反响。通过注释机制而实现的开发简易性、零配置使用框架和库、异步 servlets、可插拔性、安全性以及更多新增功能都是 Java Servlets 期待已久的特性。这说明,一些已经阅读了该规范的人表达了对注释过度使用、在扫描 web 片段时的安全威胁等问题的关注。但是,毫无疑问,Java Servlet 3.0 将使 servlet 开发人员的工作更轻松,并为开发下一代web 应用程序提供了更多的支持。
Java Servlet
(重定向自)
Servlet(Server Applet),全称Java Servlet,未有中文译文。是用编写的端。其主要功能在于交互式地浏览和修改数据,生成动态内容。狭义的Servlet是指Java语言实现的一个,广义的Servlet是指任何实现了这个Servlet接口的,一般情况下,人们将Servlet理解为后者。
Servlet运行于支持Java的中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于的。
最早支持Servlet标准的是JavaSoft的Java Web Server。此后,一些其它的基于Java的Web服务器开始支持标准的Servlet。
目录
工作模式
- 客户端发送请求至服务器
- 服务器启动并调用Servlet,Servlet根据客户端请求生成响应内容并将其传给服务器
- 服务器将响应返回客户端
通用Servlet
一般的,通用Servlet由javax.servlet.GenericServlet实现Servlet接口。程序设计人员可以通过使用或这个类来实现通用Servlet应用。
HttpServlet
javax.servlet.http.HttpServlet实现了专门用于响应请求的Servlet,提供了响应对应HTTP标准请求的doGet()、doPost()等方法。
生命周期
当servlet被部署在应用服务器中(应用服务器中用于管理Java组件的部分被抽象成为)以后,由容器控制servlet的生命周期。除非特殊指定,否则在容器启动的时候,servlet是不会被的,servlet只会在第一次请求的时候被和。servlet一旦被加载,一般不会从容器中删除,直至应用服务器关闭或重新启动。但当容器做内存回收动作时,servlet有可能被删除。也正是因为这个原因,第一次访问servlet所用的时间要大大多于以后访问所用的时间。
注:servlet在服务器中的运行:1.加载 ->2.初始化 - > 3.调用 - 4.销毁
生命周期:servlet的生命周期在【2.初始化】后开始其生命周期,在【4.销毁】后结束其生命周期
与JSP的关系
Java服务器页面()是HttpServlet的扩展。由于HttpServlet大多是用来响应HTTP请求,并返回Web页面(例如、), 所以不可避免地,在编写servlet时会涉及大量的HTML内容,这给servlet的书写效率和可读性带来很大障碍,JSP便是在这个基础上产生的。 其功能是使用HTML的书写格式,在适当的地方加入Java代码片断,将程序员从复杂的HTML中解放出来,更专注于servlet本身的内容。
JSP在首次被访问的时候被应用服务器转换为servlet,在以后的运行中,容器直接调用这个servlet,而不再访问JSP页面。JSP的实质仍然是servlet。