第十讲 JSP和Servlet

10.1 Servlet的概念、配置与运行

 10.1.1 Java Servlet的概念

  Java Servlet是一个专门用于编写网络服务器应用程序的Java组件。所有基于Java的服务器端编程都是构建在Servlet之上的。

  在J2EE中Servlet已经是一个标准的组件。让我们来认识一下,Servlet在Java的软件包中是怎样的一个结构,这会有助于我们理解Servlet的概念。

  在J2EE中跟Servlet相关的一个包是javax.servlet,其中最基本的Servlet被声明为一个接口javax.servlet: Interface Servlet,这是Servlet最高层次的一个抽象,它是和网络协议无关的。同样在javax.servlet中,实现了一个类 javax.servlet: class GenericServlet,这个类实现了Servlet接口,也是和协议无关的。而这个类是构建其他和协议相关的Servlet子类型的通用的父类(至少HttpServlet是从它继承而来的,从它的名字也能看出这一点)。

  也就是说Servlet所适用的网络协议可以是多种多样的,比如HTTP,FTP,SMTP,TELNET等,但是就目前而言,只有HTTP服务已经形成了标准的Java组件。对应的软件包有两个javax.servlet.http和javax.servlet.jsp,分别对应我们要讲解的Servlet和JSP编程。我们通常所说的Servlet编程主要就是指针对HTTP的Servlet编程,用到的就是javax.servlet.http包中的类(典型的就是HttpServlet类),实际上Java Servlet编程的概念要更广一些,在这里我们也就约定俗成的使用Servlet来指代HTTP Servlet的编程,这点读者是需要了解的。由于JSP最终都是要经过JSP引擎转换成Servlet代码的,而且Servlet编程和一般的Java编程是没有大的区别的,只需要了解一定的规范即可,所以我们在这里先讲解Servlet的编程,这样对以后理解JSP是很大的有好处的,尽管在使用的时候可能JSP更为简单一些。

  目前,Servlet引擎一般是第三方的插件,它通过一定的方法连接到Web服务器,Servlet引擎把它识别为Servlet请求的那些HTTP请求截获下来处理,而其他的HTTP请求由Web服务器按照通常的方式来处理,Servlet引擎会装载合适的Servlet到内存中,如果Servlet还没有运行的话,会分配一个可以使用的线程来处理请求,再把Servlet的输出返回到发出请求的Web客户机。

  Java Servlet和Java Applet正好是相对应的两种程序类型,Applet运行在客户端,在浏览器内执行,而Servlet在服务器内部运行,通过客户端提交的请求启动运行,读者在学习过程可以作简单的比较。
  
  使用过CGI的读者一定知道CGI程序的作用,Servlet要实现的功能和CGI是一样的,只是实现的时候更为方便,效率更高。如果读者对以上概念不是很清楚,也不必着急,通过学习以下的内容,有了感性的认识,再回来看看,一定会有更大的收获。

 

10.1.2 Servlet的优点和应用范围

  由于Servlet是用Java编写的,所以它与生俱来就有跨平台的特性,因此Servlet程序的设计完全和平台是无关的,同样的Servlet完全可以在Apache,IIS等不同Web服务器上执行,不管底层的操作系统是Windows,Solaris,Mac,Linux还是其他的能支持Java的操作系统。

  Servlet是跟普通的Java程序一样,是被编译成字节码后由JVM执行的。相比传统的CGI,尽管CGI是用本地代码直接执行的,但是由于每次客户端发出请求,服务器必须启动一个新的程序来处理请求,这就把高负载强加给了服务器资源,尤其如果CGI使用脚本语言编写时,如perl,服务器还必须启动语言解释程序,程序越多,占用的内存就越多,消耗CPU也越多,严重影响系统性能。

  Servlet运行于Servlet引擎管理的Java虚拟机中,被来自客户机的请求所唤醒,与CGI不同的是,在虚拟机中只要装载一个Servlet就能够处理新的请求,每个新请求使用内存中那个Servlet的相同副本,所以效率比CGI来得高。如果采用服务器端脚本,如ASP,PHP,语言解释程序是内置程序,因此可以加快服务器的运行,但是效率还是比不上准编译的Servlet。实际的使用也已经证明,Servlet是效率很高的服务器端程序,很适合用来开发Web服务器应用程序。

  Java Servlet有着十分广泛的应用。不光能简单的处理客户端的请求,借助Java的强大的功能,使用Servlet还可以实现大量的服务器端的管理维护功能,以及各种特殊的任务,比如,并发处理多个请求,转送请求,代理等。

  在实际的使用中,读者会有更多的机会去学习了解Servlet的最新应用,在这里我们只介绍基本的应用。

10.1.3 Servlet的运行环境
  为了运行Servlet,首先当然需要一个JVM来提供对Java的基本支持,一般需要安装JRE(Java Runtime Environment)或JDK(Java Develop Kit,JRE是其一个子集)。

  其次我们需要Servlet API的支持,一般的Servlet引擎都自带Servlet API,只要我们安装Servlet引擎,或直接支持Servlet的Web服务器之后便会自动安装上Servlet相关的程序包。

  典型的Servlet运行环境有JSWDK,Tomcat,Resin等,这几个都是免费的软件,适合用来学习Servlet和JSP。它们都自带一个简单的HTTP Server,只需简单配置即可投入使用,你也可以把它们绑定到常用的Web服务器上,如Apache,IIS等,提供小规模的Web服务。还有一些商业的大中型的支持Servlet和JSP的Web服务器,如JRun,Web Sphere,Web Logic等等,配置比较复杂,并不适合初学者。但是功能较为强大,有条件的读者可以一试。后面我们会讲解如何配置一简单的支持Servlet和JSP的Web服务器。

10.1.4 Servlet与CGI环境变量

  在使用CGI时,最重要的内容是CGI接口的环境变量。CGI规范列出了19个环境变量。尽管其他的环境变量,如HTTP_COOKIE(用于查询站点信息的)不是该规范的组成部分,但也是经常使用。

  由于Java运行于JVM,不直接在服务器上运行,所以不能直接访问环境变量。由于CGI环境变量是Web服务器建立的,而且用户能够用其他的方法查询变量值,所以Java不访问环境变量没有什么大问题。Java Servlet API定义几种查询在CGI环境变量中发现大多数信息的方法。

  有些信息是HTTP标题的组成部分,而且采用HttpServletRequest类中的getHeader()方法能够很容易的获取。采用特殊的方法可以查询其他的信息。Java唯一不可用的CGI环境变量是GATEWAY_INTERFACE。当在CGI程序中使用时,该变量包含CGI版本。在Java Servlet中这种变量信息是无关紧要的。

  以上的内容是针对有CGI编程经验的读者的,通过对比能帮助读者更快的理解Servlet。对CGI一无所知的读者浏览一下便可。
