精华内容
下载资源
问答
  • servlet替代技术
    2017-05-15 17:20:16

    第 2 章:Java Web开发简介作者:党海峰孙霞    来源:希赛网    2014年03月14日

    Servlet技术

    在Java问世一年以后,Sun引入了Servlet。Servlet是CGI的替代品,也是Java进军Web开发领域的第一款技术。Servlet完全基于Java实现,提供了对整个Java应用编程接口(API)的完全访问和一个用于处理Web应用的完备的库。一个Servlet是运行于Servlet容器中的Java对象,与CGI不同的是,Servlet为每个请求启动一个单独的线程进行响应,从而大大的节约了空间和时间,处理过程如图2.2所示:

    图2.2  Servlet访问模式示意图

    Servlet与CGI相比具有如下优势。

    有效性:Servlet的初始化代码仅在Web服务器第一次加载时执行一次。一旦加载了Servlet,在处理新的请求时,只需调用一个新的服务方法;与处理每个请求都要全部加载一个完整的可执行程序相比,这是一种相当有效的技术。

    稳定性:Servlet能够维护每个请求的状态,一旦加载了Servlet,它即驻留在内存中,对收到的请求提供服务。

    可移植性:Servlet是用Java开发的,因而它是可移植的,继承了Java“一次编写,到处运行”的优势。

    健壮性:由于Java提供了定义完善的异常处理层次以供错误处理,故Servlet较为健壮。它还有垃圾收集器,可用于防止内存溢出等问题。

    可扩充性:Servlet能够通过继承现有对象开发新的对象,从而简化了新的Servlet对象的开发过程。

    Servlet的运行需要Servlet容器作为环境,Servlet只是一种特殊的Java对象,将Servlet部署到Servlet容器中后,Servlet容器负责在适当的时候创建Servlet、调用Servlet对象和销毁Servlet。每个Servlet对象都定义了三个方法,分别用于在被创建、被调用和被销毁时执行。每个Servlet在被部署到Servlet容器中时都配置了URL映射模式,如果到服务器的某个请求的URL与某个Servlet的映射模式相匹配,则该请求就会被分发到该Servlet。Servlet的核心方法是一个service()方法,当有请求被分发到该Servlet时service()方法就会被执行。

    在Servlet执行期间,有关请求和Web应用的属性等在处理中可能会用到的信息都可以通过Servlet提供的API获得,例如请求的URL、请求消息的头域信息和Web应用的上下文路径等。同时,对该请求的响应消息的有关内容和属性也可以通过Servlet的API在Servlet中进行设置,例如响应的编码方式、响应消息的头域信息等。

    从Servlet的功能和设计的角度讲,Servlet不仅仅可以用来处理HTTP请求和响应,它还可以用来处理其他协议的请求和响应消息。但不可否认Servlet发挥作用最大的领域还是在Web应用中处理HTTP的请求和响应。

    更多相关内容
  • 2.5 会话跟踪 第一次访问 第二次访问 * 2.5 会话跟踪 禁用Cookie 打开IE浏览器在菜单栏中选择工具|Internet选项在弹出的对话框中选择隐私标签页单击高级按钮选中替代自动cookie处理在第一方Cookie和第三方Cookie下面...
  • Java Servlet 技术简介

    千次阅读 2017-04-03 22:41:08
    servlet替代servlet 不是服务于 Web 页面的惟一方式。满足该目的的最早技术之一是 公共网关接口 (CGI),但那样就要为每个请求派生不同的进程,因而会影响效率。还有专用服务器扩展,如 Netscape Server...

    开始之前

    关于本教程

    在您最喜欢的 Web 浏览器中,您所阅读的页面是如何出现的呢?当登录到您最喜欢的 Web 站点时,该 Web 站点如何知道登录的用户是您?而 Web 零售商又如何接受您的在线订购呢?这些功能都是可能的,因为在这些场景的背后,运行于服务器上的代码将在 Web 会话中与您进行交互,通过该过程访问已存储的信息,并经常在一个或多个 Web 页面中展示动态信息。在 Java 语言世界中,这些功能的核心部分是由 servlet 提供的。本教程的目的就是向您介绍 servlet。文中将描述 servlet 是什么,它们是如何工作的,您可以如何使用它们来创建您能够想像到的任意复杂度的 Web 应用程序,以及作为一名专业编程人员,您如何才能最有效地使用 servlet。

    本教程的内容是为不熟悉,或者只是略微熟悉 servlet 的 Java 编程人员准备的。本教程假定您对于下载和安装软件以及 Java 语言(创建类、导入类等)有一般性的了解,但并不假定您已经了解 servlet。本教程包括一个说明 servlet 基本概念的简单例子,以及一个涉及更多内容的例子,它说明如何在小型的合同管理程序中更复杂地使用 servlet。

    本教程的适用对象

    如果您已编写 Web 应用程序多年,那么本教程可能不适合您。如果您不知道 servlet 是什么,或者只是略懂一二,那么请您继续读下去。虽然本教程所包含的只是 servlet 的部分内容,但它是一篇很好的入门介绍。

    不过,您应该非常了解 Java 编程的基础知识。但是,如果您还没有完全达到这些要求,那么请从阅读我撰写的 Java 编程简介 教程开始。

    工具和代码

    为了运行本教程中的例子或示例代码,至少需要在机器上安装 JDK 1.4.2 或更高版本,以及 Eclipse IDE。我们将介绍安装用于 Eclipse 的 Tomcat 插件的整个过程,这将允许您很容易地开发 servlet 应用程序。

    本教程中的所有代码示例都在 Windows XP 平台上用 J2SE 1.4.2 进行了测试,但必须使用 J2SE 1.4.1 或者甚至是 5.0 版本,并且不对它们进行修改,它们才会工作。

    要安装 Tomcat,则需要进入 Jakarta 的 Web 站点(请参阅 参考资料),并下载二进制版本的 Tomcat 5.0.28(编写本教程时,这是匹配 J2SE 1.4.2 的最新版本)。随 Windows 安装程序一起提供的包会使该平台上的安装轻而易举地完成。按照 readme 文件中的说明进行安装,您将顺利完成这项操作。

    为了安装用于 Eclipse 的 Tomcat 插件,需要进入 Sysdeo 的 Web 站点(请参阅 参考资料 ),并下载该插件的 zip 文件(编写本教程时,该文件是 tomcatPluginV3.zip)。然后只要将之解压至 plugins 目录,并按照下载页面底部的说明安装该插件即可。为了确保这个插件正常工作,请阅读极为简单的 HelloWorld servlet 设置“教程”,Sysdeo 页面底部有其链接(至于直接链接,请参阅 参考资料)。

    一旦安装了 Tomcat 及其插件,就可以准备开始本教程了。

    servlet 简介

    servlet 的作用

    当使用交互式 Web 站点时,您所看到的所有内容都是在浏览器中显示的。在这些场景背后,有一个 Web 服务器接收会话 中来自于您的请求,可能要切换到其他代码(可能位于其他服务器上)来处理该请求和访问数据,并生成在浏览器中显示的结果。

    servlet 就是用于该过程的网守(gatekeeper)。它驻留在 Web 服务器上,处理新来的请求和输出的响应。它与表示无关,实际上也不它应该与表示有关。您可以使用 servlet 编写一个,将内容添加到 Web 页面中,但那通常也不是一个好办法,因为它有鼓励表示与业务逻辑的混合的倾向。

    servlet 的替代品

    servlet 不是服务于 Web 页面的惟一方式。满足该目的的最早技术之一是公共网关接口(CGI),但那样就要为每个请求派生不同的进程,因而会影响效率。还有专用服务器扩展,如 Netscape Server API(NSAPI),但那些都是完全专用的。在 Microsoft 的世界里,有活动服务器页面(ASP)标准。servlet 为所有这些提供了一个替代品,并提供了一些好处:

    • 它们与 Java 语言一样是与平台无关的。
    • 它们允许您完全访问整个 Java 语言 API,包括数据访问库(如 JDBC)。
    • 大多数情况下,它们内在地比 CGI 更高效,因为 servlet 为请求派生新的线程,而非不同的进程。
    • 对于 servelet 有一个广泛的行业支持,包括用于最流行的 Web 和应用程序服务器的容器

    servlet 是对专业编程人员工具箱的强大补充。

    但什么是 servlet?

    作为一名专业编程人员,您碰到的大多数 Java servlet 都是为响应 Web 应用程序上下文中的 HTTP 请求而设计的。因此,javax.servlet 和 javax.servlet.http 包中特定于 HTTP 的类是您应该关心的。

    在创建一个 Java servlet 时,一般需要子类 HttpServlet。该类中的方法允许您访问请求和响应包装器(wrapper),您可以用这个包装器来处理请求和创建响应。

    当然,HTTP 协议不是特定于 Java 的。它只是一个规范,定义服务请求和响应的大致式样。Java servlet 类将那些低层的结构包装在 Java 类中,这些类所包含的便利方法使其在 Java 语言环境中更易于处理。正如您正使用的特定 servlet 容器的配置文件中所定义的,当用户通过 URL 发出一个请求时,这些 Java servlet 类就将之转换成一个 HttpServletRequest,并发送给 URL 所指向的目标。当服务器端完成其工作时,Java 运行时环境(Java Runtime Environment)就将结果包装在一个 HttpServletResponse 中,然后将原 HTTP 响应送回给发出该请求的客户机。在与 Web 应用程序进行交互时,通常会发出多个请求并获得多个响应。所有这些都是在一个会话语境中,Java 语言将之包装在一个 HttpSession 对象中。在处理响应时,您可以访问该对象,并在创建响应时向其添加事件。它提供了一些跨请求的语境。

    容器(如 Tomcat)将为 servlet 管理运行时环境。您可以配置该容器,定制 J2EE 服务器的工作方式,而且您必须 配置它,以便将 servlet 暴露给外部世界。正如我们将看到的,通过该容器中的各种配置文件,您在 URL(由用户在浏览器中输入)与服务器端组件之间搭建了一座桥梁,这些组件将处理您需要该 URL 转换的请求。在运行应用程序时,该容器将加载并初始化 servlet,管理其生命周期

    当我们说 servlet 具有生命周期时,只是指在调用 servlet 时,事情是以一种可预见的方式发生的。换言之,在任何 servlet 上创建的方法总是按相同的次序被调用的。下面是一个典型场景:

    • 用户在浏览器中输入一个 URL。Web 服务器配置文件确定该 URL 是否指向一个由运行于服务器上的 servlet 容器所管理的 servlet。
    • 如果还没有创建该 servlet 的一个实例(一个应用程序只有一个 servlet 实例),那么该容器就加载该类,并将之实例化。
    • 该容器调用 servlet 上的 init()
    • 该容器调用 servlet 上的 service(),并在包装的 HttpServletRequest 和 HttpServletResponse 中进行传递。
    • 该 servlet 通常访问请求中的元素,代表其他服务器端类来执行所请求的服务并访问诸如数据库之类的资源,然后使用该信息填充响应。
    • 如果有必要,在 servlet 的有用生命结束时,该容器会调用 servlet 上的 destroy() 来清除它。

    如何“运行”servlet

    “运行”servlet 就像运行 Java 程序一样。一旦配置了容器,使容器了解 servlet,并知道某些 URL 会致使容器调用该 servlet,该容器就将按照预定的次序调用生命周期方法。因此,运行 servlet 主要是指正确配置它,然后将浏览器指向正确的 URL。当然,servlet 中的代码正是发现有趣的业务逻辑的地方。您不必担心低层事件的进展,除非发生某种错误。

    不幸的是,经常会发生 一些令人沮丧的错误,尤其是在设置 servlet 时。致使 servlet 应用程序令人头痛的最大原因就是配置文件。您无法有效地调试它们。您只能通过试错法弄清楚这些错误,比如尽力破译可能会或不会在浏览器中看到的错误消息。

    一个简单的 servlet

    这个简单的 servlet 要完成的任务

    第一个 servlet 将完成极少量的工作,但是它将暴露编写 servlet 的所有基本要求。它将在浏览器窗口中输出一些简单的无格式文本:

    Hello, World!

    在创建该 servlet 时,我们将可以证实 Tomcat 应起的作用,并证实我们可以按照计划使用 Eclipse 创建 Web 项目。我们还将遍历在 Tomcat servlet 容器中配置 Web 应用程序的整个过程,如果您碰巧在 XML 文件中犯了一个小错误,那么您可能会对这个过程感兴趣。不要担心:至少在本教程中,Tomcat 会一直发挥起作用。

    在这第一个例子中,我们会将输出直接从 servlet 写入浏览器中。在本教程中,这将是我们最后一次使用该方法。

    设置 Eclipse

    我们需要执行少量工作,确保可以在 Eclipse 中创建并管理 Tomcat 项目。

    如果已经安装了该插件(仅仅通过将 Sysdeo zip 文件解压至 eclipse/plugins 目录),那么您应该可以在工具栏上获得一些附加的菜单项和工具。如图 1 中所示。

    图 1. Tomcat 插件功能

    Tomcat 插件功能

    工具栏按钮允许您启动、停止和重启 Tomcat,当需要运行 servlet 时,必须进行这些工作。

    为了允许我们创建 Tomcat 项目,这些项目具有合适的布局,有助于 Tomcat 的部署,我们必须告诉 Eclipse 一些事情。如果单击 Window>Preferences,那么您就将看到标准的 Eclipse 偏好设定对话框,其列表底部有一个名为 Tomcat 的新类别。单击它将向您展示 Tomcat 偏好设定的主页(参见图 2)。

    图 2. Tomcat 的偏好设定

    Tomcat 参数

    选择 Version 5.x,并指定 Tomcat home 的位置。(我的系统上,该位置是 C:\Program Files\Apache Software Foundation\Tomcat 5.0,但您的可能会不同)。选择 Context files 为内容声明模式。然后,单击 JVM Settings 对子类别进行偏好设定,并确保在该页顶部的下拉菜单中选择一个有效的 JRE。您可以使用默认的 JRE,也可以指向您的 JDK,并在 Java>Installed JREs 偏好设定页面中告诉 Eclipse 这个 JDK。

    完成这些操作后,请单击 OK。现在,我们准备创建 Tomcat 项目。

    创建 Tomcat 项目

    Tomcat 插件使 Web 开发人员更易于使用 Tomcat。如果单击 File>New>Project,并展开对话框(参见图 3)中的 Java 向导类别,那么您会在该对话框中看到一类新的项目向导:Tomcat 项目。

    图 3. 新建 Tomcat 项目

    新建 Tomcat 项目

    单击 Next,将该项目命名为“HelloWorld”,然后单击 Finish。如果在 Eclipse 中切换至 Java 透视图,那么就可以看到这个新项目。它所具有的结构将有助于部署 Tomcat(参见图 4)。

    图 4. Tomcat 项目结构

    Tomcat 项目结构

    正如我们稍后将看到的,work、WEB-INF 和 WEB-INF/src 目录特别重要。

    测试 Tomcat

    单击 Start Tomcat 工具栏按钮。当 Tomcat 尝试启动时,Eclipse 将用信息语句更新控制台。如果它启动了,并且没有显示任何堆栈跟踪,那么您已作好准备。如果看到堆栈跟踪,则事情会麻烦一些。不幸的是,试错法(通过您的好朋友 Google)是跟踪所发生错误的惟一方法。好消息是:可以进行刷新,新的项目(如我们刚才创建的那个)将消除发生严重错误的可能性。

    当 Tomcat 启动时,您不会看到任何东西(除了控制台内容)。您必须加以测试以确保它能工作。如果您需要一个快速指示,就设法打开浏览器并输入下列 URL:

    http://localhost:8080/

    如果一切正常,您就将看到一个精致的 Tomcat 欢迎页面,或一个列举了 Tomcat“启动内容”的目录。不必关注第二个。当我们运行第一个 servlet 时,我们将证明 Tomcat 在工作。

    声明类

    servlet 是一个类,因此,让我们创建一个基本的。在 Eclipse 中,要在 HelloWorld 项目中创建一个名为 HelloWorldServlet 的类。该类如下所示:

    public class HelloWorldServlet extends HttpServlet {
    
         public void service(HttpServletRequest request, HttpServletResponse response)
                   throws ServletException, IOException {
              PrintWriter writer = response.getWriter();
              writer.println("Hello, World!");
              writer.close();
         }
    }

    输入这些代码,然后按 Ctrl+Shift+O 组织一下导入语句。Eclipse 将允许您导入下列类:

    • java.io.IOException
    • java.io.PrintWriter
    • javax.servlet.ServletException
    • javax.servlet.HttpServlet
    • javax.servlet.HttpServletRequest
    • javax.servlet.HttpServletResponse

    请注意,我们将 HttpServlet 作为子类,并重载了 service() 方法。service() 方法是 servlet 引擎将在 servlet 生命周期中调用的最基本的处理方法。它接收一个请求包装器和一个响应包装器,而我们可以在我们的方法中访问它们。但在这里,我们不需要这样做,因为我们只是进行一些基本操作来使 servlet 工作。我们可能重载了 doGet(),但是 service() 将提供我们所需要的东西。

    在我们的 service() 方法中,我们在 response 包装器上调用 getWriter(),以便能够将一串文字输出到输出流中。然后,我们关闭输出流。这在生成输出的 servlet 中是很典型的:您执行需要执行的逻辑,然后写入输出流。

    配置 Web 应用程序

    Java 编程工作就完成了,但是现在,我们还必须对配置文件进行必要的工作。在我看来,这是 Web 开发的最大难点。幸好,该 Tomcat 插件分担了部分重担。

    右击 HelloWorld 项目,并选择 Properties。选择属性的 Tomcat 类别。您将看到该项目的环境,如下所示:

    /HelloWorld

    现在,去查看您 Tomcat 原目录中的文件系统。转至 conf/Catalina/localhost 子目录。在那里,您将看到一组 XML 文件。具体地说,您将看到一个 HelloWorld.xml 文件。打开它。该文件为 Tomcat 定义了一个 Web 应用程序语境。

    <Context path="/HelloWorld" reloadable="true" docBase="path to your project\HelloWorld" 
    	workDir="path to your project\HelloWorld\work" />

    当 Tomcat 启动时,它读取这些上下文文件,告诉 servlet 容器在哪里找您的类(包括 servlet)。如果回顾加载 Tomcat 时向控制台发出的信息(INFO)语句,就会看到该列表中与 Web 应用程序上下文有关的信息。

    在 Tomcat 中配置 Web 应用程序的最后一步是创建 web.xml 文件,需要将该文件放在项目的 WEB-INF 目录中。(注意:不要 将其放在 WEB-INF/src 目录中 —— 该目录将包含其他东西。)对于这个简单例子,该文件将如下所示:

    <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD 
    	Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
    <web-app>
      <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>HelloWorldServlet</servlet-class>
      </servlet>
      
      <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
      </servlet-mapping>
    </web-app>

    该文件向 Tomcat 定义了 Web 应用程序。该文件中的 servlet-name 元素命名了所使用的 servlet。servlet-class 元素将该名称映射到一个特定的类,该类定义了 servlet,即 HelloWorldServlet(本示例中)。servlet-mapping 元素告诉 Tomcat /hello(本例中)形式的 URL 映射我们的 servlet,这是由映射的 servlet 类定义的。

    一旦设置好该文件,就可以启动 Tomcat 并能看到 servlet 加载。

    运行 servlet

    正如前面提到的,“运行 servlet”仅仅包括启动 Tomcat,以及将 Web 浏览器指向将调用它的 URL。通过适当的工具栏按钮启动 Tomcat(如果它已经运行,则需要停止并重新启动它)。一旦 Tomcat 结束启动过程,它会打开浏览器并输出下列 URL:

    http://localhost:8080/HelloWorld/hello

    您将在浏览器窗口中看到一条好消息。

    动作 servlet

    简介

    在 Web 开发初期,许多专业编程人员都不得不弄清当他们继续时,如何较好地使用 servlet。最普遍的结果之一就是在服务器上暴露 servlet。每种类型的请求都有一个。

    这很快就变得令人头痛,因此,编程人员开始在其 servlet 中包含条件逻辑使之更具适应性,以便处理多种类型的请求。一段时间后,这也产生了一些糟糕的代码。有一种更好的方式,称作动作 servlet(action servlet),它实现了名为模型 2 的概念。据我了解,该思想是由 David M. Geary(关于他的更多信息,请参阅 参考资料)首次写到的,但是它已经较好的用于流行的 servlet 库中了,例如 Jakarta Struts 项目。

    在动作 servlet 中,并没有指示 servlet 行为的条件逻辑,而是具有动作(编程人员定义的类),servlet 授权这些类来处理不同类型的请求。大多数情况下,这个面向对象(OO)的方法要优于拥有多个 servlet,或在一个 servlet 中有多个 if 条件。

    我们的示例动作 servlet 执行的操作

    我们的示例动作 servlet 将是一个极简单的、基于浏览器的应用程序的网守(gatekeeper),该应用程序将允许我们创建、存储、查看以及删除合同列表项。这些记录项的格式都非常良好。最后,为了使用该应用程序,用户将必须登录它,但是,我们稍后将在 用户和数据 中添加这项功能。

    设置该项目

    在 Eclipse 中创建一个新的 Tomcat 项目,就像您为 HelloWorld 所做的一样。请注意,项目名称就是 servlet 默认的上下文值,因此,当输入访问 servlet 的 URL 时,将使用它。如果配置 Tomcat 使用上下文文件,那么它将为该项目自动创建一个上下文值。

    Eclipse 还应创建一个具有正确结构的项目,并带有下列重要目录:

    • WEB-INF
    • WEB-INF/src
    • work

    第一个目录(WEB-INF)存储重要的配置文件,具体地说就是 web.xml 文件,我们稍后将讨论它。它还在 classes 目录中包含了编译的代码。第二个目录(WEB-INF/src)存储 Java 类的源代码。第三个目录(work)包含 JavaServer Pages(JSP)文件的编译代码,代码发生更改之后,每当我们第一次点击 JSP 页面时,Tomcat 就会为我们自动创建这些编译代码(我们将在下一面板上谈论更多 JSP 技术)。该项目的根目录包含所有的 JSP 源文件,以及数据库文件。

    请注意,您可以在 Eclipse 的 Resource 视图中看到该结构的所有东西,但是在 Java Browsing 视图中只能看到 WEB-INF/src 和 work 目录。

    所有这些文件都包含在本教程所包括的 contacts.jar 文件中(有关链接,请参阅 参考资料)。为了导入它们,只要创建一个新的 Tomcat 项目,然后导入 contacts.jar(使用 Import>Zip file 选项)即可。这将会在正确的位置中产生除源代码之外的所有文件。源代码最终会在项目根目录的 src 目录中产生。将该文件夹的内容移至 WEB-INF/src 中,您就完成了所有的准备工作。

    表示

    这毕竟是一篇关于 servlet 的教程,几乎与表示无关。然而,若不在屏幕某处看到一些结果,我们实际上就只告知了事情的部分内容。您当然可以编写根本不涉及表示的 servlet,但是大多数 Web 应用程序在浏览器中显示信息,这意味着您必须选择使用一种表示机制。JavaServer Pages 技术就是一种典型的备选方案,并得到了广泛采用。

    通过 JSP 技术,您可以创建动态 Web 页面。它们支持静态 HTML(或其他标记,如 XML)和动态代码元素,而正如名字所隐含的,动态代码元素可以动态创建内容。在幕后,可以通过诸如 Tomcat 之类的容器将 JSP 页面编译成 servlet(即转换成 Java 代码)。然而,您几乎永远不必关心这一点。只需要知道发生了下列流程即可:

    • 用户在浏览器中输入 URL,J2EE servlet 容器将该浏览器指向一个 servlet。
    • servlet 完成其工作,并在会话中输入信息,或者在 bean 中,再发送给 JSP 页面。
    • JSP 代码转换 bean 和/或会话中的信息,并将响应发送给浏览器。

    您可以很容易地创建简单的 JSP 页面,只需要在 Web 应用程序中进行微小的修改即可,并且无需下载额外的代码库,就可以在 Tomcat 中运行它们,因此,我们将在这里使用它们(关于 JSP 技术的更多详细信息,请参阅 参考资料)。

    我们的 Contacts 应用程序会有一个主要的 JSP 页面,列举现有的合同并添加新的合同。稍后,我们将添加用于登录和退出页面。

    重要的是记得 JSP 技术只是一种表示选择。还有其他方法。受到极大欢迎的一种方法是 Jakarta Velocity 模板包(请参阅 参考资料)。JSP 技术存在一个主要的不足:复杂的、功能丰富的应用程序倾向于需要极其复杂的 JSP 页面,如果想使逻辑与表示分开,那么还需要进行额外的服务器工作来创建定制标签。另一个不足就是 JSP 技术经常带来了无法抑制的诱惑,将业务逻辑和表示混合,这容易导致需要繁重的维护工作的脆弱系统。

    据我看来,JSP 技术常常是一个错误的选择,而 Velocity(或者其他某种模板化方法)通常是正确的。但对于我们这个简单例子,JSP 技术将起作用,可以说明我们需要介绍的概念。在这样的简单情况下,将一点点逻辑和一点点表示混合是可以接受的。但从专业的角度来说,多数情况下,这种做法是不明智的,即使许多编程人员都这样做。

    web.xml 文件

    为了让我们能够使用将要创建的 JSP 页面,我们必须告诉 Tomcat 如何处理该页面。因此,我们必须在 WEB-INF 目录中创建一个 web.xml 文件。如下所示:

    <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD 
    	Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
    <web-app>
         <servlet>
              <servlet-name>contacts</servlet-name>
              <servlet-class>com.roywmiller.contacts.model2.ContactsServlet</servlet-class>
         </servlet>
         
         <servlet-mapping>
             <servlet-name>contacts</servlet-name>
             <url-pattern>/index.htm</url-pattern>
         </servlet-mapping>
         
         <servlet-mapping>
             <servlet-name>contacts</servlet-name>
             <url-pattern>*.perform</url-pattern>
         </servlet-mapping>
    
         <servlet>
              <servlet-name>jspAssign</servlet-name>
              <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
              <init-param>
                   <param-name>logVerbosityLevel</param-name>
                   <param-value>WARNING</param-value>
              </init-param>
              <init-param>
                   <param-name>fork</param-name>
                   <param-value>false</param-value>
              </init-param>
              <load-on-startup>3</load-on-startup>
         </servlet>
         
         <servlet-mapping>
              <servlet-name>jspAssign</servlet-name>
              <url-pattern>/*.jsp</url-pattern>
         </servlet-mapping>
    </web-app>

    我们为 HelloWorldServlet 创建了一个基本的 web.xml 文件,但是它非常小。随着应用程序变得更加复杂,web.xml 文件也不得不变得更智能。让我们快速分析该文件。

    <servlet> 标签为 servlet 指定一个别名,我们将在该文件的别处使用它。它还告诉 Tomcat 实例化哪个类,以便在内存中创建 servlet。在我的 Eclipse 工作区中,我创建了 com.roywmiller.contacts.model2 包来保存该 servlet 类。无论需要什么,都可以调用我们的包,但是到 servlet 的路径必须匹配 <servlet-class> 元素中的内容。我们定义的第二个 servlet 是下载 Tomcat 时附带的,您不必修改它。它只是 JSP 正在处理的 servlet。

    <servlet-mapping> 告诉 Tomcat 当某个 URL 到达服务器时,执行哪个 servlet。我们这里有三个映射。第一个将 Web 服务器查找的默认页面(<index.htm>)映射到 servlet。第二个告诉 Tomcat 将以 .perform 结尾的 URL 映射到 servlet。该形式的 URL 将告诉 servlet 实现哪个动作(稍后,我们将更详细地讨论其工作方式)。第三个映射告诉 Tomcat 使用 JSP servlet 来处理 JSP 页面。

    JSP 页面的用户视图

    在我们的简单例子中,我们不会花太多时间谈论 JSP 技术。JSP 技术可以使事情简单,不会陷入一般表示的细节中,特别是不会陷入 JSP 技术细节中。(有关的更多信息,请再次参阅 参考资料。)我们还会将所有事情放置在一个页面上,即使这样做有些不太现实。这将最大程度地减少仅仅为了说明如何使用 servlet 的重要概念而必须创建的页面数。

    我们的最初页面将显示合同列表,这将来自于一个包含了该列表的对象。它还将包含一个用于添加新合同的表单。该页将如图 5 所示。

    图 5. 合同列表页面

    合同列表页面

    虽然并非一件艺术作品,但该页在顶部按照良好的格式显示了所有合同。每一个页面都有 Delete 链接,用户可以单击它来删除特定的合同。该表单包含名称和地址值字段,以及关于合同类型(我们的简单示例中是 family 或 acquaintance)的单选按钮。这个简单页面将允许我们探索如何在 servlet 应用程序使用简单的动作框架。它还将让我们探索如何在用户会话期间使用请求,以及对 servlet 从浏览器接收的内容进行响应。

    现在,我们准备创建该页面。

    JSP 页面编码

    下面是我们的 JSP 页面的代码:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <%@ page import="java.util.*" %>
    <%@ page import="com.roywmiller.contacts.model.*" %>
    <html>
    <head>
    <title>Contacts List 1.0</title>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    
    <style type="text/css">
         body, table, hr {
              color: black;
              background: silver;
              font-family: Verdana, sans-serif;
              font-size: x-small;
         }
    </style>
    
    </head>
    
    <body>
         <jsp:useBean id="contacts" scope="session" 
             class="com.roywmiller.contacts.model.ContactList"/>
         
         <h2>Contact List 1.0</h2>
         <hr size="2"/>
         <table frame="below" width="100%">
           <tr>
             <th align="left"></th>
             <th align="left">Name</th>
             <th align="left">Street</th>
             <th align="left">City</th>
             <th align="left">State</th>
             <th align="left">Zip</th>
             <th align="left">Type</th>
           </tr>
         <%
           List list = contacts.getContacts();
           for (Iterator i = list.iterator(); i.hasNext();) {
             Contact contact = (Contact)i.next();
         %>
           <tr>
             <td width="100"><a href="removeContactAction.perform?id=<%= contact.getId()%>"
                 >Delete</a></td>  
             <td width="200"><%=contact.getFirstname()%> <%=contact.getLastname()%></td>
             <td width="150"><%=contact.getStreet()%></td>
             <td width="100"><%=contact.getCity()%></td>
             <td width="100"><%=contact.getState()%></td>
             <td width="100"><%=contact.getZip()%></td>
             <td width="100"><%=contact.getType()%></td>
           </tr>
         <%
           }
         %>  
         </table>
         <br/>
         <br/>
         <br/>
         <fieldset>
              <legend><b>Add Contact</b></legend>
              <form method="post" action="addContactAction.perform">
                   <table>
                        <tr>
                             <td>First Name:<td>
                             <td><input type="text" size="30" name="firstname"></td>
                        </tr>
                        <tr>
                             <td>Last Name:<td>
                             <td><input type="text" size="30" name="lastname"></td>
                        </tr>
                        <tr>
                             <td>Street:<td>
                             <td><input type="text" size="30" name="street"></td>
                        </tr>
                        <tr>
                             <td>City:<td>
                             <td><input type="text" size="30" name="city"></td>
                        </tr>
                        <tr>
                             <td>State:<td>
                             <td><input type="text" size="30" name="state"></td>
                        </tr>
                        <tr>
                             <td>Zip:<td>
                             <td><input type="text" size="30" name="zip"></td>
                        </tr>
                        <tr>
                             <td>Type:<td>
                             <td><input type="radio" size="30" name="type" value="family">
                                 Family <input type="radio" size="30" name="type" 
                                    value="acquaintance"
                                     checked> Acquaintance</td>
                        </tr>
                   </table>
                   <br/>
                   <input type="submit" name="addContact" value="  Add  ">
              </form>
         </fieldset>
    
    </body>
    </html>

    此时,在该页面上看到的大多数内容可能是希腊文。我们不会对所有内容都进行详细讨论,但是在接下来的几屏中,我们将指出其中要点,便于您理解 servlet 将如何与该页面进行交互。

    简单 JSP 页面的剖析

    在 JSP 页面中,HTML 就是 HTML。Java 代码嵌在该页面中,如下所示:

    <% Java code %>

    为了在该页面中嵌入 Java 代码,必须告诉 JSP 页面这些类位于何处,就像您在 Java 类中所做的那样。可以用语句完成这项工作,如下所示:

    <%@ page import="java.util.*" %>

    我们的页面显示一个合同列表,该列表来自于一个 ContactList 实例,JSP 页面通过下列代码行可以了解该实例:

    <jsp:useBean id="contacts" scope="session" 
    class="com.roywmiller.contacts.model.ContactList"/>

    这行代码告诉 JSP 页面在该页面的别处使用一个名为 contacts 的 bean。它是 com.roywmiller.contacts.model.ContactList 的实例,并拥有 session 作用域。

    请注意,该页面的主体中有一个 Java for 循环:

    List list = contacts.getContacts();
    for (Iterator i = list.iterator(); i.hasNext();) {
        Contact contact = (Contact)i.next();
    %>
        <tr>
           <td width="100">
               <a href="removeContactAction.perform?id=<%= contact.getId()%>" >Delete</a>
           </td>
           <td width="200"><%=contact.getFirstname()%> <%=contact.getLastname()%></td>
           <td width="150"><%=contact.getStreet()%></td>
           <td width="100"><%=contact.getCity()%></td>
           <td width="100"><%=contact.getState()%></td>
           <td width="100"><%=contact.getZip()%></td>
           <td width="100"><%=contact.getType()%></td>
        </tr>
    <%
    }

    这说明了 JSP 技术是如何混合 HTML 和 Java 语句的。在这里,我们将遍历 contact 对象的合同列表。每进行一次循环,就要向 HTML 表添加一个 <tr> 元素。在该表中,每个合同为一行,我们调用 Contact 实例的 getter 来填充表单元。对于第一个单元,需要为每一行创建一个 Delete 链接。我们将 href 属性设置为下列字符串:

    removeContactAction.perform?id=<%= contact.getId()%>

    当用户单击该链接时,先添加一个斜线(/),然后,该字符串将追加到发送至服务器的 URL 的末尾处。问号是请求参数的定界符(delimiter),紧跟在 name=value 对之后。本例中,我们发送每个合同的 ID。

    该页面别处也是发生相同的事情,例如,在表单中添加新合同。请注意 <form> 标签:

    <form method="post" action="addContactAction.perform">

    当用户单击 Add 按钮(表单底部的提交按钮)时,addContactAction.perform 被追加到 URL 中。

    这就是全部工作!一些精妙的语法正是许多专业编程人员要么吝啬于使用 JSP 技术,要么创建各种 helper 类(例如定制的 JSP 标签)来易化页面的创建、读取和维护的部分原因。但既然我们有了该页面,就可以开始编写一些代码了。

    创建 servlet

    我们的 servlet 类似于 HelloWorldServlet,并添加了动作处理功能:

    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.roywmiller.contacts.actions.Action;
    
    public class ContactsServlet extends HttpServlet {
    
         protected ActionFactory factory = new ActionFactory();
    
         public ContactsServlet() {
              super();
         }
    
         protected String getActionName(HttpServletRequest request) {
              String path = request.getServletPath();
              return path.substring(1, path.lastIndexOf("."));
         }
    
         public void service(HttpServletRequest request, HttpServletResponse response) throws 
         	ServletException, IOException {
              Action action = factory.create(getActionName(request));
              String url = action.perform(request, response);
              if (url != null)
                   getServletContext().getRequestDispatcher(url).forward(request, response);
         }
    }

    就像以前一样,我们扩展 HttpServlet 并重载 service() 方法。在该方法中,我们:

    • 从导致调用 servlet 的 URL 中派生动作名。
    • 基于该名称实例化正确的动作。
    • 告诉该动作开始执行。
    • 将响应发送给动作所指向的 URL。

    我们从导致调用 servlet 的 URL 中派生动作名,而该 servlet 是从 request.servletPath() 获得的。请记住,导致我们调用动作的所有 URL 都具有 *.perform 的形式。我们将解析该形式来获得圆点左边的字符串,该字符串就是动作名,然后将该动作名传递给 ActionFactory,以实例化正确的动作。现在,您看到我们为何告诉 Web 应用程序如何处理该形式的 URL,以及为何在 JSP 页面中使用这些“神奇”的字符串。正是因为这样,我们才可以在这里对它们进行解码,并采取对我们有利的动作。有什么替代方案?大量的 if 语句和大量的附加代码。正如我们将看到的,通过动作,需要执行的每个动作都已完全封装。

    这样做很好,但是我们需要一些附加类来完成该任务。这就是动作框架要做的事。

    简单的动作框架

    我们的简单动作框架有 4 个主要组件:

    • ActionFactory该工厂将请求中的动作名转换成 servlet 可以用来完成其工作的动作类。
    • Action 接口。该接口定义所有动作的极其简单的公共接口。
    • 名为 ContactsAction 的抽象类。该类实现了所有动作共用的一个方法,并强制子类实现另一个方法(perform())。
    • ContactsAction 的三个子类。这些子类使 servlet 能够进行自我引导、添加新合同和删除合同。

    在 servlet 的 service() 方法中,该过程以 ActionFactory 开始。

    ActionFactory

    下面是我们的 ActionFactory

    import java.util.HashMap;
    import java.util.Map;
    import com.roywmiller.contacts.actions.Action;
    import com.roywmiller.contacts.actions.AddContactAction;
    import com.roywmiller.contacts.actions.BootstrapAction;
    import com.roywmiller.contacts.actions.RemoveContactAction;
    
    public class ActionFactory {
         protected Map map = defaultMap();
         
         public ActionFactory() {
              super();
         }
         public Action create(String actionName) {
              Class klass = (Class) map.get(actionName);
              if (klass == null)
                   throw new RuntimeException(getClass() + " was unable to find 
                   	an action named '" + actionName + "'.");
              
              Action actionInstance = null;
              try {
                   actionInstance = (Action) klass.newInstance();
              } catch (Exception e) {
                   e.printStackTrace();
              }
    
              return actionInstance;
         }
         protected Map defaultMap() {
              Map map = new HashMap();
    
              map.put("index", BootstrapAction.class);
              map.put("addContactAction", AddContactAction.class);
              map.put("removeContactAction", RemoveContactAction.class);
    
              return map;
         }
    }

    ActionFactory 极其简单。它有一个 Map 动作类及其名称。我们在页面中使用该名称告诉 servlet 执行哪个动作。本例中,我们有三个动作:

    • BootstrapAction
    • AddContactAction
    • RemoveContactAction

    记得要分别通过 Add 表单和 Delete 链接,将添加和删除合同的动作作为 URL 发送给 servlet。BootstrapAction 仅仅适用于将 /index.htm调用至我们的动作框架中。

    当告诉该工厂创建 Action 时,它将实例化该类,并把它送回实例。向该工厂添加新动作其实就是简单地为该动作创建一个类,然后在工厂的动作 Map 中添加新的条目。

    Action

    Action 接口如下所示:

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public interface Action {
         public String perform(HttpServletRequest request, HttpServletResponse response);
         public void writeToResponseStream(HttpServletResponse response, String output);
    }

    现在,我们将广泛使用的方法是 perform()。而另一方法,writeToReponseStream() 允许动作直接写入响应的输出流,以传递给 JSP 页面。写入的任何内容(文本、HTML 等)都将在该页面上显示。我们暂时不需要使用该方法,但是,您可以在 ContactsAction 上获得它,以查看它如何工作。记得我们在 HelloWorldServlet 里使用了该方法体中的代码,因此,您不会对它感到陌生。

    用 BootstrapAction 启动

    我们拥有的 ContactsAction 的最简单的子类是 BootstrapAction,它是其他子类的一个好模型:

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class BootstrapAction extends ContactsAction {
         public String perform(HttpServletRequest request, HttpServletResponse response) {
              return "/" + "contactList.jsp";
         }
    }

    我们仅实现 perform() 来完成需要的工作。本例中,我们只要返回一个指向 contactList.jsp 的 URL(开头的斜线很重要,因此不要忘了它)。现在来回顾 ContactsServlet 上的 service() 方法:

    public void service(HttpServletRequest request, HttpServletResponse response) 
    	throws ServletException, IOException {
         Action action = factory.create(getActionName(request));
         String url = action.perform(request, response);
         if (url != null)
              getServletContext().getRequestDispatcher(url).forward(request, response);
    }

    该动作要么返回一个 URL 字符串,要么写入输出流,在 JSP 页面上显示。如果动作返回 URL 字符串,那么这是 BootstrapAction 的功劳,我们可以获取 ServletContext,在 URL 上向它请求 RequestDispatcher,最后将请求和响应发送给 JSP servlet,以便构造该页面。在这之后,只要动作 servlet 没有写入 JSP 页面的 PrintStream(现在已经关闭),它就会取回控制权,并完成剩下的工作。

    如果需要,可以在 response 上调用 sendRedirect(),而不是使用 RequestDispatcher

    response.sendRedirect("http://...");

    但是,这样做要付出代价。当使用调度程序时,我们将 request 和 response 授权给 JSP servlet,它还将转发现有的 HttpSession。那样就保存了该会话的内容。转发给另一个 URL 就不会如此。此刻,当我们开始显示该页时,我们所关心的会话中没有任何东西,因此,效果是一样的。但是很快,保存会话内容就会很重要。

    为了告诉动作框架这个新的可用动作,我们要向工厂的动作 Map 添加以下代码行:

    map.put("index", BootstrapAction.class);

    添加合同

    一个仅显示页面却不允许您做任何事情的应用程序不是很有用。我们需要能够添加合同的页面。

    为此,必须执行以下操作:

    • 创建一个名为 AddContactAction 的类。
    • 实现 perform(),向 Web 会话中维护的 ContactList 添加新的 Contact 实例。
    • 将新动作告诉工厂。

    将动作告诉工厂就是在工厂的 map 中添加另一条目,正如我们用 BootstrapAction 所做的那样。

    AddContactAction 类及其已实现的 perform() 方法如下所示:

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import com.roywmiller.contacts.model.Contact;
    import com.roywmiller.contacts.model.ContactList;
    
    public class AddContactAction extends ContactsAction {
         
         public String perform(HttpServletRequest request, HttpServletResponse response) {
              Contact newContact = createContact(request);
              
              HttpSession session = request.getSession();
              ContactList contacts = (ContactList) session.getAttribute("contacts");
              contacts.addContact(newContact);
              session.setAttribute("contacts", contacts);
              
              return "/contactList.jsp";
         }
    
         protected Contact createContact(HttpServletRequest request) {
              Contact contact = new Contact();
              contact.setFirstname(request.getParameter(RequestParameters.FIRSTNAME));
              contact.setLastname(request.getParameter(RequestParameters.LASTNAME));
              contact.setStreet(request.getParameter(RequestParameters.STREET));
              contact.setCity(request.getParameter(RequestParameters.CITY));
              contact.setState(request.getParameter(RequestParameters.STATE));
              contact.setZip(request.getParameter(RequestParameters.ZIP));
              contact.setType(request.getParameter(RequestParameters.TYPE));
              
              return contact;
         }     
    }

    在这里,我们要做的所有操作就是调用 createContact() 创建一个新的 Contact,并将其实例变量设置为包含请求参数的相应值。然后,我们在 HttpSession 中向 ContactList 添加新的 Contact。最后,我们告诉 servlet 返回 /contactList.jsp。

    记住每当我们创建一个 Contact 时,构造函数就给它分配一个惟一的 ID。回顾一下 JSP 代码。您将看到与我们在该动作中做什么有关的两项重要事情。首先要注意的是,通过添加下列这个行,我们保证在会话中总是有一个 ContactList 实例:

    <jsp:useBean id="contacts" scope="session" 
    class="com.roywmiller.contacts.model.ContactList"/>

    在第一次编译并显示(BootstrapAction 执行后发送至 JSP 页面的结果)JSP 页面时,它将实例化 ContactList。该对象没有包含任何内容,因此,当我们启动应用程序时,合同列表显示为空。在 AddContactAction 中,我们修改该对象,添加新的合同信息,然后将它重新插入会话中。以后,当显示该页面时,它将读取 Contact 实例的 ContactList 列表,并显示它们。

    第二,注意用来添加合同的表单,如下所示:

    <form method="post" action="addContactAction.perform">
    
         table with labels and text input fields
         
         <input type="submit" name="addContact" value="  Add  ">
    </form>

    该表单的动作致使在请求中将 addContactAction.perform 发送给 servlet。然后,它提取 addContactAction 的一部分作为动作名,在工厂中查找它,并创建 AddContactsAction 的一个实例。

    删除合同

    添加合同很重要,但是能够删除它们也同样重要。能够对合同进行编辑是很不错,但本教程只能做到这些。此外,添加编辑功能将像添加另一动作一样简单。因此,我们暂时只是添加删除合同的功能,然后转向更有意思的事情。

    与前面一样,我们只要添加了一个新的动作类,并实现其 perform() 方法,告诉工厂添加了这个方法。我们还必须确保 JSP 代码在合适的时候告诉 servlet 调用该动作。

    可以在 JSP 页面中查看合同表中每一行的 Delete 链接:

    <a href="removeContactAction.perform?id=<%= contact.getId()%>" >Delete</a>

    该链接告诉 servlet 请求工厂为名称 removeContactAction 提供正确的动作类。它还传递了该请求中一个名为 id 的参数,该请求中有一个为当前合同 ID 设置的值。

    我们的类如下所示:

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import com.roywmiller.contacts.model.ContactList;
    
    public class RemoveContactAction extends ContactsAction {
    
         public String perform(HttpServletRequest request, HttpServletResponse response) {
              int contactId = Integer.parseInt(request.getParameter("id"));
              
              HttpSession session = request.getSession();
              ContactList contacts = (ContactList) session.getAttribute("contacts");
              contacts.removeContact(contactId);
              session.setAttribute("contacts", contacts);
              
              return "/contactList.jsp";
         }
    }

    这里,我们要做的就是从会话中提取 id 参数和 ContactList,告诉该列表删除具有该 id 的 Contact,然后替换会话上的列表。最后但也是很重要的,我们要告诉 servlet 返回 contactList.jsp。

    运行应用程序

    如果好奇心还没有占上风,那么您现在就应该运行这个应用程序,看看它是如何工作的。

    启动浏览器,并输入下列 URL:

    http://localhost:8080/contacts/

    如果 Tomcat 运行正确,您就应查看 contactList.jsp,其列表中没有合同。在 add 表单上的文本字段中输入一些值,然后并单击 Add 按钮。您将在该列表中看到新的合同,该合同名称的左边有一个 Delete 链接。除非您修改它,否则其类型将设为 Acquaintance(单选钮的默认类型选择)。为了简便起见,我们没有对该表单进行任何验证,因此,您可以输入所有字段值完全相同的多个合同。每个合同都有一个惟一的 ID,因此,每个合同将分开显示,您可以逐个删除它们。

    说得简单点 —— 我们有了一个实用的 Web 应用程序!但我们无法保存合同列表,因此,每当启动该应用程序时,我们都必须重新输入它们。更糟的是,该应用程序的每位用户都有相同的合同列表。我们是可以通过添加对于惟一用户的支持,以及通过在文件中存储数据(可以工作的最简单的数据库),来解决这些问题。在下一小节中,我们将完成这两项工作。

    用户和数据

    增强应用程序

    在这一小节中,我们将对代码和现有的 JSP 页面进行少量重构(refactor),以便能为惟一的用户处理持久存储的合同数据。简言之,我们进行下列工作:

    • 创建一个 ContactsUser 对象。
    • 为每个 ContactsUser 提供用户名、密码和合同列表。
    • 修改 JSP 页面的 <jsp:useBean/> 标签以使用 ContactsUser
    • 添加 login.jsp 作为该应用程序的第一页。
    • 修改 contactList.jsp,为登录用户提供友好的欢迎消息。
    • 向 contactList.jsp 添加 Logout 链接,然后调用 LogoutAction
    • 添加 goodbye.jsp 来显示个性化的再见消息。
    • 添加 LoginAction 和 LogoutAction
    • 添加 UsersDatabase 来处理 usersDatabase.txt 中 Contacts 的存储和检索。
    • 通过重载 servlet 上的 init() 初始化 ContactsDatabase
    • 重载 servlet 上的 destroy(),告诉 UsersDatabase 关闭 usersDatabase.txt。

    实际上并非如此糟糕。惟一真正较新的概念是使用文件(只是更多标准 Java 语言工作)以及指向新页面。所有动作处理机制都是相同的。这说明动作框架的功能强大,而创建该框架只需要花费一点点宝贵的时间。它完全不像 Jakarta 的 Struts 框架那样复杂(请参阅 参考资料),将 Struts 框架用于应用程序中所进行的工作可能有点小题大做。

    ContactsUser

    删除导入语句和存取程序之后,ContactsUser 对象将如下所示(您可以在 contacts.jar 中找到完整的源代码):

    public class ContactsUser {
    
         protected String username = "";
         protected String password = "";
         protected List contactList = new ArrayList();
    
         public ContactsUser() {
         }
    
         public ContactsUser(String username, String password, List contactList) {
              this.username = username;
              this.password = password;
              this.contactList.addAll(contactList);
         }
    
         public boolean hasContacts() {
              return !contactList.isEmpty();
         }
    
         public void addContact(Contact aContact) {
              contactList.add(aContact);
         }
    
         public void removeContact(Contact aContact) {
              contactList.remove(aContact);
         }
    
         public void removeContact(int id) {
              Contact toRemove = findContact(id);
              contactList.remove(toRemove);
         }
    
         protected Contact findContact(int id) {
              Contact found = null;
    
              Iterator iterator = contactList.iterator();
              while (iterator.hasNext()) {
                   Contact current = (Contact) iterator.next();
                   if (current.getId() == id)
                        found = current;
              }
              return found;
         }
    
         accessors...
    }

    该类保存应用程序用户的有关信息。这通常就是它所要做的所有工作。它保存用户的用户名和密码,并维护该用户的合同列表。它允许动作框架中的各种动作为该用户添加和删除 Contact。在这里,不带参数的构造函数被用于单元测试。另一个接收三个参数的构造函数才是应用程序用户所使用的。

    您可能会问自己,“该类为何没有一个 ContactList 实例变量呢?”毕竟,我们早先花功夫创建了这样一个实例。我们为何不使用它呢?答案很简单,我们实际上不再需要该类。它包装了一个 ArrayList,并为我们提供了一些辅助方法。这些辅助方法实际在 ContactUser 上更有意义。如果我们使用了 ContactList,则需要通过拥有相同名称以及需要完成相同事情的 ContactUser 来调用它上面的方法。例如,如果 ContactUser 拥有一个 ContactList,并且将该实例变量命名为 contactList,那么 addContact() 将如下所示:

    public void addContact(Contact aContact) {
         contactList.addContact(aContact);
    }

    在这里对其他对象进行授权有些愚蠢。因此,我们删除了 ContactList 类。那正是重构要做的全部工作。我们简化了代码,并减少了系统中类的数目,但仍能完成相同的任务。拥有 ContactList 是创建系统时的中间步骤。它允许我们启动并运行系统,并帮助我们创建动作框架。然后,它的有效寿命就结束了,我们将删除它。编写一些代码并不代表您必须永远维护它们。

    修改 contactList.jsp

    修改 JSP 页面来使用新的 ContactUser 十分简单。我们需要进行三处修改。

    第一处就是修改 <jsp:useBean> 标签,如下所示:

    <jsp:useBean id="user" scope="session" 
    class="com.roywmiller.contacts.model.ContactsUser"/>

    现在,页面将实例化 ContactsUser,而非 ContactList

    第二处修改就是更新页面中的表行构建逻辑,以使用新的 user 变量:

    <%
      List list = user.getContacts();
      for (Iterator i = list.iterator(); i.hasNext();) {
        Contact contact = (Contact)i.next();
    %>

    第三处修改就是为用户添加一个退出链接:

    <a href="logoutAction.perform">Logout</a>

    我们将该链接置于“Contacts 1.0”头旁边。当用户单击该链接时,servlet 将执行 LogoutAction

    添加登录/退出页面

    与其他页面相比,支持登录和退出的页面都十分简单。惟一的差别存在于 <body> 标签中。下面是 login.jsp:

    <body>
    <h2>Contact List 1.0</h2>
    <hr size="2"/>
    <fieldset>
    <legend><b>Please Login</b></legend>
    <form method="post" action="loginAction.perform">
         <table>
              <tr>
                   <td>Username:<td>
                   <td><input type="text" size="30" name="username"></td>
              </tr>
              <tr>
                   <td>Password:<td>
                   <td><input type="text" size="30" name="password"></td>
              </tr>
         </table>
         <br/>
         <input type="submit" name="login" value="  Login  ">
    </form>
    </fieldset>
    
    </body>

    该页面有一个表单,其中带有两个文本字段和一个提交按钮。当用户单击 Login 时,servlet 将执行 LoginAction

    下面是 goodbye.jsp:

    <body>
    <jsp:useBean id="user" scope="session" 
        class="com.roywmiller.contacts.model.ContactsUser"/>
    
    <h2>Contact List 1.0</h2>
    <hr size="2"/>
    Goodbye <%= user.getUsername() %>!
    </body>

    该页面调用 ContactsUser bean 上的 getUsername() 来显示个性化的再见消息。

    当用户用一个数据库中没有的用户名尝试登录时,应用程序将放弃登录,并将用户指向一个错误页面,如下所示:

    <body>
    <h2>Contact List 1.0</h2>
    <hr size="2"/>
    <fieldset>
    <legend><b>Error</b></legend>
    There was an error: <%= session.getAttribute("errorMessage") %>
    </fieldset>
    </body>

    这是我们拥有的最简单的页面。它使用可从所有 JSP 页面获得的默认 session 变量来显示出错消息。

    添加 LoginAction

    LoginAction 类如下所示:

    public class LoginAction implements Action {
    
         public String perform(HttpServletRequest request, HttpServletResponse response) {
              String username = request.getParameter(USERNAME);
              String password = request.getParameter(PASSWORD);
              
              ContactsUser user = UserDatabase.getSingleton().get(username, password);
              if (user != null) {
                   ContactsUser contactsUser = (ContactsUser) user;
                   request.getSession().setAttribute("user", contactsUser);
                   return "/contactList.jsp";
              } else
                   request.getSession().setAttribute("errorMessage", 
                       "Invalid username/password.");
                   return "/error.jsp";
         }
    
    }

    该动作从请求中提取 username 和 password 参数,然后用 username/password 组合查看数据库中是否包含该用户。如果存在该用户,那么就将该用户置于会话中,并直接进入 contactList.jsp。如果数据库中没有该用户,那么就在会话上设置一条出错消息,并转至 error.jsp。

    现在,添加动作对于我们而言应该很容易了。我们向动作工厂添加一个条目,如下所示:

    map.put("loginAction", LoginAction.class);

    在设置好页面之后,工厂会感知新动作,添加操作也就完成了。您应该能够运行该应用程序,并看到登录页面。当输入用户名和密码时,不管输入的是什么,您都会看到出错页面。等一会儿之后,您就可以通过有效的用户名和密码登录,并看到包含空合同列表的 contactList.jsp。

    添加 LogoutAction

    LogoutAction 类如下所示:

    public class LogoutAction implements Action {
    
         public String perform(HttpServletRequest request, HttpServletResponse response) {
              UserDatabase.getSingleton().shutDown();
              return "/goodbye.jsp";
         }
    
    }

    在这里,我们将告诉数据库执行 shutDown() 操作。UserDatabase 上的方法如下所示:

    public void shutDown() {
         writeUsers();
    }
    
    protected void writeUsers() {
         StringBuffer buffer = new StringBuffer();
         Collection allUsers = users.values();
         Iterator iterator = allUsers.iterator();
         while (iterator.hasNext()) {
              ContactsUser each = (ContactsUser) iterator.next();
              UserRecord record = new UserRecord(each);
              buffer.append(record.getFullRecord());
         }
         writeText(buffer.toString());
    }
    
    protected synchronized void writeText(String text) {
         Writer writer = null;
         
         try {
              writer = new FileWriter(usersFile.getAbsolutePath());
              writer.write(text);
         } catch (Exception e) {
              throw new RuntimeException("Unable to append to file.", e);
         } finally {
              closeWriter(writer);
         }
    }

    shutDown() 调用 writeUsers(),该方法将迭代内存中保存的所有用户(当 servlet 对自身进行初始化时,将从我们读入该文件的地方开始),为每个用户创建一个 UserRecord,然后将完整的字符串传递给 writeText()。writeText() 将该字符串写入文件中,重写现有的内容。UserRecord 类是一个极好的辅助类,封装了文件中每条用户记录的所有烦杂的标记工作。您可以自己检查代码(关于完整的源代码清单,请参阅 contacts.jar)。

    一旦关闭数据库,就可以告诉 servlet 发送 goodbye.jsp,显示个性化的再见。

    userDatabase.txt 文件

    大多数 Web 应用程序从某种“数据库”中访问数据。许多都使用行业级(industrial-strength)的 RDBMS,但文本文件也可以是数据库。它是可以工作的最简单的数据库。如果您将它包装得很好,并将访问细节隐藏在一个接口之后,而该接口使得应用程序中的其他类极易于访问这些数据,那么底层数据采用什么样的存储形式实际上就没什么关系。

    在这个应用程序中,我们将使用一个文本文件。该文件将按照下列形式,为每位用户保存一行:

    username password comma-delimited contact1 info|comma-delimited contactN info|...

    该文件中的用户名将是明文,但出于安全考虑,密码将是 Base64 编码(绝对最简单)。合同条目将用逗号分隔。而合同本身将通过 | 字符分隔。这种格式没有什么特别。它只是执行我们需要它完成的工作,以允许我们易于解析该文件。

    为了方便,我们将该文件放置在本项目的根目录中,以便该文件的路径简单直接。

    为了使事情简单,该应用程序不支持用户维护功能,这意味着无法在应用程序中添加或删除用户。这就表示您必须手工将用户添加到 userDatabase.txt 中。例如,要添加一个名为 testuser 以及密码为 password 的用户,就要向该文件添加下列一行:

    testuser cGFzc3dvcmQ=

    每个条目中的密码都是通过 Base64 编码进行编码的。您可以在 contacts.jar 中使用 EncoderDecoder 类来计算您密码的编码版本。它的 main() 方法允许您输入明文字符串,然后运行该类,在控制台上输出已编码的密码。

    UserDatabase

    UserDatabase 包装了与文本文件的交互。这个类的清单看上去很大,但是并不复杂(大部分让人感觉很复杂的东西是那些额外的 Java 编码内容,处理读写文件操作需要它们)。我们将在本面板上讨论一些要点(关于完整的代码清单,请参阅 contacts.jar)。

    该类实现了 Singleton 模式,并且维护了一个实例,而所有用户则通过调用 getSingleton() 共享这个实例。

    该类维护了 ContactsUser 的一个 Map,该 Map 将用户名与密码的组合作为每个条目的密钥。任何东西都可以充当每个条目的键,但这个比较方便。

    在 servlet 的 init() 方法中,我们将告诉 UserDatabase 数据库文件位于何处(基于 ServletContext),然后告诉它通过调用 initialize() 初始化它本身。该方法如下所示:

    public void initialize() {
         usersFile = new File(databaseFilePathname);
         
         String allUsers = retrieveText();
         StringTokenizer tokenizer = new StringTokenizer(allUsers, "\n");
         while (tokenizer.hasMoreTokens()) {
              String userEntry = tokenizer.nextToken();
              UserRecord record = new UserRecord(userEntry);
              put(new ContactsUser(record.getName(), record.getPassword(), 
                  record.getContactList()));
         }
    }

    该方法通过调用 retrieveText 读入完整的文件,标记较大的字符串,为每个用户创建 UserRecord,然后调用 put() 来在该 map 中放置新的 ContactsUser。该方法的真正作用体现在调用 retrieveText() 和 put() 中:

    protected synchronized String retrieveText() {
         BufferedReader bufferedReader = null;
    
         try {
              bufferedReader = 
                  new BufferedReader(new FileReader(usersFile.getAbsolutePath()));
              char charBuff[] = 
                  new char[(int) new File(usersFile.getAbsolutePath()).length()];
    
              bufferedReader.read(charBuff);
              return new String(charBuff);
         } catch (Exception e) {
              throw new RuntimeException("Unable to read in the file.", e);
         } finally {
              closeReader(bufferedReader);
         }
    }
    
    protected void closeReader(BufferedReader bufferedReader) {
         try {
              if (bufferedReader != null)
                   bufferedReader.close();
         } catch (Exception ex) {}
    }
    
    public void put(ContactsUser user) {
         String userKey = user.getUsername() + user.getPassword();
         users.put(userKey, user);
    }

    retrieveText() 方法负责完成文件读取工作。它创建了一个 BufferedReader,将整个文件内容读入到字符缓冲区中,然后将这些内容转换成一个 String。在其 finally 子句中,它只调用 closeReader() 来完成该工作。writeText() 方法将输出写入文件,重写现有的内容。该方法隐藏了同一类型的文件交互细节。

    put() 方法为用户创建密钥(用户名加上密码),并将该密钥插入用户的 map 中。

    有效地使用 servlet

    简介

    本教程中,我们只涉及了用 servlet 可完成的浅层功能。Web 应用程序可以与您所能想像的一样复杂。尽管所有 Web 应用程序的底层机制基本上是相同的,如果用 Java 语言编写代码,servlet 将是核心部分。创建更为复杂的应用程序实质上就是使用更复杂的工具和库。

    然而,许多编程人员会在这个地方犯错,从而导致创建出糟糕的 Web 应用程序。这一小节包含一些关于如何避免这些错误的建议。大多数具有 Web 开发经验的 Java 编程人员都赞同其中的一些建议。还有一些更具争议性的建议。无论在哪种情况下,它们都将帮助您较好地了解 servlet。

    使用一个 servlet

    如果您不能只使用一个 servlet,就使用尽可能少的数目。实际上,我建议您只使用一个,直到肯定无法再这样下去。应该不需很多 servlet,您肯定无需为每种类型的请求提供一个 servlet。

    不要在 servlet 上耗费太长时间

    在 servlet 中尽可能少花时间。

    servlet 不是为业务逻辑提供的场所。只有糟糕的 OOP 设计才那样。将 servlet 考虑成以下两种事物之一:

    • UI 后面的一个附加层,帮助服务器了解“事件”。
    • 服务器前面的一个附加层,允许您将浏览器用作 UI。

    无论将 servlet 视为哪一种事物,它都是您快速将问题分派到应用程序的其他部分、然后退出的一个地方。

    使用动作

    动作框架,即使是像本教程中所使用的一样简单,是一种功能强大的方法。它允许您采纳前面的意见:在 servlet 中花费尽可能少的时间。它还是很好的 OOP 设计。每个动作类完成一件事,或一组联系紧密的事情。

    某些人认为这会分裂代码,使之更难以理解。我认为这种反对源于以下两件事:

    • 人们不习惯于查看面向对象的代码,当您第一次开始查看它时,肯定会感觉它们支离破碎的。
    • 人们更喜欢程序代码,即使该代码在对象中。

    考虑其他选择。若没有动作(假设您不是简单地授权给其他对象,不调用“动作”),则必须使用大量的 if 语句。如果只有两三个 if 语句,代码可能还很容易阅读。但是如果您有 10 个 if 语句,则很可能令人感到目眩。要求您滚动多个屏幕的方法比较令人讨厌。它通常意味着该方法要进行太多工作。至少,您要将 service()(或 doGet() 等)中完成的类似于动作的事情提取到其他方法中,要对这些方法起个好的名称,这样您才知道它们是做什么的。

    使用动作。在需要添加功能时,尽管添加动作好了。

    使用 service(),除非您不能使用它

    servlet 世界中有许多人们声称,您不应重载 service()。并且说您应该重载 doGet() 和/或 doPost(),可以让它们相互调用,使代码类似于下面所示:

    public void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
         statements
    }
          
    public void doPost(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
         doGet(request, response);
    }

    我不知道这些思想源于何处。Bruce Eckle 指出该思想是从 CGI 时期遗留下来的,当人们开始习惯注意进来的是 GET 还是 POST 时。(关于对他的理论的更详细版本的链接,请参阅 参考资料。)我从未听到不要使用 service() 的好理由。任何情况下,如果您使用 service(),然后确定它将比使用任何一种 doX() 风格都要好,那么请重构代码!在这个时候,就使用 service(),因为它更简单。

    不要混合表示和业务逻辑

    对于简单的应用程序而言,生成复杂的 HTML 字符串来输出 JSP 页面的输出流很不错,但是创建功能更丰富的应用程序时,该方法要困难得多。

    将表示放置在它所属的地方是一种比较明智的做法:在页面中。JSP 技术允许您完成该工作,但正如我前面所说的,它需要做大量工作才能使业务逻辑与表示分离。诸如 Velocity 之类的模板化引擎通常是更好的选择。无论您选择哪种方法,都要尽可能少地混合业务逻辑和表示。

    很好地处理异常

    我们没有在本教程中过多谈论这一点,但是异常处理在创建 Web 应用程序时变得很重要。没有比在服务器端发生一些意想不到的事情,然后在浏览器中看到隐藏的堆栈跟踪更让用户沮丧的了。其中一些跟踪可能极其迟钝、晦涩。追查它们可能令人很苦恼。

    许多打包的 Web 应用程序开发库,如 Struts(请参阅 参考资料),都附带了用来处理动态消息(包括错误)显示的内置框架。您可以使用这些功能。

    不要使用每种功能

    您是否需要使用您正采用的 Web 应用程序开发框架或库中的每项功能呢?很可能不需要,每一种功能都使用将使代码比您所需要的要复杂得多。实际上,除了可以工作的最简单框架,我建议您根本不要使用别的框架。有时候您知道,对于您手头的问题而言,将要使用或需要使用的功能或框架可能有些小题大做。

    当使用框架是可取的方法时,就使用它。不要假定您需要它,等待系统告诉您需要使用时再使用它。一些编程人员认为那是“糟糕的设计”。并非如此。假设您将需要一个特定的框架,甚至是需要该框架中的某个特定功能,那么这样的设计可能是过分设计。您应针对所需要的进行设计;设计通常会随着系统扩展而更改。在开发开始之前选择框架是无益的,如果该框架不支持或不允许完成您需要完成的一些工作,那会让您急得撞墙。

    结束语

    本教程中,您知道了 Java servlet,以及如何专业地使用它们。当然,本教程中的例子十分简单,但是它们说明了创建 Web 应用程序将用到的大部分 servlet 概念。还有更多可用的功能(配置等),但是几乎用 Java 语言编写的每个 Web 应用程序的核心都是一个或多个 servlet,它们在幕后的一台或多台服务器上充当业务逻辑的网守。

    更重要的是,您了解了很好地使用 servlet 的一些技术。Web 应用程序常常会变成一大堆凌乱的代码。通过使用由基本 OOP 原则驱动的简单技术,您可以避免这种混乱,创建易于增强和维护的应用程序。

    参考资料

    展开全文
  • SpringBoot 的Web模块默认使用 Tomcat作为内嵌的Servlet容器,同时,SpringBoot还支持Jetty、Undertow等Servlet容器。 tomcat、undertow、jetty性能对比 Tomcat:是最稳定的服务器,而不是网上各种博主说的Undertow...

    SpringBoot官方文档——Use Another Web Server

    1 前言

    SpringBoot 的Web模块默认使用 Tomcat作为内嵌的Servlet容器,同时,SpringBoot还支持Jetty、Undertow等Servlet容器。

    tomcat、undertow、jetty性能对比

    Tomcat:是最稳定的服务器,而不是网上各种博主说的Undertow更优。

    Jetty:更适合长连接的服务,但是长连接我更倾向于选择Jetty。

    Undertow:更适合用于IO密集型服务器,或者文件服务器使用Undertow会是一个不错的选择。

    所以,在一般情况下还是老老实实的使用Springboot默认的Tomcat吧。

    2 替换为其他嵌入式Servlet容器

    以Jetty为例

    我们可以把Tomcat的相关依赖在pom.xml中的spring-boot-starter-web中剔除掉,使环境不再拥有Tomcat依赖,同时加入Jetty的依赖那么就能使Jetty作为满足条件被SpringBoot选择了。
    a1wxyT.png
    嵌入式Servlet容器的自动配置原理 GO!!!

    2.1 Tomcat(默认使用)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器 -->
    </dependency>
    

    2.2 替换为 Jetty

    <!-- Jetty 9.4不支持Servlet 4.0 -->
    <properties>
        <java.version>1.8</java.version>
        <servlet-api.version>3.1.0</servlet-api.version>
    </properties>
    
    <!‐‐ 引入web模块 ‐‐>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring‐boot‐starter‐web</artifactId>
        <!-- 排除 Tomcat依赖 -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!‐‐引入Jetty‐‐>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    

    注意:

    Servlet API的版本已被覆盖,因为与Tomcat 9和Undertow 2.0不同,Jetty 9.4不支持Servlet 4.0

    2.3 替换为 Undertow

    <!‐‐ 引入web模块 ‐‐>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring‐boot‐starter‐web</artifactId>
        <!-- 排除 Tomcat依赖 -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!‐‐引入Undertow‐‐>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    

    2.4 替换成功控制台打印结果

    a1rEpn.png

    展开全文
  • Java Servlet技术

    千次阅读 2019-02-16 10:00:03
    Java Servlet技术
                   

    Java Servlet技术

    Stephanie Bodoff

    当Web刚开始被用来传送服务时,服务提供者就已经意识到了动态内容的需要。Applet是为了实现这个目标的一种最早的尝试,它主要关注使用客户端平台来交付动态用户体验。与此同时,开发人员也在研究如何使用服务器平台实现这个目标。开始的时候,公共网关接口(Common Gateway Interface ,CGI)脚本是生成动态内容的主要技术。虽然使用得非常广泛,但CGI脚本技术有很多的缺陷,这包括平台相关性和缺乏可扩展性。为了避免这些局限性,Java Servlet技术因应而生,它能够以一种可移植的方法来提供动态的、面向用户的内容。

    什么是Servlet?

    一个servlet就是Java编程语言中的一个类,它被用来扩展服务器的性能,服务器上驻留着可以通过“请求-响应”编程模型来访问的应用程序。虽然servlet可以对任何类型的请求产生响应,但通常只用来扩展Web服务器的应用程序。Java Servlet技术为这些应用程序定义了一个特定于HTTP的 servlet类。

    javax.servletjavax.servlet.http包为编写servlet提供了接口和类。所有的servlet都必须实现Servlet接口,该接口定义了生命周期方法。

    当实现一个通用的服务时,您可以使用或扩展由Java Servlet API提供的GenericServlet类。HttpServlet类提供了一些方法,诸如doGet和doPost,以用于处理特定于HTTP的服务。

    本章主要讲述如何编写对HTTP请求产生响应的servlet。这里假设您已经了解了一些HTTP协议的基础知识。如果对这些协议不熟悉的话,您可以从HTTP概述中对HTTP协议有一个初步的了解。

    Servlet示例

    本章使用Duke's Bookstore应用程序来说明与servlet编程相关的任务。表14-1列出了解决每个书店功能的servlet。每个编程任务用一个或多个servlet来说明。例如,BookDetailsServlet说明如何处理HTTP GET请求,BookDetailsServlet和CatalogServlet显示如何构建响应,而CatalogServlet 则说明如何跟踪会话信息。

    141 Duke's Bookstore Servlet例子 

                                                                                                                                                                                                           
               

    功能

               
               

    Servlet

               
               

    进入书店

               
               

    BookStoreServlet

               
               

    创建书店标识

               
               

    BannerServlet

               
               

    浏览书店的目录

               
               

    CatalogServlet

               
               

    将书放入购物车

               
               

    CatalogServlet,

               

    BookDetailsServlet

               
               

    获取关于特定的某本书的一些详细信息

               
               

    BookDetailsServlet

               
               

    显示购物车

               
               

    ShowCartServlet

               
               

    从购物车中移除一本或多本书

               
               

    ShowCartServlet

               
               

    购买购物车中的书

               
               

    CashierServlet

               
               

    获得对购买的确认

               
               

    ReceiptServlet

               

    这些书店应用程序的数据保存在数据库中,并通过帮助类database.BookDB进行存储。database包也包括BookDetails类,一个BookDetails类用来代表一种书。购物车和购物车项用cart.ShoppingCart 和cart.ShoppingCartItem来分别表示。

    书店应用程序的源代码放在<JWSDP_HOME>/docs/tutorial/examples/web/bookstore1目录中,这个目录是在对指南包进行解压缩时创建的。

    要构建、安装和运行这个实例,需完成以下步骤:

    1.     在终端窗口中,转到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore1。

    2.     运行build。 build目标将产生任何必要的编码并且将文件直接拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore1/build 。

    3.     确认Tomcat已经开始执行。

    4.     运行ant install 。install 目标通知Tomcat 已经有了新的上下文 。

    5.     启动PointBase公司的数据库服务器,并且在没完全准备好的情况下仍然指向数据库(见从Web应用中访问数据库)。.

    6.     打开书店的URL http://localhost:8080/bookstore1/enter以运行该应用程序。

    要部署该应用程序,要完成以下步骤:

    1.     运行ant package。这个包任务是创建一个WAR 文件,该文件包含WEB-INF/classes中的应用程序类 和META-INF中的context.xml 文件。

    2.     确认Tomcat已经开始执行。

    3.     运行ant deploy。Deploy目标将WAR拷贝到Tomcat,并且通知已经有了新的上下文。

    故障排除   

    一般的问题和其解决方案列举了Web客户端为什么会失败的一些原因。另外,Duke书店返回了以下异常:

    ·       BookNotFoundException——如果一本书不能在书店的数据库中找到,则返回该异常。如果用户没有运行ant create-book-db来加载书店数据库中的数据、或没有运行数据库服务器、或数据库已经崩溃,这些都将产生该异常。

    ·       BooksNotFoundException——如果书店的数据不能被获取,则返回该异常。如果用户没有运行ant create-book-db来加载书店数据库中的数据、或没有运行数据库服务器、或数据库已经崩溃,这些都将产生该异常。

    ·       UnavailableException——如果servlet不能获取到用来表示书店的Web上下文属性,则返回该异常。如果您没有拷贝指向(PointBase)的客户端库<PB_HOME>/lib/pbclient45.jar to <JWSDP_HOME>/common/lib、或者如果指向的(PiontBase)服务器没有运行、或用户没有定义Tomcat中用来引用指向数据库(PointBase)的数据源,这都将产生该异常。

    因为指定了一个错误页,用户将看到这样的一个消息The application is unavailable. Please try later. 如果指定了一个正确页,Web容器将产生一个包含A Servlet Exception Has Occurred消息的默认页和一个用来帮助诊断异常产生原因的栈。如果使用errorpage.html,用户可以了解Web容器决定异常产生原因的日志。Web日志位于<JWSDP_HOME>/logs目录中,由jwsdp_log.<date>.txt来命名。

    Servlet的生命周期

    一个servlet的生命周期由部署servlet的容器来控制。当一个请求映射到一个servlet时,该容器执行下列步骤。

    1.     如果一个servlet的实例并不存在,Web容器

    a.   加载servlet类。

    b.   创建一个servlet类的实例。

    c.   调用init初始化servlet实例。该初始化过程将在初始化servlet中讲述。

    2.     调用service方法,传递一个请求和响应对象。服务方法将在编写服务方法中讲述。

    如果该容器要移除这个servlet,可调用servlet的destroy方法来结束该servlet。结束过程将在结束Serlvet中讨论。

    处理Servlet生命周期事件  

    在servlet的生命周期中,用户可以通过定义监听器对象对事件进行检测和产生反应。当生命周期事件发生时,调用该对象的方法。要使用这些监听器对象,用户必须定义监听器类,并且指定相应的监听器类。

    定义监听器类   

    您可以将监听器类定义为一个listener接口的实现。Servlet生命周期事件列出了可以检测的事件和相应的必须实现的接口。当调用一个监听器方法时,需向该方法传递一个包含事件适当信息的事件。例如,向HttpSessionListener接口中的方法传递的是一个HttpSessionEvent事件,这个事件包含了一个HttpSession。

    表14-2Servle生命周期事件

                                                                                                                                           
               

    对象

               
               

    事件

               
               

    监听器接口和事件类

               
               

    Web上下文
    (见访问Web上下文)

               
               

    初始化和销毁

               
               

    javax.servlet.
    ServletContextListener

               

    ServletContextEvent

               
               

    属性的添加、删除或替代

               
               

    javax.servlet.
    ServletContextAttributeListener

               

    ServletContextAttributeEvent

               
               

    会话
    (见维护客户给状态)

               
               

    创建、失效和超时

               
               

    javax.servlet.http.
    HttpSessionListener

               

    HttpSessionEvent

               
               

    属性的添加、删除或替代

               
               

    javax.servlet.http.
    HttpSessionAttributeListener
     和

               

    HttpSessionBindingEvent

               

    listeners.ContextListener类负责创建和移除在Duke书店应用程序中使用的数据库助手和计数器对象。方法从ServletContextEvent中获取Web上下文对象,进而存储(和移除)作为servlet上下文属性的对象。

    import database.BookDB;
    import javax.servlet.*;
    import util.Counter;

      public final class ContextListener
      implements ServletContextListener {
      private ServletContext context = null;
     public void contextInitialized(ServletContextEvent event) {
        context = event.getServletContext();
        try {
          BookDB bookDB = new BookDB();
          context.setAttribute("bookDB", bookDB);
        } catch (Exception ex) {
          System.out.println(
            "Couldn't create database: "
            + ex.getMessage());
        }
        Counter counter = new Counter();
        context.setAttribute("hitCounter", counter);
        context.log("Created hitCounter"
          + counter.getCounter());
        counter = new Counter();
        context.setAttribute("orderCounter", counter);
        context.log("Created orderCounter"
          + counter.getCounter());
      }

        public void contextDestroyed(ServletContextEvent event) {
        context = event.getServletContext();
        BookDB bookDB = context.getAttribute(
          "bookDB");
        bookDB.remove();
        context.removeAttribute("bookDB");
        context.removeAttribute("hitCounter");
        context.removeAttribute("orderCounter");
      }
    }

    指定事件监听器类    

    为了指定一个事件监听器类,用户要为Web应用部署描述符添加一个listener元素。以下就是Duke书店应用程序的一个listener元素。

    <listener>
      <listener-class>listeners.ContextListener</listener-class>
    </listener>

    处理错误  

    当servlet执行时,可能产生许多异常。而当异常产生时,Web容器将产生一个包含A Servlet Exception Has Occurred消息的缺省页。但是,用户也可返回一个容器,该容器应包含为给定异常指定的错误页。为了指定这样一个页,用户要为Web应用添加部署描述符添加一个error-page元素。这些元素将Duke书店应用程序返回的异常映射到errorpage.html:

    <error-page>
      <exception-type>
        exception.BookNotFoundException
      </exception-type>
      <location>/errorpage.html</location>
    </error-page>
    <error-page>
      <exception-type>
        exception.BooksNotFoundException
      </exception-type>
      <location>/errorpage.html</location>
    </error-page>
    <error-page>
      <exception-type>exception.OrderException</exception-type>
      <location>/errorpage.html</location> </error-page>

    共享信息 

    像大多数对象一样,Web组件通常与其他一些对象协同工作,以完成任务。要做到这一点,可以有多种方法。Web组件可以使用私有的helper(助手)对象(例如,JavaBeans组件),也可以共享那些有公共作用域属性的对象,它们可以使用数据库,还可以调用其他的Web资源。Java Servlet技术机制允许一个Web组件调用其他的Web资源,这在调用其他Web资源中有描述。

    使用作用域对象

    几个协作的Web组件通过一些对象来共享信息,这些对象是作为四个作用域对象的属性来维护的。这些属性可以通过表示域的类的[get|set]Attribute方法访问。表14-3列出了这个作用域对象。

    表14-3 作用域对象

                                                                                                                                                                   
               

    作用域对象

               
               

               
               

    哪些组件可以对其进行访问

               
               

    Web 上下文

               
               

    javax.servlet.
    ServletContext

               
               

    Web上下文中的Web组件。见访问Web上下文

               
               

    会话

               
               

    javax.servlet.
    http.HttpSession

               
               

    处理属于会话的请求的Web组件。见维护客户端状态

               
               

    请求

               
               

    javax.servlet.
    ServletRequest

               

    的子类型

               
               

    处理请求的Web组件。

               
               

               
               

    javax.servlet.
    jsp.PageContext

               
               

    创建对象的JSP页。见隐式对象

               

    图14-1显示了Duke书店应用程序维护的作用域属性。

    Duke's Bookstore Scoped Attributes

    图14-1 Duke书店作用域属性

    控制对共享资源的并发访问

    在多线程的服务器中,可能出现对共享资源的并发访问。除了作用域对象属性外,共享资源还包括存储器中的数据(如实例和类变量)、外部对象(如文件)、数据库连接和网络连接。并发访问可出现在多个情况下。

    ·  多个Web组件访问存储在Web上下文中的对象。t

    ·  多个Web组件访问存储在会话中的对象。

    ·  一个Web组件中的多个线程访问实例变量。一个Web容器一般为每个请求创建一个线程来处理。如果用户确认一个servlet实例每次只处理一个请求,servlet就能实现SingleThreadModel 接口。如果servlet实现了这个接口,用户就能确保servlet的服务方法中不可能有两个线程并发执行。Web容器可通过同步访问一个servlet的单独实例、或者通过维护一个Web组件池为每个实例调用一个新的请求来实现。这个接口并不能防止Web组件访问共享资源(如静态类变量、外部对象)导致的同步问题

    当资源可以并发访问时,使用资源也就可以用不一致的方式。为了防止这样的情况发生,用户必须使用在Java指导中的线程单元中描述的同步机制来控制访问。

    在以前的部分中,我们说明了被多个servlet共享的5个作用域属性: bookDB, cart, currency, hitCounter和orderCounter。bookDB属性将在下一节中讨论。cart, currency和counter可以被多线程的servlet设置和读。使用同步方法来控制访问以防止这些对象的使用不一致。例如,下面是一个util.Counter类:

    public class Counter {
      private int counter;
      public Counter() {
        counter = 0;
      }
      public synchronized int getCounter() {
        return counter;   }
      public synchronized int setCounter(int c) {
        counter = c;     return counter;
      }
      public synchronized int incCounter() {
        return(++counter);
      }
    }

    访问数据库

    在Web组件之间共享,并且在对一个Web应用被调用的间隙内维持的数据通常是由一个数据库来维护的。Web组件使用JDBC 2.0 API来访问关系数据库。书店应用程序的数据由数据库来维护,并通过助手类database.BookDB访问。例如,当用户购买书后,ReceiptServlet调用BookDB.buyBooks方法来更新书的清单。buyBooks方法为每本包含在购物车中的书调用buyBook。为了确保命令被完全执行,buyBook的调用程序将被包装在一个单独的JDBC事务处理中。通过[get|release]Connection方法可以使共享数据库连接同步使用。

    public void buyBooks(ShoppingCart cart) throws OrderException {
      Collection items = cart.getItems();  
     Iterator i = items.iterator();
      try {
        getConnection();
        con.setAutoCommit(false);
        while (i.hasNext()) {
          ShoppingCartItem sci = (ShoppingCartItem)i.next();
          BookDetails bd = (BookDetails)sci.getItem();
          String id = bd.getBookId();
          int quantity = sci.getQuantity();
          buyBook(id, quantity);
        }
        con.commit();
        con.setAutoCommit(true);
        releaseConnection();
      } catch (Exception ex) {
        try {
        con.rollback();
        releaseConnection();
        throw new OrderException("Transaction failed: " +
          ex.getMessage());
        } catch (SQLException sqx) {
          releaseConnection();
          throw new OrderException("Rollback failed: " +
            sqx.getMessage());
        }
      }
    }

    初始化Servlet

    在Web容器加载和实例化servlet类之后、servlet实例传递来自客户端的请求之前,Web容器对servlet进行初始化。用户可以自定义这个初始化过程,以允许servlet读持久的配置数据、初始化资源,并且忽略Servlet接口的init方法以执行任何其它的一次性的活动。servlet必须使用UnavailableException来完成初始化过程。

    所有的访问书店数据库的servlet(BookStoreServlet, CatalogServlet, BookDetailsServlet, 和 ShowCartServlet在它们的init方法中初始化一个变量,指向用Web上下文监听器创建的数据库助手对象。

    public class CatalogServlet extends HttpServlet {
     private BookDB bookDB;
     public void init() throws ServletException {
       bookDB = (BookDB)getServletContext().
         getAttribute("bookDB");
       if (bookDB == null) throw new
         UnavailableException("Couldn't get database.");
     }
    }

    编写服务方法

    servlet提供的服务实现在GenericServlet的service方法、HttpServlet的doMethod方法(在该方法中,Method可以带Get、Delete、Options、Post、Put、Trace的值),或者是任何其他的由实现了Servlet接口的类定义的协议指定(protocol-specific)的方法中。在这一章剩下的部分中,服务方法这个术语将用于在一个向客户端提供服务的servlet类中定义的任何方法。

    服务方法的一般模式是从请求中提取信息、访问外部资源并且基于这些信息填充响应。

    对于HTTPservlet来说,填充响应的正确过程是:首先填充响应头,然后从响应中获取一个输出流,最后编写输出流的所有主体内容。响应头必须在PrintWriter或ServletOutputStream被获取到之前设置好,因为HTTP协议希望获得主体内容前的所有头的信息。下两节将描述如何从请求中获得信息和产生响应。

    从请求中获得信息

    一个请求包含客户端和servlet之间传递的数据。所有请求都实现了ServletRequest接口,该接口为访问一下的信息定义了方法:

    ·  参数,通常用来在客户端和servlet之间传送信息

    ·  对象属性(Object-valued attribute),通常用来在servlet容器与servlet之间或在协作的servlet之间传递信息

    ·  有关协议的信息,用来在请求、客户端和涉及到该请求中的服务器之间的通信。

    ·  有关地区化的信息。

    例如,在CatalogServlet中,顾客希望购买的书的标识符作为参数包含在请求中。下面的这段代码说明了如何使用getParameter方法提取标识符。

    String bookId = request.getParameter("Add");
    if (bookId != null) {
      BookDetails book = bookDB.getBookDetails(bookId);

    用户也可以从请求中获取一个输入流,并对数据进行手工解析。要读字符数据,可以使用由请求的getReader方法返回的 BufferedReader对象来完成。而要读二进制数据,可以使用getInputStream 返回的ServletInputStream

    HTTP serlvet通过HTTP请求对象传递,HttpServletRequest包含了请URL、HTTP头、查询字符串等等。

    一个HTTP请求URL包含以下几部分:

    http://[host]:[port][request path]?[query string]

    请求路径由以下元素组成:

    ·  上下文路径:向前的斜线/和servlet的Web应用的上下文根的拼接。

    ·  servlet路径:与激活该请求的组件别名相应的路径部分,由向前的斜线/开始。

    ·  路径信息:请求路径的部分,不是上下文路径或者servlet路径的部分。

    如果上下文路径是/catalog和表14-4列举出的别名,表14-5给出了一些实例,说明如何分解URL。

    表14-4别名 

                                                               
               

    模式

               
               

    Servlet

               
               

    /lawn/*

               
               

    LawnServlet

               
               

    /*.jsp

               
               

    JSPServlet

               

    14-5 请求路径元素

                                                                                                   
               

    请求路径

               
               

    Servlet 路径

               
               

    路径信息

               
               

    /catalog/lawn/index.html

               
               

    /lawn

               
               

    /index.html

               
               

    /catalog/help/feedback.jsp

               
               

    /help/feedback.jsp

               
               

    null

               

    查询字符串由参数和值的集合组成。每个参数都是从请求中用getParameter方法获取得到的。这里有两种方法产生查询字符串:

    ·  一个查询字符串能在Web页中明确地显示出来。例如,一个HTML页由CatalogServlet产生,该HTML页包含了<a href="/bookstore1/catalog?Add=101">Add _fcksavedurl=""/bookstore1/catalog?Add=101">Add" To Cart</a>。 CatalogServlet 将命名为Add的参数提出,如下:

             String bookId = request.getParameter("Add");

    ·  当一个表单与一个GET HTTP方法一起被提交时, 在URL上附加一个查询字符串。在Duke书店应用程序中,首先CashierServlet产生了一个表单,然后在表单中输入一个用户名,该表单附加在映射到ReceiptServlet的URL上,最后ReceiptServlet使用getParameter方法提取用户名。

     转自: http://gceclub.sun.com.cn/staticcontent/html/webservices/web_services_tutorial/jst.14/14.1.htm           

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

    展开全文
  • servlet简介

    2015-06-29 16:02:40
    对于所有的jsp而言,它都会被转换为servlet来执行,只是这个转换的过程由Tomcat服务器来完成,我们可以通过work目录来查询相应的代码。
  • [Java Servlet]调用 Servlet

    2021-02-12 14:22:32
    要调用 Servlet 或 Web 应用程序,请...1. 由 URL 调用 Servlet这里有两种用 Servlet 的 URL 从浏览器中调用该 Servlet 的方法:(1) 指定 Servlet 名称:当用 WebSphere应用服务器 管理器来将一个 Servlet 实例添...
  • 辅助英文版学习。
  • 使用JSP/Servlet技术开发新闻发布系统

    万次阅读 2017-05-30 18:18:29
    动态网页需要使用服务器端的脚本语言,例如:JSP技术就是使用Java+HTML 动态网页的优势: 1:交互性:  网页会根据用户的要求和选择而动态改变和显示内容 2:自动更新:  无须改变页面代码,便会自动生成新的...
  • 你以为 Servlet 过时了?

    2020-06-13 18:13:46
    Servlet 是规范之一,只是框架在底层帮我们实现了 Servlet,直观上感觉不到 Servlet 的存在,并不是说已经过时了,Servlet 在今后的长时间内还会被广泛使用。
  • 目前流行框架的一些替代技术

    千次阅读 2016-11-14 09:16:34
    1. jsp、servlet、javabean的替代技术:  jsp和servlet完全同意,底层运行机制完全一致,jsp必须被web服务器编译成servlet,在服务器真正运行的事servlet。jsp充当表现层技术的替换是:freeMark或者velocity。 2....
  • servlet知识点小结

    2013-09-27 16:13:02
    1、serialVersionUID作用, 一个是默认的1L,比如:private static final long serialVersionUID = 1L; 2、Servlet中service()方法 3、servlet中的doGet和doPost的不同 4、……
  • servlet处理表单数据 表单数据的作用 form action="...">form> 如果省略action,那么数据将提交给当前页面对应的URL 在servlet中读取表单数据 1、HttpServletRequest.getParameter方法可以得到表单...
  • servlet

    2019-03-31 16:08:00
    servlet Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。 狭义的...
  • Servlet

    2020-09-04 22:23:18
    是 SUN 公司提供的一门用于开发动态 Web 资源的技术。(言外之意:要实现 web 开发,需要实现 Servlet 标准)Servlet 本质上也是 Java 类,但要遵循 Servlet 规范进行编写,没有 main()方法,它
  • JavaEE之servlet相关技术

    2018-09-18 09:42:00
    相关技术:为了灵活实现的不同路径(/hello)执行不同的资源( HeIIoMyServlet)我们需要使用XML进行配置;为了限定XML内容,我们需要使用xml约束(DTD或schema);为了获得xml的内容,我们需要使用dom4j进行解析。 XML:...
  • 适合初期的学习,最初的笔记整理
  • java jsp servlet struts hibernate spring 实用技术
  • web服务器三大组件servlet、Filter、Listene——浅浅笔记
  • 在Spring Boot2.0以上配置嵌入式Servlet容器时EmbeddedServletContainerCustomizer类不存在,经网络查询发现被WebServerFactoryCustomizer替代,原文链接:https://segmentfault.com/a/1190000014610478 。...
  • Java程序开发教学课件:15-Servlet高级编程.ppt
  • java web之Servlet技术

    千次阅读 2015-08-24 19:56:36
    本文针对Web项目结构,Servlet基本概念,对Servlet体系结构,通过实例讲解Servletservlet配置,servlet上下文,监听器,过滤器等技术
  • JSP与Servlet的区别

    2020-10-29 23:03:43
    JSP与Servlet的区别 一、最重要的一句话!! jsp就是在html里面写java代码,servlet就是在java里面写html代码…其实jsp经过容器解释之后就是servlet.只是我们自己写代码的时候尽量能让它们各司其职,jsp更注重前端...
  • 使用Servlet+JSP实现学生管理系统

    千次阅读 2021-08-21 15:18:18
    文章目录使用Servlet+JSP实现学生管理系统eg1: 使用servlet来实现登录功能eg2: 使用servlet来实现登录功能1.创建数据库表 mysql2.创建web project3. 在web目录下创建登录页面 login.html4.编写实体类User5.编写数据...
  • Servlet 3.1规范

    千次阅读 2019-06-29 18:31:18
    目录概览1.1 什么是Servlet1.2 什么是Servlet容器1.3 例子1.4 Servlet与其他技术比较1.5 与Java平台企业版的关系1.6 与Java Servlet规范2.5版本间兼容性1.6.1 监听器(Listener)顺序1.6.1 注解处理Servlet接口2.1 ...
  • 声明:本文基于Servlet3.1规范翻译整理,如有谬误,烦请指正。 JSR 340,原文请查阅 ...Servlet 是基于Java技术的Web组件 Servlet 用于生成动态内容。 动态: 数据动态 .
  • JavaWeb-Servlet

    2018-09-05 09:34:20
    好吧,这么说你可能还是不太懂,简单的讲,这个servlet是运行在服务器上的一个小程序,用来处理服务器请求的。进一步讲,我们知道,一般的网页程序,是由我们通过浏览器访问来实现的,在这个过程中,我们的浏览器...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,289
精华内容 11,315
关键字:

servlet替代技术