【Jsp精品源码栏目提醒】:网学会员为广大网友收集整理了,Spring核心技术与最佳实践 - 软件工程,希望对大家有所帮助!
Java 技术大系 Spring 2.0 核心技术与最佳实践 JavaEE 为 Web 开发提供了强大的支持。
为了实现拥有良好结构的、可扩展的 Web应用程序,各种 Web 框架层出不穷。
Spring 框架除了作为优秀的 IoC 容器之外,其本身也提供了一个完整的 Web MVC 模块。
本章将详细介绍 JavaEE Web 开发的基础知识,以及如何使用 Spring MVC 框架开发出灵活的、可扩展的 Web 应用程序。
本章还将介绍如何集成现有的一些流行的 MVC 框架,例如,Struts 和 WebWork,并比较它们和 Spring MVC 框架的优劣。
7.1 JavaEE Web 基础7.1.1 HTTP 协议简介 HTTP(HyperText Transfer Protocol,超文本传输协议)协议是 Web 应用所使用的最主要的协议。
以浏览器为界面的 Web 应用程序均是以 HTTP 协议为基础的请求相应模式。
浏览器作为客户端向服务器发送一个请求,服务器收到请求后,将响应返回给客户端。
图 7-1 显示了浏览器访问 http://www.sina.com.cn/的请求和响应。
http://www.sina.com.cn/ 图 7-1 HTTP 是一个无状态协议,浏览器和服务器的交互包括以下步骤。
① 浏览器向服务器请求建立 TCP 连接。
② 连接建立后,浏览器发送 HTTP 请求给服务器。
③ 服务器将响应内容发送给浏览器。
④ 双方关闭 TCP 连接。
如果服务器支持 HTTP 1.1 版本,则第 ②、③ 步可以多次执行,以便减少 TCP 连接196 使用 Spring MVC 框架 第7章的次数,从而提高网络效率。
HTTP 请求由请求方式、URL 和数据三部分构成,最常见的 HTTP 请求是 GET 请求和 POST 请求。
GET 请求仅仅给服务器发送一个 URL,可以在 URL 中包含参数,然后期待服务器返回相应的内容。
一个完整的 GET 请求的 URL 格式如下。
http://www.livebookstore.net/listBooks.jspx 与 GET 请求相比,POST 请求的参数不包含在 URL 中,而是以附加的消息体发送给服务器。
POST 请求的数据不会显示在浏览器的地址栏,因此用户无法看到。
由于 HTTP 协议是无状态的,而 Web 应用程序常常需要跟踪用户的身份,因此,服务器通常使用以下两种方式来保存用户状态。
(1)使用 Cookie 来标识用户。
浏览器在第一次请求服务器时将获得服务器传递给它的 Cookie,此后的请求中,浏览器将 Cookie 附加在请求中,服务器就可以识别出用户身份。
(2)通过 URL 重写的方式来跟踪用户。
服务器通过将响应页面中的 URL 链接附加上一个特定的标识符,就可以跟踪用户身份。
对于一个用户来说,在浏览器和服务器之间的反复的请求响应被称为一个会话。
由于服务器的资源是有限的,因此,会话有一个超时设置。
如果用户长时间没有通过浏览器请求服务器,服务器就认为此会话结束。
选择一个合适的会话超时是必要的,过短的会话会导致用户操作不便,过长的会话会导致服务器负担过重。
通常,JavaEE 服务器的默认会话超时(例如,30 分钟)是一个比较合理的设置。
在对 HTTP 协议有基本了解后,我们需要了解 JavaEE 的两种 Web 组件标准:Servlet和
JSP。
它们是整个 JavaEE Web 应用程序的基础。
7.1.2 Servlet 组件 Servlet 组件是 JavaEE 中最核心的 Web 标准。
Servlet 运行于 Web 容器中,按照请求/响应模式为用户提供服务。
一个典型的 Servlet 代码如下。
public class HelloServlet extends HttpServlet protected void doGetHttpServletRequest request HttpServletResponseresponse throws ServletException IOException PrintWriter pw response.getWriter pw.printquotlthtmlgtltboaygtlth1gtHello worldlt/h1gtlt/bodygtlt/htmlgtquot protected void doPostHttpServletRequest request HttpServletResponseresponse throws ServletException IOException doGetrequest response 197Java 技术大系 Spring 2.0 核心技术与最佳实践 doGet 方法和 doPost 方法分别对应 HTTP 的 GET 请求和 POST 请求。
Servlet API 定义了 HttpServletRequest 对象和 HttpServletResponse 对象,Web 容器负责将这两个对象传递给 Servlet 组件,开发人员需要从 HttpServletRequest 对象中获取需要的参数,然后将生成的页面写入 HttpServletResponse 对象的输出流中,即完成了一次完整的请求/响应。
由于 Servlet 组件必须运行在 Web 容器中,因此,要运行上面的示例,必须将其部署到 Web 服务器上。
我们以 Resin 服务器为例,建立一个 HelloServlet 项目,结构如图7-2 所示。
图 7-2 web 目录是网站的根目录,WEB-INF 目录(注意:区分大小写)存放了 Web 应用程序所需的全部文件,在 Web 应用程序运行期,服务器不允许用户直接通过 URL 访问WEB-INF 目录下的任何文件,这就保证了 服务 器端代码不会被客户端所获得。
在WEB-INF 目录下,classes 目录存放编译好的 class 文件,这里我们直接设置项目输出路径为 HelloServlet/web/WEB-INF/classes,如图 7-3 所示。
图 7-3 为了编译 HelloServlet,需要将 servlet-api.jar 引入到 Java Build Path 中。
注意,这个文件仅在编译时需要用到,在 Web 应用
程序的运行期不需要它,因为 Web 服务器内置了所有的 Servlet API。
web/WEB-INF/lib 目录存放了所有用到的第三方库文件,在这个简单的项目中我们198 使用 Spring MVC 框架 第7章没有用到任何第三方库,在后面的章节中,如果用到了第三方库,都需要将其加入到工程的
Java Build Path 中。
web.xml 是整个 Web 应用程序最基本的配置文件。
要让HelloServlet
工作,需要在 web.xml 中定义它,并为其配置 URL 映射。
ltxml versionquot1.0quot encodingquotUTF-8quotgt ltDOCTYPE web-app PUBLIC quot-//Sun Microsystems Inc.//DTD Web Application2.3//ENquot quothttp://java.sun.com/dtd/web-app_2_3.dtdquotgt ltweb-appgt ltservletgt ltservlet-namegthelloServletlt/servlet-namegt ltservlet-classgtexample.chapter7.HelloServletlt/servlet-classgt lt/servletgt ltservlet-mappinggt ltservlet-namegthelloServletlt/servlet-namegt lturl-patterngt/hellolt/url-patterngt lt/servlet-mappinggt lt/
web-appgt 在启动 Resin 前,请配置好环境变量 JAVA_HOME 和 RESIN_HOME,然后编写一个 resin.conf 配置文件,为了方便启动,我们还编写了一个 start_resin.bat 批处理文件来启动 Resin。
读者可以导入本书配套光盘中的配置文件。
PATHJAVA_HOMEbinPATH set RESIN_CONFCD/resin.conf CD web RESIN_HOMEhttpd -server-root CD -conf RESIN_CONF 运行 start_resin.bat 启动 Resin,由于我们配置的 HelloServlet 的映射路径为/hello,因此,在浏览器地址栏中输入“http://localhost:8080/hello”,即可看到 HelloServlet 在浏览器中运行的效果,如图 7-4 所示。
图 7-4 在 每 个 HTTP 请 求 到 来 时 , Web 服 务 器 都 会 创 建 HttpServletRequest 对 象 和HttpServletResponse 对象来封装 HTTP 请求和输出,然后传递给 Servlet 组件处理。
除了HttpServlet 作 为 核 心 的 Web 组 件 用 于 处 理 HTTP 请 求 外 , Web 容 器 还 提 供 了 199Java 技术大系 Spring 2.0 核心技术与最佳实践ServletContext 对象和 Session 对象来简化 Web 应用程序的开发。
ServletContext 对象在一个 Web 应用程序中有且仅有一个,它封装了应用程序所需的
常用信息;Session 对象负责管理一个会话,Web 容器为每一个客户端创建一个独立的 Session,Web 应用程序可以将客户端的相关信息放入其各自的 Session 中,以便管理。
7.1.3
JSP 组件 由于在 Servlet 中输出 HTML 页面极其困难且难以维护,因此,JavaEE 还提供了另一种以 HTML 为主的
JSP 组件。
在一个
JSP 页面中,大部分为 HTML 标签,仅用lt ... gt嵌入小部分 Java 代码,因此,
JSP 降低了网页设计的难度。
当用户请求一个
JSP 页面时,
JSP 页面首先要被 Web 容器转化为 Servlet 源代码,然后被编译为 class 文件并执行。
通常,编译过程在首次请求时被执行。
如果原始的
JSP文件做了更新,则 Web 容器会检测到更新并自动对其重新编译。
以下是一个典型的
JSP 页面。
lthtmlgt ltbodygt lth1gtlt out.printquotHello worldquot gtlt/h1gt lt/bodygt lt/htmlgt 和 Servlet 相比,
JSP 页面的部署就非常简单,不需要在 web.
xml 中定义,直接在地址栏输入
JSP 页面的路径即可。
可以从本书的配套光盘中导入 HelloJsp 项目,如图 7-5所示。
然后启动 Resin, “http://localhost:8080/hello.
jsp”其执行效果和 7.1.2 节的 Servlet 输入 ,示例完全一样,如图 7-6 所示。
图 7-5 图 7-6 由于
JSP 在执行前将首先被转化为 Servlet 源代码,然后被编译为 class 文件并载入执行。
第一次请求
JSP 时,需要一个转化和编译的过程,因此时间较长,而后续的请求就可以跳过转化和编译过程,因此速度会快得多。
可以在/WEB-INF/work 目录下找到由200 使用 Spring MVC 框架 第7章 hello.
jsp 页面转化的 Servlet
源代码和编译后的 class 文件。
JSP 和 Servlet 另一个不同之处在于,服务器会自动检测
JSP 页面的改动。
若发现更 改,则自动重新编译,而改动 Servlet 的 class 文件后,只有重新启动服务器或者重新加 载 Web 应用程序后才会生效。
7.1.4
JSP 标签
JSP 页面的输出通常是调用隐含的 out 对象的 print方法,这样做会导致复杂的 Java 代码。
为了简化输出,SUN 又在
JSP 中引入了标签的概念。
标签就是封装了一些功能并 能够输出
HTML 片段的 Java 类,使用起来却和 HTML 代码差不多。
例如,使用了 JSTL(
JSP Standard Tag Library)标签库的
JSP 页面输出“Hello world”和直接使用 out 对象 的
JSP 页面相比,格式更简洁,但是必须在页面开头处声明使用的标签库。
lt page contentTypequottext/html charsetutf-8quot gt lt taglib prefixquotcquot uriquothttp://java.sun.com/jstl/corequot gt lthtmlgt ltbodygt lth1gtltc:out valuequotHello worldquot /gtlt/h1gt lt/bodygt lt/htmlgt
JSP 标签的功能相当强大,不但可以输出 HTML 片段,还可以执行任意 Java 代码, 甚至包括执行 SQL 语句等,但是,滥用
JSP 标签将造成页面逻辑混乱,给后期维护造成 极大的困难,此外,自己开发定制标签库也不是一件容易的事情。
我们强烈建议,对标 签库的使用应该严格限制那些只用于显示输出结果的标签,不要使用诸如执行 SQL
查询 之类的标签。
那么,处理请求的逻辑应该在放在何处呢?MVC 模式正是为了解决逻辑 处理和显示输出相分离而提出的一种
设计模式。
稍后我们还将介绍 MVC 模式的原理和 在 Web 应用中的实现方式。
7.1.5 Filter 从 Servlet 2.3 规范开始,还引入了另一个激动人心的特性——Filter(过滤器)。
Filter 组 件 有 能 力 在 请 求 被 传 递 给 Servlet 或
JSP 组 件 处 理 前 截 获 请 求 , 通 过 修 改 HttpServletRequest 或 HttpServletResponse,实现诸如安全检查、定制输出等许多功能。
此 外,可以将多个 Filter 组合在一起形成过滤链,使请求能被链上的每一个 Filter 依次处理。
一个最简单的 Filter 实现如下。
201Java 技术大系 Spring 2.0 核心技术与最佳实践 public class SimpleFilter implements Filter public void initFilterConfig filterConfig throws ServletException // 在此初始化 Filter public void destroy // 在此释放资源 public void doFilterServletRequest request ServletResponse response FilterChain chain throws IOException ServletException // 千万不要忘了调用 chain.doFilter否则请求无法被继续处理: chain.doFilterrequest response 过滤器在 doFilter 方法中处理请求, SimpleFilter 中, 在 我们简单地调用 chain.doFilter将请求直接传递给下一个过滤器。
要特别注意,如果忘记了调用 chain.doFilter,那么请求处理就会到此结束,客户端很可能得不到任何输出。
下面的例子演示了如何利用 Filter 来实现安全检查。
这个 SecurityFilter 将向客户端返回 403 禁止访问的错误。
public class SecurityFilter implements Filter public void initFilterConfig filterConfig throws ServletException public void destroy public void doFilterServletRequest request ServletResponse response FilterChain chain throws IOException ServletException HttpServletResponseresponse.sendError403 // 没有调用 chain.doFilter,因为已经向客户端发送了 403 错误 在 web.xml 中,我们规定 SecurityFilter 将过滤/security 目录下的所有资源,即访问/security 目录下的任何资源,都会得到一个 403 禁止访问的错误。
ltxml versionquot1.0quot encodingquotUTF-8quotgt ltDOCTYPE web-app PUBLIC quot-//Sun Microsystems Inc.//DTD Web Application2.3//ENquot quothttp://java.sun.com/dtd/web-app_2_3.dtdquotgt ltweb-appgt ltfiltergt ltfilter-namegtsecurityFilterlt/filter-namegt ltfilter-classgtexample.chapter7.SecurityFilterlt/filter-classgt lt/filtergt ltfilter-mappinggt ltfilter-namegtsecurityFilterlt/filter-namegt202 使用 Spring MVC 框架 第7章 lturl-patterngt/security/.jsplt/url-patterngt ltdispatchergtREQUESTlt/dispatchergt lt/filter-mappinggt lt/web-appgt 我们创建如下的 SecurityFilter 工程,在 web 目录下包含两个
jsp 文件,其中,security.
jsp 放在/security 目录下,如图 7-7 所示。
图 7-7 打开浏览器,访问/normal.
jsp 页面是没有
问题的,如图 7-8 所示。
如果访问/security/security.
jsp,则会得到一个 403 错误,这说明 SecurityFilter 起作用了,如图 7-9 所示。
图 7-8 图 7-9 Filter 不仅能对请求实现预处理,还能定制输出。
利用这个特性,可以对输出进行后处理,例如,对输出的内容进行 GZip 压缩,以加快
网络传输。
下面的 GZipFilter 的例子
演示了如何对输出内容进行
压缩,可以从本书的配套光盘中找到工程源代码。
实现定制输出的关键是对 HttpServletResponse 进行包装,截获所有的输出,等到过滤器链处理完毕后,再对截获的输出进行处理,并写入到真正的 HttpServletResponse 对象 中 。
JavaEE 框 架 已 经 定 义 了 一 个 HttpServletResponseWrapper 类 使 得 包 装HttpServletResponse 更加容易。
我们扩展这个 HttpServletResponseWrapper,截获所有的输出,并保存到 ByteArrayOutputStream 中。
public class Wrapper extends HttpServletResponseWrapper 203Java 技术大系 Spring 2.0 核心技术与最佳实践 public static final int OT_NONE 0 OT_WRITER 1 OT_STREAM 2 private int outputType OT_NONE private ServletOutputStream output null private PrintWriter writer null private ByteArrayOutputStream buffer null public WrapperHttpServletResponse resp throws IOException superresp buffer new ByteArrayOutputStream public PrintWriter getWriter throws IOException ifoutputTypeOT_STREAM throw new IllegalStateException else ifoutputTypeOT_WRITER return writer else outputType OT_WRITER writer new PrintWriternew OutputStreamWriterbuffer getCharacterEncoding return writer public ServletOutputStream getOutputStream throws IOException ifoutputTypeOT_WRITER throw new IllegalStateException else ifoutputTypeOT_STREAM return output else outputType OT_STREAM output new WrappedOutputStreambuffer return output public void flushBuffer throws IOException ifoutputTypeOT_WRITER writer.flush ifoutputTypeOT_STREAM output.flush public void reset outputType OT_NONE buffer.reset public byte getResponseData throws IOException flushBuffer return buffer.toByteArray204 使用 Spring MVC 框架 第7章 class WrappedOutputStream extends ServletOutputStream private ByteArrayOutputStream buffer public WrappedOutputStreamByteArrayOutputStream buffer this.buffer buffer public void writeint b throws IOException buffer.writeb public byte toByteArray .