10.1.5 Servlet的安全性

  Java Servlet能够使用包括SSL在内的安全协议。Servlet与Java内在的安全措施紧密相连,如不能直接访问内存等。采用安全管理器,用户能够限定对其他资源的访问,如文件、目录和局域网。Java Servlet支持代码符号,从而能够更好的控制委托每个Servlet要执行的程序。

  Java的安全机制本身是比较复杂的,在这里我们只能作简单讲解,否则就喧宾夺主了。具体的分析参看有关Java安全性的章节。

10.1.6 Servlet的管理

  对于大多数用户来说,Servlet比CGI程序和服务器脚本更容易管理。Servlet是以Java类的形式编译的。Java采用能够保存在Java Archive(.JAR)文件中的编制目录树内的组件,提供一种管理类的方法,如Sun公司的Java Web服务器的Servlet管理平台。有些服务器和第三方产品为管理Servlet提供图形用户界面。

  具体的Servlet的管理是比较复杂的一件事情,尤其是对于一个大系统而言。对于初学者只要能掌握基本的配置一个Servlet,使其能正常运行的技能就可以了。更高级的技巧要在更多的实践中才能真正掌握。
 

10.1.7 Servlet的编译

  Servlet的编译和一般的Java程序是完全一样的,在使用javac编译的时候不需要任何特殊的参数。只要Servlet的编写是正确的,编译完后生成的Class文件就可以做为Servlet来运行了。

10.1.8 用Servlet Runner运行Servlet

  在真正开始编写Servlet之前,我们先介绍一个简单的Servlet引擎–Resin。目前支持Servlet的Web服务器不下数十种,Resin是一个简单易用的Servlet运行器(Servlet Runner),很适合初学者。由于各个厂家的Servlet引擎各不相同,配置方法也是千差万别,在这里不可能一概而论,但是Servlet的编写方法却是一样的,所以读者不必太在意服务器的配置方法,只要知道如何让自己的Servlet正常运行就可以了,把更多的注意力放在Servlet的编写上。

  Resin自带一个Servlet Runner和HTTP Server,因此要构建一个简单的Web环境,光有Resin已经足够了,不需要额外的支持软件。Resin不需要安装,解压之后即可使用。

  Resin目录下有几个子目录,bin目录存放的是可执行文件,要启动HTTP Server和Servlet Runner只需要分别点击其中的httpd.exe和srun.exe即可,启动后会出现四个窗口,分别对应HTTP Server的标准输出,启/停控制和Servlet Runner的标准输出,启/停控制。conf目录下存放的是Resin Servlet Runner的配置文件,这是配置整个Web环境的关键,包括Servlet的配置和后面要用到的JSP的配置。doc目录是默认的发布目录,即Resin自带的HTTP Server是以这个目录为根目录的。

  下面我们以一个最简单的HelloWorld的例子,来讲解如何配置Resin,使其能运行Servlet程序,同时也做为Servlet编写的入门。程序如下:
   //HelloServlet.java
   import java.io.*;
   import java.util.*;
   import javax.servlet.http.*;
   import javax.servlet.*;
   //导入必要的包
   public class HelloServlet extends HttpServlet {
          //所有Servlet必须从HttpServlet派生
     public void doGet (HttpServletRequest req, HttpServletResponse res)
     throws ServletException, IOException
     //doGet()是这个Servlet的核心,真正处理请求的地方
   {
   res.setContentType(“text/html”);
     //设置相应的类型为text/html
   PrintWriter pw = res.getWriter();
     //从HttpServletResponse得到输出流

   pw.println(“”);
   pw.println(“”);
   pw.println(“”);
   pw.println(“”);
   pw.println(“”);
   pw.println(“”);
   pw.println(“”);
   pw.println(”

Hello, world!
“);
   pw.println(“”);
   //上面的语句都是向客户端打印HTML文本
   pw.close();
   //关闭HttpServletResponse,使Web服务器知道相应结束
  }
  public HelloServlet() {} //构造函数,可以不要
 }

  这是最简单的一个Servlet程序,整个类从HttpServlet派生,就跟Applet一样,这个派生关系是必须的。这个Servlet必须实现doGet()方法(因为它是作为静态页面通过地址访问的,这种方式在HTTP中称为GET请求,在后面还会有更具体的讲解),这是这个Servlet真正处理请求的地方,是整个Servlet的主体,就跟线程体的run()方法一样。doGet()有两个参数HttpServletRequest req和 HttpServletResponse res。HttpServletRequest包含了客户请求的各种信息,HttpServletResponse则包装了服务器响应,主要处理对客户机的输出。这个程序是很简单的,如果对网络编程和HTML有一些概念的话,很容易就应该能理解。

  通过javac对以上程序进行编译,我们可以得到HelloServlet.class,下面我们就通过配置Resin来运行这个Servlet。前面说过如果你没有修改过Resin的配置文件的话,doc是Resin默认的发布目录。发布目录下的WEB_INF/classes是Resin默认的Classpath,读者只要把自己的Servlet拷贝到该目录下,Resin就能识别,当然只要拷贝的有效的系统的或用户的Classpath下,Resin都是能够找到的。我们把HelloServlet.class拷贝到WEB_INF/classes目录下。接下来就需要修改conf目录下的resin.conf文件,来配置我们的Servlet。读者可以使用任何一种自己所熟悉的文本编辑器来打开该配置文件。找到,在它和 之间任何地方添加以下的配置语句(可以参考已有的语句)
   
       servlet-class=’HelloServlet’ >
   

  这样当客户端产生/Hello请求的时候,Resin就能把这个请求定向到HelloServlet上,该Servlet就能正常运行了,我们也可以采用以下的配置语句
   

  这种情况下,没有对Servlet的名字进行映射,系统会默认的使用servlet-name作为servlet-class进行类的查找,所以servlet-name必须填写正确的类名,而不能是随意的名字。关于Resin更多的配置信息,有兴趣的读者可以参考Resin的帮助文档,这里就不再赘述了。

  下面我们启动Http Server和Servlet Runner(通过双击bin下的httpd.exe和srun.exe)。这样我们就可以通过浏览器访问这个Servlet了。打开浏览器,比如IE,在地址栏键入http://localhost:8080/Hello,我们可以打开如下的一个网页:

 通过查看源码,我们可以得到如下的结果
  
  
  
  
  
  
  
  
Hello, world!

  

  很显然这些文本正是我们在Servlet中向客户端所打印的信息,在Http头部content=text/html也是我们在程序中所设置的。

  通过这样一个简单的HelloWorld的程序,读者对Servlet的工作原理的基本配置方法应该有了一个大概的了解,如果读者对这个例子还有疑问,务必搞清楚后再继续学习。
 

 

10.2 Servlet的应用实例

  上面我们已经讲解了Servlet的基本概念,并介绍了一个运行环境及其配置方法,下面我们就开始讲解Servlet在编写Web应用方面的具体应用。

 10.2.1 Servlet与表单交互的方法

  表单是HTML中使用最广泛的传递信息的手段。搞清楚Servlet与表单的交互,就在客户端与服务器之间架起了一座桥梁。Servlet使用HttpServlet类中的方法与表单进行交互。在HttpServlet类中有几个未完全实现的方法,你可以自己定义方法的内容,但是必须正确使用方法名称以使HTTP Server把客户请求正确的映射到相应的函数上。

  doHeader 用于处理HEADER请求
  doGet 用于处理GET请求,也可以自动的支持HEADER请求
  doPost 用于处理POST请求
  doPut 用于处理PUT请求
  doDelete 用于处理DELETE请求
  HttpServlet的Service方法,当它接收到一个OPTIONS请求时,它会自动调用doOptions方法,当接收到一个TRACE请求时调用doTrace。DoOptions默认执行方式是自动决定什么样的HTTP被选择并返回哪个信息。

  在使用这些方法时必须带两个参数。第一个包含来自客户端的数据HttpServletRequest。第二个参数包含客户端的相应HttpServletResponse。在我们的第一个例子中使用的是doGet方法,因为通过地址访问的话,对应的方式是GET。

  一个HttpServletRequest对象提供请求HTTP头部数据,也允许获取客户端的数据。怎样获取这些数据取决于HTTP请求方法。

  不管何种HTTP方式,都可以用getParameterValues方法返回特定名称的参数值。

  对于HTTP GET请求的方式,getQueryString方法将会返回一个可以用来解剖分析的参数值。

  对于用HTTP POST,PUT和DELETE请求的方式,HttpServletRequest有两种方法可以选择:如果是文本数据,你能通过getReader的方法得到BufferedReader获取数据;如果是二进制数据,可以通过getInputStream方法得到ServletInputStream获取数据。

  为了相应客户端,一个HttpServletResponse对象提供返回数据给用户的两个方法:一种是用getWriter方法得到一个PrintWriter,用于返回文本数据;另一种方法是用getOutputStream方法得到ServletOutputStream,用于返回二进制数据。在使用Writer或OutputStream之前应先设置头部(HttpServletResponse中有相应的方法),然后用Writer或OutputStream将相应的主体部分发给用户。完成后要关闭Writer或OutputStream以便让服务器知道相应已经结束。

  下面我们举一个使用HttpServletRequest和HttpServletResponse得到并打印客户端信息的例子:

  //RequestInfo.java
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;

  public class RequestInfo extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException
               //处理GET请求的方法
    { 
      response.setContentType(“text/html”);
      //先设置Header,在这里只设置ContentType一项
      PrintWriter out = response.getWriter();
      //得到文本输出Writer

      //下面打印相关的HTML
      out.println(“”);
      out.println(“”);
      out.println(“”);
      out.println(“”);
      out.println(“”);
      out.println(”

Request Information Example
“);

      out.println(“Request URI: ” + request.getRequestURI()+”
“);
      //打印请求的路径
      out.println(“Protocol: ” + request.getProtocol()+”
“);
      //打印协议名称
      out.println(“PathInfo: ” + request.getPathInfo()+”
“);
      //打印额外的路径信息
      out.println(“Remote Address: ” + request.getRemoteAddr());
      //打印客户机的地址,如果没有打印IP地址
      out.println(“”);
      out.println(“”);
      out.close(); //关闭Writer
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
     //如果是POST请求类型,同样调用GET类型的响应函数
     doGet(request, response);
    }
  }

  用我们在前面介绍的方法在Resin中配置使其运行,我们得到的结果如下:

 

  这样的一个例子很好的说明了所谓的动态网页和静态网页的区别,就上面这个例子而言,每个客户看到的内容是不一样的,而静态网页则对每一个客户而言都是一成不变的。

 

10.2.2 Servlet与表单交互的例子

  上面我们介绍了Servlet如何与表单进行交互,并提供了一个从HTTP请求头部得到客户端信息的例子,下面我们给出一个Servlet与HTTP提交的表单进行交互的例子,通过这个例子读者应该能对整个客户端和服务器端交互的过程有一个整体的了解。这个例子分成两个文件,一个是静态的HTML文件,提供一个表单,并设置提交按钮,表单被提交后,服务器会把它定向到另一个文件,也就是我们的Servlet,由它读取表单并打印到客户端。

  1. 静态HTML文本:ourform.html

  2. FormDemo.java

 1. 静态HTML文本:ourform.html

  
  
  
  
  
  

Our Form

  

  
     
        姓名:
        
    

     
        性别:
                男        女      
    

  
  

  
  

  在IE下显示效果如图所示:
 

  不熟悉HTML的读者可以参考有关HTML的书籍,尤其要注意的form的两个属性method和action

 2. FormDemo.java

  //FormDemo.java
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;

  public class FormDemo extends HttpServlet {
   public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException //处理GET请求的方法
   {
    response.setContentType(“text/html”);
    //先设置Header,在这里只设置ContentType一项
    PrintWriter out = response.getWriter();
    //得到文本输出Writer

    String name = request.getParameter(“Name”);
    //得到表单值Name
    String sex = request.getParameter(“Sex”);
    //得到表单值Sex
    name = new String(name.getBytes(),”ISO-8859-1″);
    //转换到正确的编码

    //打印得到的表单值
    out.println(“”);
    out.println(“”);
    out.println(“”);
    out.println(“”);
    out.println(“”);
    out.println(“”);
    out.println(”

Data You Posted
“);
    out.println(” “);
    out.println(”  “);
    out.println(new String(new String(”  你的姓名: “).getBytes(),”ISO-8859-1”));
    out.println(”  “+name+” “);
    out.println(”
“);
    out.println(”  “);
    out.println(new String(new String(”  你的性别: “).getBytes(),”ISO-8859-1”));
    out.print(”  “);
    if(sex.equals(“1”)) out.println(new String(new String(“男 “).getBytes(),”ISO-8859-1”));
    else out.println(new String(new String(“女”).getBytes(),”ISO-8859-1″));
    out.println(”
“);
    out.println(” “);
    out.println(“”);
    out.println(“”);
    out.close(); //关闭Writer
   }
  }

  这个Servlet也是比较简单的,首先从提交的表单中得到需要的两个值,然后用HTML向客户端打印这些信息。
  
  值得注意的是,在这个例子中,所有出现打印中文的地方,我们都使用了字符编码的转换来正确打印中文。前面的例子我们都没有涉及中文,在这里我们有必要提一下Servlet的中文问题。我们知道在同一台机器上,所有的编码方式都是一样的,一般中文平台是gb2312,英文平台是ISO-8859-1,但是网络上的两台机器并不能保证他们的编码方式都是一样的,这时候就有可能出现乱码的问题。在进行HTTP网络传输的时候,统一采用的编码方式是ISO-8859-1,这时候如果还是按照本地编码来传输就会出现问题,这也是Servlet在实现网络传输的时候一个不完美的地方,它不会自动进行本地编码到ISO-8859-1的转换,所以直接打印的话就会出现乱码。原理上讲任何出现打印字符串的地方,都是需要进行编码转换的,但是西文字符在不同字符集下对应相同的编码,以在打印西文字符的时候就不需要转换了。在Servlet后继的规范中可能会改变这种麻烦的状况。不同的是,从网络提交的表单数据,Servlet是自动把它转换成本地编码的,所以程序中得到的name字符串变量是gb2312编码的,同样需要进行转换后才能在客户端正确打印。

  字符编码转换常用的方法是
  String native_encoded = “中文字符串”;
            //本地编码的字符串
  Byte[] byte_array = native_encoded.getBytes();
            //得到本地编码的字节数组
  String net_encoded = new String(native_encoded, “ISO-8859-1”);
            //生成ISO-8859-1编码的字符串

  这样得到的net_encoded字符串就可以用来向客户端打印而不出错了。
  还有一点要注意的是,为了在客户端正常显示中文,必须在HTML头部设置charset=gb2312。

  当我们按下提交按钮后,我们得到的结果如下:
 

  如果我们把ourform.html中的method由GET改成POST,会有什么样的结果呢?由于我们没有重写doPost方法,所以在Resin下,我们得到如下的结果:
 

  有兴趣的读者可以修改FormDemo类来响应POST方法。

  通过以上的几个例子,读者对Servlet如何响应HTTP请求,并从提交的表单中获取数据应该有了一个大概的了解,但是要构建Web应用程序,光有这几点是不够的,下面我们要讲解的Servlet的会话和生命周期对于构建Web应用是及其重要的两个特性。

10.2.3 用Servlet控制会话

  会话状态的维持是开发Web应用所必须面对的问题,有多种方法可以来解决这个问题,如使用Cookies,hidden类型的表单域,或直接把状态信息加到URL中等,还有Servlet本身提供了一个HttpSession接口来支持会话状态的维持,在这里我们主要介绍基于这个接口的会话状态的管理。

  Session的发明是为了填补HTTP协议的局限。请注意HTTP协议是如何工作的–用户发出请求,服务器作出响应,这种用户端和服务器端的联系就是离散的,非连续的。HTTP协议不能提供允许服务器跟踪用户请求的功能。在服务器端完成响应用户的请求之后,服务器不能继续与该浏览器继续保持连接。从服务器这端来看,每一个请求都是独立的,因此HTTP协议被认为是无状态协议,当用户在多个主页间切换时,服务器无法知道他的身份。Session的出现就是为了弥补这个局限。利用Session,您就可以当一个用户在多个主页间切换的时候也能保存他的信息。这样很多以前根本无法去做的事情就变得简单多了。

  在访问者从到达某个特定的主页到离开为止的那段时间,每个访问者都会单独获得一个Session。
  Java Servlet定义了一个HttpSession接口,实现的Session的功能,在Servlet中使用Session的过程如下:
  
  (1) 使用HttpServletRequest的getSession方法得到当前存在的session,如果当前没有定义session,则创建一个新的session,还可以使用方法getSession(true)

  (2) 写session变量。可以使用方法HttpSession.setAttribute(name,value)来向Session中存储一个信息。也可以使用HttpSession.putValue(name,value),但这个方法已经过时了。

  (3) 读Session变量。可以使用方法HttpSession.getAttribute(name)来读取Session中的一个变量值,如果name是一个没有定义的变量,那么返回的是null。需要注意的是,从getAttribute读出的变量类型是Object,必须使用强制类型转换,比如:
     String uid = (String) session.getAttribute(“uid”);
  也可以使用HttpSession.getValue(name),但是这个方法也已经过时了。

  (4) 关闭session,当时用完session后,可以使用session.invalidate()方法关闭session。但是这并不是严格要求的。因为,Servlet引擎在一段时间之后,自动关闭seesion。

  下面举一个简单的例子说明session的使用
  // SessionExample.java
  import java.io.*;
  import java.util.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  //导入必要的软件包
  public class SessionExample extends HttpServlet
  {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException //实现doGet方法
    {
      response.setContentType(“text/html”); //设置HTTP头
      PrintWriter out = response.getWriter(); //得到输出Writer
      HttpSession session = request.getSession(true);
                          //得到session对象

      //打印HTML标记
      out.println(“”);
      out.println(“”);
      out.println(“”);
      out.println(“”);
      out.println(“”);

      Date created = new Date(session.getCreationTime());
                  //得到session对象创建的时间
      Date accessed = new Date(session.getLastAccessedTime());
                  //得到最后访问该session对象的时间
      out.println(“ID ” + session.getId()+”
“);
                  //得到该session的id,并打印
      out.println(“Created: ” + created+”
“);
                  //打印session创建时间
      out.println(“Last Accessed: ” + accessed+”
“);
                  //打印最后访问时间

      session.setAttribute(“UID”,”12345678″);
                  //在session中添加变量UID=12345678
      session.setAttribute(“Name”,”Tom”);
                  //在session中添加变量Name=Tom

      Enumeration e = session.getAttributeNames();
                  //得到session中变量名的枚举对象
      while (e.hasMoreElements()) { //遍历每一个变量
        String name = (String)e.nextElement(); //首先得到名字
        String value = session.getAttribute(name).toString();
                  //由名字从session中得到值
        out.println(name + ” = ” + value+”
“); //打印
      }
      out.println(“”); //打印HTML标记
      out.println(“”);
      }
    }
  }

  该Servlet运行的结果是:
 

  有了Session对象,Web应用程序就可以在服务器端保存客户的状态,这对构建Web应用的重要性读者在以后的实践中会逐步有所体会。正像前面提到的,Session只是一个概念,可以有多种实现方法,在这里我们就不过多的介绍了。因为Servlet本身就是实用性很强的内容,各种各样的技巧,实现方案,不胜枚举。而且很多相关内容也不只是Servlet的专利,本讲在这里只能做简单的介绍,使读者对Servlet有一个概念上的认识。有兴趣的读者可以参考相关书籍。

10.2.4 Servlet的生命周期

  跟客户端的Applet相似,Servlet(这里Servlet的概念又回到了最原始的含义)也遵循严格的生命周期。在每个Servlet实例的生命中有三种类型的事件,这三种事件分别对应于由Servlet引擎所唤醒的三个方法:

  1.init()。当Servlet第一次被装载时,Servlet引擎调用这个Servlet的init()方法,只调用一次。如果某个Sevlet需要特殊的初始化需要。那么Servlet编写人员可以重写该方法来执行初始化任务。这是一个可选的方法。如果某个Servlet不需要初始化,那么默认情况下将调用它父类的init方法。系统保证,在init方法成功完成以前,是不会调用Servlet去处理任何请求的。

  2.service()。这是Servlet最重要的方法,是真正处理请求的地方。对于每个请求,Servlet引擎将调用Servlet的service方法,并把Servlet请求对象和Servlet响应对象最为参数传递给它。

  3.destroy()。这是相对于init的可选方法,当Servlet即将被卸载时由Servlet引擎来调用,这个方法用来清除并释放在init方法中所分配的资源。
  
  Servlet的生命周期可以被归纳为以下几步:
  (1) 装载Servlet,这一项操作一般是动态执行的。然而,Servlet通常会提供一个管理的选项,用于在Servlet启动时强制装载和初始化特定的Servlet
  (2) Server创建一个Servlet实例
  (3) Server调用Servlet的init方法
  (4) 一个客户端请求到达Server
  (5) Server创建一个请求对象
  (6) Server创建一个响应对象
  (7) Server激活Servlet的service方法,传递请求和响应对象作为参数
  (8) service方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息
  (9) service方法使用响应对象的方法。将响应传回Server,最终到达客户端。Service方法可能激活其他方法以处理请求。如doGet,doPost或其他程序员自己开发的方法
  (10) 对于更多的客户端请求,Server创建新的请求和响应对象,仍然激活此servlet的service方法,将这两个对象作为参数传递给它,如此重复以上的循环,但无需再次调用init方法,Servlet一般只初始化一次
  (11) 当Server不再需要Servlet时,比如当Server要关闭时,Server调用Servlet的destroy

  至此关于Servlet的内容已经讲解完毕,读者通过对比Servlet与传统静态网页的区别,应该能大概理解Web应用程序的概念,Web应用的出现,使得呆板的Web页面变得生动,具有交互能力。Java在Web应用方面算不上独树一帜,但是却是做的相当优秀的。通过Servlet与下面我们要讲解的JSP的配合,可以方便的构建出功能强大的Web应用。

  学习Servlet,其实最主要的已经不是Java本身,大家也已经看到Servlet和一般Java程序是没有什么区别的。要学好Servlet,除了掌握基本Servlet API之外,最关键的还在于如何从整体上把握整个Web应用,如何合理的使用Servlet,使Servlet在构建Web网站的时候起到画龙点睛的作用,而不是复杂冗余的重复性劳动。在真正编写Servlet的时候,一般是没有什么大的困难的。所以读者在学习之余,最好能找一些规模大一点的例子,通过阅读代码,分析结构,更好的掌握Servlet的使用方法,知道什么时候该用Servlet实现,什么时候应该用其他的方法实现更为合适,而不必深究Servlet的语法或其他的一些小技巧。对于那些对Web网站不熟悉的读者,在学习的时候可能有困难,那么我们的建议是,先去学习基本的Web网站的知识。可以这么说Servlet只是一个工具,思想是需要读者在实践中慢慢体会的,当你有了成熟的思想之后再来学习Servlet,一定会有驾轻就熟的感觉的。

10.3 JSP简介

 10.3.1 JSP的概念

  JSP(Java Server Pages?)是Sun Microsystems公司倡导、许多公司参与一起建立的一种动态网页技术标准。它在HTML代码中,插入JSP标记(tag)及Java程序片段 (Scriptlet), 构成JSP页面,其扩展名为 .jsp。当客户端请求JSP文件时,Web服务器执行该JSP文件,然后以HTML的格式返回给客户。前面已经提到过JSP只是构建在Servlet之上的高层次的动态网页标准,因此,从概念上将,相对Servlet而言,JSP并没有什么新的东西,如果读者对前面的Servlet已经十分的了解,那么JSP的概念可说跟Servlet是完全一样的,只不过在实现方法上稍有不同。
  
  目前JSP,ASP是使用最为广泛的两种服务器端脚本语言,如果读者对ASP已经有所了解的话,那么对于JSP应该也是很容易理解的,从概念上讲,JSP和ASP是一样的,只不过使用的语言不同而已。
10.3.2 JSP的优点

  1. 一次编译,多次、多处运行,代码的执行效率高
  JSP的脚本语言是JAVA语言,因此它具有JAVA语言的一切特性。同时,JSP也支持现在大部分平台。 当JSP第一次被请求时,JSP页面转换成servlet,然后被编译成.calss文件,以后(除非页面有改动或Web服务器被重新启动)再有客户请求该JSP页面时,JSP页面不被重新编译,而是直接执行已编译好的.class文件,因此执行效率特别高。

  2. 组件的重用性
  可重用的、跨平台的JavaBeans和EJB(Enterprise JavaBeans)组件,为JSP程序的开发提供方便,我们可以将复杂的处理程序(如页面中需要显示的动态内容及对数据库的操作)放到组件中。可以多次使用这些组件,极大的减少了在JSP页面中重写重复的代码。

  3. 将内容的生成和显示进行分离
  使用JSP技术,Web页面开发人员可以使用HTML或者XML标识来设计和格式化最终页面。使用JSP标识或者脚本来生成页面上的动态内容。生成动态内容的语句一般被封装在JavaBean组件、EJB组件或JSP脚本段中。这样,页面的设计人员和页面的编程人员可以同步进行。同时在客户端通过查看源文件,看不到JSP标识的语句,更看不到JavaBean和EJB组件,也可以保护源程序的代码。
 

 

 

10.3.3 JSP的运行方式

  JSP一般的运行方式为:当服务器启动后,当Web浏览器端发送过来一个页面请求时,Web服务器先判断是否是JSP页面请求。如果该页面只是一般的HTML/XML页面请求,则直接将HTML/XML页面代码传给Web浏览器端。如果请求的页面是JSP页面,则由JSP引擎检查该JSP页面,如果该页面是第一次被请求、或不是第一次被请求但已被修改,则JSP引擎将此JSP页面代码转换成Servlet代码,然后JSP引擎调用服务器端的Java编译器javac.exe对Servlet代码进行编译,把它变成字节码(.class)文件,然后再调用JAVA虚拟机执行该字节码文件,然后将执行结果传给Web浏览器端。如果该JSP页面不是第一次被请求,且没有被修改过,则直接由JSP引擎调用JAVA虚拟机执行已编译过的字节码.class文件,然后将结果传送Web浏览器端。下面是一张JSP运行的示意图:
 

  从这里我们已经不难看出JSP和Servlet的关系,JSP引擎负责把JSP页面翻译成Servlet,因此JSP在底层完全就是Servlet(指原始概念上的Servlet,而不是HttpServlet)。前面我们提到JSP编程对应javax.servlet.jsp,更确切的讲,这个包是供JSP引擎使用的,它在做翻译的时候需要用到这个包,我们在编写JSP页面的时候是不需要涉及这个包的使用的。

  为什么有了Servlet还要在高层实现一个JSP呢?这个问题跟Servlet本身编写的烦杂程度有关,如果用Servlet来控制页面外观的话,将是一件十分头疼的事情,使用JSP就把烦杂的打印任务交给了JSP引擎,程序员可以把精力集中到逻辑控制上面。在后面还会有进一步的比较。
 

 

10.3.4 JSP与ASP的比较

  JSP,ASP是使用最为广泛的两种服务器端脚本语言,总体说来这两者在技术上有很多的相似之处。两者都是为基于Web应用实现动态交互网页制作所提供的技术环境。两者都能够为程序开发人员提供实现应用程序的编制与自带组件设计网页的从逻辑上分离的技术。而且两者都能够替代CGI,使网站建设与发展变得较为简单与快捷。

  但是两者还是有很大的区别的,其中最本质的区别在于:两者是来于不同的技术规范组织,其实现的基础不同。JSP技术基于平台和服务器的互相独立,输入支持来自广泛的、专业化的各种工具包和数据库产品。相比之下,ASP技术主要依靠微软的技术支持。

  从学习的角度来讲,这两者是同多于不同的,有ASP经验的读者完全可以对比着学习。
 

10.3.5 JSP与传统CGI的比较
  跟Servlet一样,CGI的概念是十分简单的,但是真正编写CGI程序却不是这样的,它需要实实在在的技巧,必须具备调试程序的能力。编写CGI是一件令程序员头痛的事情,也正是应为这样,才会有象JSP,ASP这样的服务器端脚本语言出现。

  JSP隐藏了传统CGI编程中的大部分复杂性,使得服务器端编程大大简化。同时由于JSP最终是编译成Servlet执行的,通过前面Servlet与CGI的比较,我们很容易知道JSP的效率会比CGI来得高。
 

10.3.6 建立JSP运行环境

  一般说来支持JSP的服务器总是支持Servlet的,因为JSP本身需要Servlet的支持。前面我们介绍的Resin其实是一个JSP引擎,对Servlet的支持只是其功能的一部分。所以我们不必再寻找什么新的环境去试验我们的JSP。

  Resin中JSP的配置相对Servlet而言更为简单一些,因为它本身就是为JSP编写的,你只要启动httpd和srun,
在doc目录下的JSP文件就能够被识别。下面举一个最简单的例子:
  JSP文件:add.jsp
  2+2=
  
  把这个文件保存到doc目录下,在IE的地址栏键入
http://localhost:8080/add.jsp,这时候如果出现以下的页面就说明你的JSP页面已经开始工作了:

  至于这个简单JSP的意义在后面有分析。
 

10.3.7 服务器参数设置

  我们刚才只是在默认发布目录下试验了一个JSP文件,这个在Resin的默认配置文件中是已经有相应的配置语句的。如果需要配置其他目录或虚拟目录,就需要手工修改配置文件,由于各个JSP Server的配置方法各不相同,在这里我们就不对Resin的配置进行过多的介绍了,有兴趣的读者可以参考已有的配置语句和Resin的帮助文档。
 

10.3.8 JSP指令(1)

  下面我们开始讲解JSP的语法。从本质上讲JSP还是Java程序,因为它最终还是会被翻译成Servlet进而编译成class文件执行。但是由于JSP是嵌入式的Java程序,有些特殊的符号还是需要学习的。下面我们一一列举,读者不必深究,多使用之后就会熟悉。

  1.HTML注释

  2.JSP注释

  3. 声明

  4. 表达式

  5. 脚本段

 1.HTML注释

  该注释在客户端可通过查看源文件的方法看到。
  JSP语法:

  例1
  
  在客户端页面源程序中显示为:
  

  例2
     –>
  在客户端页面源程序中显示为:
  

  描述
  可以在注释中使用任何有效的JSP表达式。表达式是动态的,当用户第一次调用该页面或该页面后来被重新调用时,该表达式将被赋值。JSP引擎对HTML注释中的表达式执行完后,其执行的结果将直接插入到表达式显示的地方。然后该结果和HTML注释中的其它内容一起输出到客户端。在客户端可通过查看源文件的方法看到该注释。

 2.JSP注释

  JSP注释作为JSP页面的文档资料,但是该注释在客户端通过查看源文件的方法是看不到的。即该注释不发送到客户端。

  JSP语法:

  例子
  
  
  
  
  

注释测试

  
  
  

  描述
  JSP引擎对JSP注释不作任何处理。JSP注释既不发送到客户端,也不在客户端的JSP页面中显示。在客户端查看源文件时也看不到。因此,如果你想在JSP页面源程序中写文档说明时,JSP注释是很有用的。
注意:在JSP注释中,不能使用–%>,但是如果你一定要使用的话,可以通过使用–%\>来避开。

 3. 声明

  在JSP页面脚本语言中声明变量或方法

  JSP语法:

  例子
  
  
  

  描述
  在JSP文件中,一次可以声明一个或多个变量和方法,它们之间用分号隔开。在声明时使用JAVA语言时,必须符合JAVA语言规范。

  注意:
  (i) 变量必须先声明,然后才能使用。
  (ii) 声明必须以分号结尾,但是在表达式中则不用。
  (iii) 通过page指令导入的包中已声明的变量或方法,可以在主JSP文件中直接使用它们。
  (iv) 一般情况下,一个声明的有效范围是本页面。但是,如果JSP源文件用指令或include指令包含一些静态文件,声明的有效范围将会扩大,也就是说:声明在该静态文件中也有效。但是如果JSP源文件中用指令包含进来一个动态文件,主JSP文件中的声明在该动态文件中将不起作用。

 4. 表达式

  在JSP脚本语言中,可以使用任何有效的表达式。

  JSP语法:
 
  例子1:
  
  

  例子2:
  随机显示一个数字:
  
 
  描述
  表达式用于求值,然后其值转化为字符串,而且插入在JSP文件中显示该表达式的地方。而且可以在文本中使用表达式。表达式标签能包含符合JAVA语言规范的任何有效表达式,但是要注意:不能使用分号作为表达式的结尾,然而,在脚本段标签中,表达式要求以分号作为结尾。

 5. 脚本段

  在JSP页面脚本语言中,包含一段有效的代码片段。

  JSP语法:

  例1:
  
  
  

  描述
  在脚本段能声明多个变量和方法。能使用任何有效的表达式。能使用任何JSP隐含的对象或任何用标签声明的对象。能使用页面语言中任何有效的语句,如果使用Java语言,必须符合JAVA语言程序规范。

  说明:假如脚本段有输出语句,则输出内容被存储在out对象中,通过out对象输出到JSP页面中。

  以上介绍的是JSP的基本语法,有了以上的几条规则,我们就可以方便的书写一般的JSP文件了。比如我们前面测试Resin用的那个例子只有一句话,使用的就是表达式的语法,2+2=,前面一部分对应的是需要直接打印的HTML文本,对中的是Java语句,加了个等号就表示要返回表达式的值,并打印到客户端。翻译成对应的Servlet语句,大概就是:
        out.println(“2+2=”+(2+2));
  这样的一个语句如果改成
        System.out.println(“2+2=”+(2+2));
  并在console上运行,那么运行的结果就是2+2=4

10.3.8 JSP指令(2)

  下面是JSP中一些功能语句:

  1.Include指令

  2.Page 指令

  3.元素

  4.

  5.

  6.

  7.

  8.

 1.Include指令

  该指令用于包含一个文本或代码的文件。

  JSP语法:

  例子:
  include.jsp文件内容为:
  
  
  
  random.jsp中的随机显示的数为:
  
  
  
  random.jsp文件中内容为:
  

  在页面中显示为:
  random.jsp中的随机显示的数为: 2148.093521070482

  描述
  Include指令在JSP文件中插入一个包含文本和代码的文件。被包含的文件可以是JSP文件,HTML文件或文本文件。如果被包含的文件是JSP文件,则JSP引擎编译完该JSP文件后,执行的结果将插入到主JSP文件中Include指令所在的位置。如果被包含的文件是HTML文件或文本文件,则JSP引擎不对其进行编译,直接将其内容插入到主JSP文件中Include指令所在的位置。该包含是静态包含,即被包含的文件处理完,而且结果也插入到主JSP文件完毕,主JSP文件将继续执行include指令下面的内容。

  注意:
  (1)被包含的文件中不能含有,,,或标签。因为被包含的文件的全部内容将被插入到JSP文件中include指令所在的地方,这些标签将会同JSP文件中已有的同样的标签发生冲突。
  (2)假如被包含的文件发声变化,主JSP页面将被重新编译。
  
  属性:
  file=”relativeURL”
  file是被包含文件的路径名。 其值是一个URL的一部分,没有协议、端口号或域名. 如:
  ”error.jsp”
  ”/templates/onlinestore.html”
  ”/beans/calendar.jsp”
  如果相对URL以”/”开始,这个路径是相对于JSP应用上下文而言的,JSP应用上下文是存储在application对象中的javax.servlet.ServletContext对象。如果相对URL以目录或文件名开始,这个路径是相对于当前JSP文件所在的路径而言的。

 2. Page 指令

  定义整个JSP页面的全局属性。
  
  JSP语法:
  
  
  例:
  
  
  
  
  
  描述:
  Page指令的作用范围是整个JSP文件和该JSP文件用include指令或包含进来的任何静态文件,整个JSP文件和这些静态文件一起叫做一个”平移单元”. 注意:Page指令不适用于任何动态的包含文件。你可以在一个”平移单元”使用多个Page指令。但是每一个属性你只能使用一次,除了import(因为import属性和JAVA程序语言的import语句很类似,你能够多次使用它,就象在JAVA语言中你能够多次使用import命令一样)。不论你将Page指令放到JSP文件或被包含的文件的任何一个位置,它的作用范围都是整个”平移单元”.然而,一个好的编成风格是常常将它放到文件的顶部.

  language=”java”
  在JSP文件的脚本段、声明和表达式中使用的语言。现只允许用”JAVA”。

  extends=”package.class”
  该属性用于指明JAVA类文件超类全名。使用该属性时要小心,因为,它会限制JSP引擎编译文件的能力。

  import= “{ package.class | package.* }, …”
  JSP文件中导入的一个或多个用逗号隔开的包的列表。这些包(和它们的类)可以在JSP文件的脚本段、表达式、声明和标签中使用。你必须将import属性放到调用被到入的类的标签前面。如果你想导入多个包,你可以在import后面用逗号将每个包隔开即可,或你可以在一个JSP文件中使用多个import.

  session=”true|false”
  该属性用于确定JSP页面是否使用HTTP session.假如这个值是true, 则sesssion引用的是当前或新的session. 假如这个值是false,则在JSP文件中,不能使用session对象。 默认值是true.

  buffer=”none|8kb|sizekb”
  设置buffer缓冲区的大小,当out对象将被编译的JSP页面向客户Web浏览器端输出时使用。它的默认值是8kb.假如你指定了buffer缓冲区的大小,这个输出量将不低于你指定的大小。

  autoFlush=”true|false”
  该属性指出:当缓冲区充满时,是否自动刷新。如果值为true(默认值为true),则自动刷新。如果值为false,则当缓冲区溢出时,将会产生错误。而且如果buffer属性的值设为none,autoFlush属性的值不能设为false.

  isThreadSafe=”true|false”
  假设这个值设为true,多个线程能同时访问一个JSP页面。假如这个值为false,对一个JSP页面,JSP引擎一次只能响应一个用户的请求。默认值为true。

  info=”text”
  关于作者、版本和著作权等信息,可以通过javax.sevlet.Servlet.getServletInfo()方法查到该信息的详细情况。

  errorPage=”relativeURL”
  该属性用于设置处理例外事件的JSP文件的路径名。假如这个路径名以”/”开始,则这个路径名是JSP应用文档根目录而言的,而且由Web服务器决定。否则,这个路径是相对于当前JSP文件而言的。

  isErrorPage=”true|false”
  JSP文件是否显示错误页面。 如果这个值为true,你可以使用例外exception对象。如果这个值为false(默认值也为false),在JSP文件中,不能使用exception对象。

  contentType=”mimeType [ ;charset =characterSet ]” |
  ”text/html;charset=ISO-8859-1″
  JSP文件中使用的MIME type和character encoding(字符编码)是用于响应客户端的请求。你可以使用JSP引擎中有效的任何MIME type或character set(字符集).默认的MIME type是text/html,而且默认的字符集合是ISO-8859-1

 3.元素

  将客户端的请求转交给一个HTML文件、JSP文件或脚本段处理。
  
  JSP语法:

  例子:
  
  
  描述:
  标签将请求对象从一个JSP文件转交给另一个文件处理。

  特别注意:JSP引擎对主JSP页面下面的代码不再执行。

  说明:如果JSP文件的输出被设置为缓冲输出(即使用默认的Page指令值或直接设置缓冲区的buffer大小),则在请求被转交之前,缓冲区被清空。如果输出被设置为非缓冲输出(即用Page指令设置buffer=none),而且输出区中已经有内容,则使用元素,将会导致非法例外。

  属性

  page=”{ relativeURL | }”
  该属性用于设置将要转交的文件的相关URL.
  该URL不能包含协议名、端口号或域名,相对于当前JSP文件来说的。如果它是绝对地址(以”/”开始),该路径由你的Web或应用服务器决定。

 4.

  取得Bean属性的值,以便在结果页面中显示。
 
  JSP语法:

  例:
  Bean的程序代码为:
  package AccessDatabase;
  public class Readdate
  {
  private String username=”changcheng”;
  public String void getUsername(){return username; }
  }

  JSP文件的内容为:
  
  
  从Bean中取得属性名为username的值为:
  
  

  执行后显示结果为:
  从Bean中取得属性名为user的值为:changcheng

  描述:
  在使用前,你必须使?lt;jsp:useBean>元素创建或调用一个Bean实例。标签是用于取得JavaBeans属性值,相当于调用Bean中的某个属性的getXXX()方法。

  属性:
  name=”beanInstanceName”
  在标签中声明的Bean实例的名字。
  property=”propertyName”
  Bean属性的名字。

  说明:使用元素时如上例中:,username必须是Bean(Readdate)中的属性,且该Bean中要有getUsername()方法,否则编译时会出错。

 5.

  在JSP文件中,包含一个静态或动态文件.
 
  JSP语法?lt;jsp:include page=”{ relativeURL | }” flush=”true” />

  例子:
  
  
  

  属性:
  标签允许你包含一个静态文件或动态文件。一个静态文件被执行后,它的内容插入在主JSP页面中。一个动态文件对请求作出响应,而且将执行结果插入到JSP页面中。
  标签能处理两种文件类型,当你不知道这个文件是静态或动态的文件时,使用该标签是非常方便的。
  当include动作执行完毕后,JSP引擎将接着执行JSP文件剩下的文件代码。

  page=”{ relativeURL | }”
  该属性指出被包含文件相关URL;该URL不能包含协议名、端口号或域名。该URL是绝对或相对于当前JSP文件来说的。如果它是绝对地址(以”/”开始),该路径由你的Web或应用服务器决定

  flush=”true”
  在JSP文件中,常设置flush=”true”,因为它不是一个默认值。

 6.

  下载一个plugin插件到客户端以便执行applet或Bean

  JSP语法:
    type=”bean|applet”
  code=”classFileName”
  codebase=”classFileDirectoryName”
  [ name=”instanceName”]
  [ archive=”URIToArchive, …” ]
  [ align=”bottom|top|middle|left|right” ]
  [ height=”displayPixels”]
  [ width=”displayPixels”]
  [ hspace=”leftRightPixels”]
  [ vspace=”topBottomPixels”]
  [ jreversion=”JREVersionNumber | 1.1″]
  [ nspluginurl=”URLToPlugin”]
  [ iepluginurl=”URLToPlugin”]>
  [
  [ ]+
   ]
  [ text message for user ]
  
  
  例子:
  
  
  
  
  
  

Unable to load applet<

发表评论

邮箱地址不会被公开。 必填项已用*标注

昵称 *