Servlet教程

Servlet教程
一、        Servlet简单介绍
Servlet是对支持Java的server的一般扩充。它最常见的用途是扩展Webserver,提供非常安全的、可移植的、易于使用的CGI替代品。它是一种动态载入的模块,为来自Webserver的请求提供服务。它全然运行在Java虚拟机上。由于它在server端运行,因此它不依赖于浏览器的兼容性。
servlet容器:
负责处理客户请求、把请求传送给servlet并把结果返回给客户。不同程序的容器实际实现可能有所变化,但容器与servlet之间的接口是由servlet API定义好的,这个接口定义了servlet容器在servlet上要调用的方法及传递给servlet的对象类。
servlet的生命周期:
1、servlet容器创建servlet的一个实例
2、容器调用该实例的init()方法
3、假设容器对该servlet有请求,则调用此实例的service()方法
4、容器在销毁本实例前调用它的destroy()方法
5、销毁并标记该实例以供作为垃圾收集
一旦请求了一个servlet,就没有办法阻止容器运行一个完整的生命周期。
容器在servlet首次被调用时创建它的一个实例,并保持该实例在内存中,让它对全部的请求进行处理。容器能够决定在不论什么时候把这个实例从内存中移走。在典型的模型中,容器为每一个servlet创建一个单独的实例,容器并不会每接到一个请求就创建一个新线程,而是使用一个线程池来动态的将线程分配给到来的请求,可是这从servlet的观点来看,效果和为每一个请求创建一个新线程的效果同样。
servlet API
servlet接口:
public interface Servlet
它的生命周期由javax.servlet.servlet接口定义。当你在写servlet的时候必须直接或间接的实现这个接口。一般趋向于间接实现:通过从javax.servlet.GenericServlet或javax.servlet.http.HttpServlet派生。在实现servlet接口时必须实现它的五个方法:
init():
public void init(ServletConfig config) throws ServletException
一旦对servlet实例化后,容器就调用此方法。容器把一个ServletConfig对象传统给此方法,这样servlet的实例就能够把与容器相关的配置数据保存起来供以后使用。假设此方法没有正常结束就会抛出一个ServletException。一旦抛出该异常,servlet就不再运行,而随后对它的调用会导致容器对它又一次载入并再次运行此方法。接口规定对不论什么servlet实例,此方法仅仅能被调用一次,在不论什么请求传递给servlet之前,此方法能够在不抛出异常的情况下运行完毕。
service():
public void service(ServletRequest req,ServletResponse res) throws ServletException,IOException
仅仅有成功初始化后此方法才干被调用处理用户请求。前一个參数提供訪问初始请求数据的方法和字段,后一个提供servlet构造响应的方法。
destroy():
public void destroy()
容器能够在不论什么时候终止servlet服务。容器调用此方法前必须给service()线程足够时间来结束运行,因此接口规定当service()正在运行时destroy()不被运行。
getServletConfig():
public ServletConfig getServletConfig()
在servlet初始化时,容器传递进来一个ServletConfig对象并保存在servlet实例中,该对象同意訪问两项内容:初始化參数和ServletContext对象,前者通常由容器在文件里指定,同意在运行时向sevrlet传递有关调度信息,后者为servlet提供有关容器的信息。此方法能够让servlet在不论什么时候获得该对象及配置信息。
getServletInfo():
public String getServletInfo()
此方法返回一个String对象,该对象包括servlet的信息,比如开发人员、创建日期、描写叙述信息等。该方法也可用于容器。
GenericServlet类
Public abstract class GenericServlet implants Servlet,ServletConfig,Serializable
此类提供了servlet接口的基本实现部分,其service()方法被申明为abstract,因此须要被派生。init(ServletConfig conf)方法把servletConfig对象存储在一个private transient(私有临时)实例变量里,getServletConfig()方法返回指向本对象的指针,假设你重载此方法,将不能使用getServletConfig来获得ServletConfig对象,假设确实想重载,记住要包括对super.config的调用。2.1版的API提供一个重载的没有參数的init()方法。如今在init(ServletConfig)方法结束时有一个对init()的调用,虽然眼下它是空的。2.1版API里面,此类实现了ServletConfig接口,这使得开发人员不用获得ServletConfig对象情况下直接调用ServletConfig的方法,这些方法是:getInitParameter(),getInitParameterNames(),getServletContext。此类还包括两个写日志的方法,它们实际上调用的是ServletContext上的相应方法。log(String msg)方法将servlet的名称和msg參数写到容器的日志中,log(String msg,Throwable cause)除了包括servlet外还包括一个异常。
HttpServlet类
该类扩展了GenericServlet类并对servlet接口提供了与HTTP更相关的实现。
service():
protected void service(HttpServletRequest req,HttpServletResponse res) throws ServletException,IOException
public void service(HttpServletRequest req,HttpServletResponse res)throws ServletException,IOException
该方法作为HTTP请求的分发器,这种方法在不论什么时候都不能被重载。当请求到来时,service()方法决定请求的类型(GET,POST,HEAD,OPTIONS,DELETE,PUT,TRACE),并把请求分发给相应的处理方法(doGet(),doPost(),doHead(),doOptions(),doDelete(),doPut(),doTrace())每一个do方法具有和第一个service()同样的形式。为了响应特定类型的HTTP请求,我们必须重载相应的do方法。假设servlet收到一个HTTP请求而你没有重载相应的do方法,它就返回一个说明此方法对本资源不可用的标准HTTP错误。
getLatModified():
protected long getLastModified(HttpServletRequest req)
该方法返回以毫秒为单位的的自GMT时间1970年1月1日0时0分0秒依赖的近期一次改动servlet的时间,缺省是返回一个负数表示时间未知。当处理GET请求时,调用此方法能够知道servlet的近期改动时间,server就可决定是否把结果从缓存中去掉。
HttpServletRequest接口
public interface HttpServletRequest extends ServletRequest
全部实现此接口的对象(比如从servlet容器传递的HTTP请求对象)都能让servlet通过自己的方法訪问全部请求的数据。以下是一些用来获取表单数据的基本方法。
getParameter()
public String getParameter(String key)
此方法试图将依据查询串中的keyword定位相应的參数并返回其值。假设有多个值则返回列表中的第一个值。假设请求信息中没有指定參数,则返回null。
getParameterValues():
public String[] getParameterValues(String key)
假设一个參数能够返回多个值,比方复选框集合,则能够用此方法获得相应參数的全部值。假设请求信息中没有指定參数,则返回null。
GetParameterNames():
Public Enumeration getParameterNames()
此方法返回一个Enumeration对象,包括相应请求的全部參数名字列表。
HttpServletResponse接口
public interface HttpServletResponse extends servletResponse
servlet容器提供一个实现该接口的对象并通过service()方法将它传递给servlet。通过此对象及其方法,servlet能够改动响应头并返回结果。
setContentType():
public void setContentType(String type)
在给调用者发回响应前,必须用此方法来设置HTTP响应的MIME类型。能够是不论什么有效的MIME类型,当给浏览器返回HTML是就是”text/html”类型。
getWriter():
public PrintWriter getWriter()throws IOException
此方法将返回PrintWriter对象,把servlet的结果作为文本返回给调用者。PrintWriter对象自己主动把Java内部的UniCode编码字符转换成正确的编码以使client能够阅读。
getOutputStream():
public ServletOutputStream getOutputStream() throws IOException
此方法返回ServletOutputStream对象,它是java.io.OutputStream的一个子类。此对象向客户发送二进制数据。
setHeader():
public void setHeader(String name,String value)
此方法用来设置送回给客户的HTTP响应头。有一些快捷的方法用来改变某些经常使用的响应头,但有时也须要直接调用此方法。
编译条件
须要从http://java.sun.com/products/servlet/ 获得一份JSDK的拷贝,并把servlet.jar移动到安装JDK文件夹下的/jre/lib/ext文件夹下。假设是JDK1.1,则移动到/lib下,并在CLASSPATH中添�servlet.jar的绝对路径。
运行条件
须要Apache Jserv,Jrun Servlet Exec,Java Web Server,Weblogic,WebSphere,Tomcat,Resin等servletserver端程序。
简单范例

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

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

servlet的性能和效率
一个servlet仅被初始化一次而运行多次,因此极小的低效性也会随着时间的添加�而产生非常非常大的影响。在代码中须要考虑String对象的使用,假设产生HTML响应须要用到非常多字符串时,不应该为每一个字符串生成一个String对象,由于这会产生大量的String和StringBuffer对象,造成大量的对象构造消耗和垃圾收集负担,解决的办法是一行一行的把全部须要写入的直接写入PrintWriter中,或者创建一个StringBuffer对象,并使用append()方法将文本添�。
及时回送
有时,程序须要花费非常长时间运行,在这种情况下应该回送给client一些信息,而不是长时间显示白屏,这能够在运行到一定程度就回送一些东西,能够使用PrintWriter的flush()方法强制将现有的内容回送给浏览器。

Servlet会话
由于Webserver使用的协议HTTP是一种无状态的协议,因此要维护单个客户机一系列请求的当前状态就须要使用其他的附加手段,在曾经,一般的方法是使用:
l    隐藏的表格字段:在浏览器中,这种类型的字段是不可见的,然而,它在请求中被传送,server端程序能够读取它的值。它的优点是实现easy,且大多浏览器都支持;缺点是字段必须依照特定的顺序建立,client能够通过查看源码得到其值,在浏览器中单击“后退”button会丢失加到当前页中的附加字段,同一时候也限制了用户动态的生成文档。
l    Cookie:是一些重要的作用片断,由server建立并由客户机的浏览器存放。浏览器维护一个它自己的Cookie表,这使得它能够作为一种会话跟踪的解决方式。使用Cookie的优点是它比在URL或表单中储存数据更直观。它的缺点是它能够用于在比一次短会话更长时间内跟踪用户,甚至能够用来跟踪某个用户向站点发送的每一个请求,因此有人操心自己的隐私问题而关闭了Cookie,一些老的浏览器也不支持cookie。Servlet API提供一个Cookie类支持cookie,使用HttpServletResponse.addCookie()和HttpServletResponse.getCookies()方法添加和读取cookie。
l    URL重写:改动请求的url,使之包括会话ID。这种方法的缺点是:对于大量的数据,URL会变得非常长而失去控制;在某些环境下,URL的字符串长度有一定的限制;数据保密问题,你可能不想让旁边的人或者能够使用同一个计算机的看到你的会话数据。Servlet提供HttpServletRequest类能够获得參数。
Servlet API有自己内置的会话跟踪支持,使用HttpSession对象既可。它的setAttribute()方法绑定一对名字/值数据,把它存到当前会话中,假设会话中已经存在该名字责替换它,语法为:public void setAttribute(String name,Object value)。getAttribute()方法读取存储在会话中的对象,语法为:public Object getAttribute(String name)。getAttributeNames()方法返回存储在会话中的全部名字,语法为:public String[] getAttributeNames()。最后一个方法是removeAttribute()方法,它从会话中删除指定的信息,语法为:public void removeAttribute(String name)。HttpSession对象能够使用HttpServletRequest对象request的getSession(true)方法获得。參数为true意味着假设不存在该对象则创建一个。
 
二、    servlet规范定义的Servlet 生命周期
servlet有良好的生存期的定义,包括怎样载入、实例化、初始化、处理client请求以及怎样被移除。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。
1、载入和实例化
容器负责载入和实例化一个servlet。实例化和载入能够发生在引擎启动的时候,也能够推迟到容器须要该servlet为客户请求服务的时候。
首先容器必须先定位servlet类,在必要的情况下,容器使用通常的Java类载入工具载入该servlet,可能是从本机文件系统,也能够是从远程文件系统甚至其他的网络服务。容器载入servlet类以后,它会实例化该类的一个实例。须要注意的是可能会实例化多个实例,比如一个servlet类由于有不同的初始參数而有多个定义,或者servlet实现SingleThreadModel而导致容器为之生成一个实例池。

2、初始化
servlet载入并实例化后,容器必须在它能够处理client请求前初始化它。初始化的过程主要是读取永久的配置信息,昂贵资源(比如JDBC连接)以及其他仅仅须要运行一次的任务。通过调用它的init方法并给它传递唯一的一个(每一个servlet定义一个)ServletConfig对象完毕这个过程。给它传递的这个配置对象同意servlet訪问容器的配置信息中的名称-值对(name-value)初始化參数。这个配置对象同一时候给servlet提供了訪问实现了ServletContext接口的详细对象的方法,该对象描写叙述了servlet的运行环境。
    2.1初始化的错误处理
    在初始化期间,servlet实例可能通过抛出UnavailableException 或者 ServletException异常表明它不能进行有效服务。假设一个servlet抛出一个这种异常,它将不会被置入有效服务而且应该被容器马上释放。在此情况下destroy方法不会被调用由于初始化没有成功完毕。在失败的实例被释放后,容器可能在不论什么时候实例化一个新的实例,对这个规则的唯一例外是假设失败的servlet抛出的异常是UnavailableException而且该异常指出了最小的无效时间,那么容器就会至少等待该时间指明的时限才会又一次试图创建一个新的实例。
    2.2、工具因素
    当工具(注:依据笔者的理解,这个工具可能是应用server的某些检查工具,一般是验证应用的合法性和完整性)载入和内省(introspect)一个web应用时,它可能载入和内省该应用中的类,这个行为将触发那些类的静态初始方法被运行,因此,开发人员不能假定仅仅要当servlet的init方法被调用后它才处于活动容器运行状态(active container runtime)。作为一个样例,这意味着servlet不能在它的静态(类)初始化方法被调用时试图建立数据库连接或者连接EJB容器。

3、处理请求
在servlet被适当地初始化后,容器就能够使用它去处理请求了。每一个请求由ServletRequest类型的对象代表,而servlet使用ServletResponse回应该请求。这些对象被作为service方法的參数传递给servlet。在HTTP请求的情况下,容器必须提供代表请求和回应的HttpServletRequest和HttpServletResponse的详细实现。须要注意的是容器可能会创建一个servlet实例并将之放入等待服务的状态,可是这个实例在它的生存期中可能根本没有处理过不论什么请求。
    3.1、多线程问题
    容器可能同一时候将多个client的请求发送给一个实例的service方法,这也就意味着开发人员必须确保编写的servlet能够处理并发问题。假设开发人员想防止这种缺省的行为,那么他能够让他编写的servlet实现SingleThreadModel。实现这个类能够保证一次仅仅会有一个线程在运行service方法而且一次性运行完。容器能够通过将请求排队或者维护一个servlet实例池满足这一点。假设servlet是分布式应用的一部分,那么,那么容器可能在该应用分布的每一个JVM中都维护一个实例池。假设开发人员使用synchronizedkeyword定义service方法(或者是doGet和doPost),容器将排队处理请求,这是由底层的java运行时系统要求的。我们强烈推荐开发人员不要同步service方法或者HTTPServlet的诸如doGet和doPost这种服务方法。
    3.2、处理请求中的异常
    servlet在对请求进行服务的时候有可能抛出ServletException或者UnavailableException异常。ServletException表明在处理请求的过程中发生了错误容器应该使用合适的方法清除该请求。UnavailableException表明servlet不能对请求进行处理,可能是临时的,也可能是永久的。假设UnavailableException指明是永久性的,那么容器必须将servlet从服务中移除,调用它的destroy方法并释放它的实例。假设指明是临时的,那么容器能够选择在异常信息里面指明的这个临时无法服务的时间段里面不向它发送不论什么请求。在这个时间段里面被被拒绝的请求必须使用SERVICE_UNAVAILABLE (503)返回状态进行响应而且应该携带稍后重试(Retry-After)的响应头表明不能服务仅仅是临时的。容器也能够选择不正确临时性和永久性的不可用进行区分而全部当作永久性的并移除抛出异常的servlet。
    3.3线程安全
    开发人员应该注意容器实现的请求和响应对象(注:即容器实现的HttpServletRequest和HttpServletResponese)没有被保证是线程安全的,这就意味着他们仅仅能在请求处理线程的范围内被使用,这些对象不能被其他运行线程所引用,由于引用的行为是不确定的。

4、服务结束
容器没有被要求将一个载入的servlet保存多长时间,因此一个servlet实例可能仅仅在容器中存活了几毫秒,当然也可能是其他更长的随意时间(可是肯定会短于容器的生存期)
当容器决定将之移除时(原因可能是保存内存资源或者自己被关闭),那么它必须同意servlet释放它正在使用的不论什么资源并保存不论什么永久状态(这个过程通过调用destroy方法达到)。容器在能够调用destroy方法前,它必须同意那些正在service方法中运行的线程运行完或者在server定义的一段时间内运行(这个时间段在容器调用destroy之前)。一旦destroy方法被调用,容器就不会再向该实例发送不论什么请求。假设容器须要再使用该servlet,它必须创建新的实例。destroy方法完毕后,容器必须释放servlet实例以便它能够被垃圾回收。
 
三、    serlvet为什么仅仅须要实现doGetdoPost
Serlvet接口仅仅定义了一个服务方法就是service,而HttpServlet类实现了该方法而且要求调用下列的方法之中的一个:
doGet:处理GET请求
doPost:处理POST请求
doPut:处理PUT请求
doDelete:处理DELETE请求
doHead:处理HEAD请求
doOptions:处理OPTIONS请求
doTrace:处理TRACE请求
通常情况下,在开发基于HTTP的servlet时,开发人员仅仅须要关心doGet和doPost方法,其他的方法须要开发人员非常的熟悉HTTP编程,因此这些方法被觉得是高级方法。
而通常情况下,我们实现的servlet都是从HttpServlet扩展而来。

doPut和doDelete方法同意开发人员支持HTTP/1.1的相应特性;
doHead是一个已经实现的方法,它将运行doGet可是仅仅向client返回doGet应该向client返回的头部的内容;
doOptions方法自己主动的返回servlet所直接支持的HTTP方法信息;
doTrace方法返回TRACE请求中的全部头部信息。

对于那些仅仅支持HTTP/1.0的容器而言,仅仅有doGet, doHead 和 doPost方法被使用,由于HTTP/1.0协议未定义PUT, DELETE, OPTIONS,或者TRACE请求。

另外,HttpServlet定义了getLastModified方法以支持有条件的(conditional)get操作。有条件的get操作是指使用GET方式请求资源而且在头部指定仅仅有在资源内容在指定时间后被改动的情况下server才有必要回应请求并发送请求的内容。对于那些实现doGet方法而且在不同请求之间内容同样的servlet而言,它应该实现这种方法以提高网络资源的利用率。

另外要提及的是,依照规范的要求,servlet容器至少要实现HTTP/1.0协议规范,推荐实现HTTP/1.1规范,在此基础上能够实现其他的基于请求回应模式(based request response model)的协议(比如HTTPS)。
 
四、    servlet实例的个数及因此引发的问题
在缺省情况下,一个容器中仅仅为每一个servlet定义生成一个servlet类实例。在servlet实现SingleThreadModel接口的情况下,容器能够生成多个实例以应付沉重的请求,也能够将请求排队发送给同一个实例(对于一个高性能的容器,也可能是这两种方式的结合,由于实例的个数是有限制的,因此在线程安全方式下一个实例会有多个请求排队等待服务同一时候容器中多个实例能够对请求进行服务)。对于为可分布式(distributable)应用开发的servlet而言,在每一个JVM中对每一个SERVLET定义都会有一个实例,假设在这种应用中servlet也实现了SingleThreadModel接口,那么在每一个JVM中每一个servlet定义也可能有多个实例。

使用SingleThreadModel接口能够保证一个线程一次性运行完给定实例的service方法,须要注意的是这个保证仅仅能应用于servlet实例,那些能够被多个servlet实例訪问的对象(比如HttpSession实例)依旧对多个servlet有效,即使他们实现了SingleThreadModel。

依据规范中的这些说明,我们在实现自己的serlvet时须要考虑多线程的问题,一般而言,不要在servlet中定义可变的成员,仅仅能定义一些常量(使用final定义,假设没有使用,应该注意在程序中不应该改动其值),笔者见过一个定义非常差的servlet:
public class SomeHttpServlet extends HttpServlet {

    HttpSession session;
    ...
}

这种servlet在使用中一定会出现故障,全部的用户都会共用一个session(这样非常节约系统资源,不是吗?:)),因此一个用户请求的信息突然跑到还有一个用户的ie窗体豪不奇怪。
而且,即使你的servlet实现了SingleThreadModel接口也不要定义可变的成员,由于该成员的信息会保留下来,而这对于其他的用户而言在绝大部分情况下是毫无意义的。(你确定会有意义的情况例外,比如某种计数)

另外须要说明的是上面说明中都是针对servlet定义而言的,而servlet定义定义不等价servlet类定义,即一个servlet类可能会有多个servlet定义,可是笔者还没有找到“servlet定义”的定义,规范中提到实例化一个servlet时可能会有不同的初始參数,可是这个也不同于带參数的多个构造方法。普通情况下我们能够觉得一个servlet类相应一个servlet定义。
 
五、    servlet会话
HTTP协议是一种无状态的协议,而对于如今的web应用而言,我们往往须要记录从特定client的一系列请求间的联系。如今已经有非常多会话跟踪的技术,可是对于程序猿而言都不是非常方便直接使用。servlet规范定义了一个简单的HttpSession接口以方便servlet容器进行会话跟踪而不须要开发人员注意实现的细节。

一般而言,有两种最经常使用的会话跟踪机制,一种就是URL重写。在client不接受cookie的情况下能够使用URL重写进行会话跟踪。URL重写包括向URL路径添加一些容器能够解释的数据。规范要求会话ID必须编码在URL路径中,參数名称必须是jsessionid,比如:
http://www.myserver.com/catalog/index.html;jsessionid=1234

还有一种就是如今最经常使用的cookie了,规范要求全部的servlet都必须支持cookie。容器向client发送一个cookie,client在兴许的处于同一个会话的请求中向server返回该cookie。会话跟踪cookie的名字必须是JSESSIONID。

新出现的一种会话功能是SSL会话,SSL(Secure Sockets Layer,安全套接字层)是HTTPS协议使用的一种加密技术,内建了会话跟踪功能,servlet容器能够非常easy的使用这些数据建立会话跟踪。(可是HTTPS不是规范要求servlet必须支持的协议) 

由于HTTP是一种基于请求响应的协议,因此会话仅仅有在client添�它以后才被新建立。当会话跟踪信息被成功的返回给server以指示会话给建立时client才算添�了一个会话。假设client没有添�会话,那么下一次请求不会被觉得是会话的一部分。怎样client还不知道会话或者client选择不添�一个会话,那么会话被觉得是新的。开发人员必须自己设计自己的应用中的会话处理状态,在什么地方没有添�会话,什么地方不能添�会话以及什么地方不须要添�会话。
规范要求HttpSession在应用或者servlet上下文级别有效,诸如cookie这种建立会话的底层机制能够在上下文中共享,可是对于那些外露的对象,以及更重要的是对象的那些属性是不能在上下文中共享的。

对于会话的属性的绑定而言,不论什么对象都能够绑定到某个命名属性。被绑定的属性对象对于其他处于同样ServletContext而且处于同一个会话处理中的其他servlet也是可见的。
某些对象在被添�会话或者被从会话中移除时要求得到通知,这种信息能够通过让该对象实现HttpSessionBindingListener接口得到。该接口定义了两个方法用以标记被绑定到会话或者从会话中被移除。
valueBound方法在对象通过getAttribute之前就被调用,而valueUnbound方法在对象已经不能通过getAttribute得到后才被调用。

由于HTTP是无状态协议,因此client不再活动时没有什么明显的信号,这也就意味着仅仅有一种机制能够用于表明client不再活动:超时。会话的缺省的时限由servlet容器定义而且能够通过HttpSession的getMaxInactiveInterval得到,开发人员也能够通过使用setMaxInactiveInterval方法进行设置,这些方法返回的单位是秒,假设时限被设置为-1,那么意味着永远不会超时。

通过调用HttpSession的getLastAccessedTime方法,我们能够得到在当前请求之前的訪问时间。当会话中的一个请求被servlet上下文处理时会话就被觉得被訪问了。

另外须要注意的就是一些非常重要的会话的语义问题。
多线程问题:多个请求线程可能会同一时候訪问同一个会话,开发人员有责任以适当的方式同步訪问会话中的资源。
分布式环境:对于被标记为可分布的应用而言,同一会话中的全部请求仅仅能被单一的VM处理。同一时候,放入HttpSession中的全部对象都必须实现Serializable接口,否则容器可能会抛出IllegalArgumentException(在jboss_tomcat下没有抛出这个异常,可是假设在关闭server时还有未完毕的会话,那么server在试图存储会话时会出现串行化异常,在又一次启动的时候会试图回复会话,也会出现异常)。这个限制意味着开发人员不会遇到非可分布容器中的那些并发问题。另外容器提供者能够通过将一个会话对象以及它的内容从分布式系统的一个活动节点移动到系统的其他不同节点的能力来保证可伸缩性。
client的语义:基于cookie或者SSL证书一般是被web浏览器控制而且不联系到特定浏览器窗体的事实,从client应用的全部窗体发送到容器的请求都可能是同一个会话。为了达到最大的可移植性,开发人员不能总假设特定client的全部窗体的请求都处于同一个会话中。
六、    BeanServlet的企业应用
J2EE是一个企业应用程序的开发平台,包括了对EJB、Servlet、JavaServer Page、JNDI、XML等的支持。在这个平台上能够开发瘦client的多层体系结构的企业应用程序。

  Enterprise JavaBean技术是J2EE的主要基础。EJB技术对在分布式的计算环境中运行应用逻辑提供了一个可伸缩的框架结构。J2EE通过将EJB组件结构和其他的企业技术相结合,攻克了在Java平台上进行开发和配置服务端应用程序的无缝结合。

  要使用J2EE开发您的企业应用,您必须要在您的机器上安装一个Webserver,还要支持XML。为了在浏览器中运行Java 2的API,还要给您的浏览器安装一个支持Java2的插件。

  以下就介绍怎样用J2EE SDK写一个包括了HTML页面,Servlet和Session Bean的一个简单的瘦client的多层体系结构的企业应用程序。听起来是不是心动了呢?以下就開始吧。

还要提醒一点的就是:在编程的时候,适当的添加�catch子句是一个非常好编程风格。假设样例代码抛出了一个正确的异常,代码就被 try/catch这种程序结构包围。Catch子句应该中应该添�处理异常的代码,千万不要留成空白。至少,应该添�语句:e.printStackTrace()来在控制台显示异常信息。

  J2EE SDK是一个J2EE平台上用于演示、教育等用途的非商业的东东。能够从javasoft的站点上免费下载。非常适合用来学习。假设你没有出国权限,还能够从国内各高校的FTPserver上去下载,速度比較快,但可能版本号不是最新的。


瘦client的多层体系结构的应用程序的样例:

  本样例通过一个HTML页面的输入来调用一个Servlet,Servlet再用Java的名字文件夹服务接口(JNDI)APIs来寻找一个会话Session Bean,用这个Session Bean来运行一个计算。当Servlet得到了计算的结果的之后,Servlet把计算结果返回给HTML页面的用户。

  之所以说这是一个瘦client的应用程序,是由于Servlet本身并没有运行不论什么的应用逻辑。这个简单的计算是由一个Session Bean在J2EE的应用server上运行的。客户没有參与过程的不论什么操作,全部的计算都是由Session Bean完毕的。

  所谓的多层体系结果实际上是由三或者四层组成的。我们的样例实际上是四层的一个结构。三层的体系结构是在标准的两层的客户/server结构基础上,将一个多线程的应用server加到了非浏览器的client和后台数据库之间。而四层的体系结构是通过Servlet和JavaServer Pages技术将client的应用程序由浏览器和HTML页面来代替。这个样例我们临时仅仅用当中的三层,在下一个样例中。我们再去訪问数据库。这样,就扩展到四层了。再以后,我们会涉及到JavaServer Pages技术和XML技术。


J2EE软件的安装:

  为了使我们的样例能够运行起来,首先要下载一个Java2 SDK Enterprise Edition(J2EE)的1.2.1的版本号和一个J2SE(Java 2 Standard Edition)的1.2以上的版本号。在Windows 2000系统中,假设我们把J2EE和J2SE都装到了C:/J2EE文件夹下。安装详细文件夹例如以下:

J2EE:C:/J2EE/j2sdkee1.2.1

J2SE:C:/J2EE/jdk1.2.2


Path和ClassPath的设置:

  下载的东西包括了J2EE的应用server、Cloudscape数据库、使用了加密套接字协议层的Webserver、开发和配置的工具、企业级的Java APIs。其Path和ClassPath的设置例如以下:

Path的设置:在Windows系统中,须要把Path的文件夹包括以下两个文件夹:

C:/J2EE/j2sdkee1.2.1/bin

C:/J2EE/jdk1.2.2/bin

Classpath的设置:在Windows系统中,须要把Classpath參数包括以下的文件:

C:/J2EE/j2sdkee.1.2.1/lib/j2ee.jar

另外,还要配置环境变量:

J2EE_HOME=C:/J2EE/j2sdkee1.2.1

JAVA_HOME=C:/J2EE/jdk1.2.2

  这样,就能够运行C:/J2EE/j2sdkee1.2.1/bin文件夹以下的批处理命令了。细致看看里面的批处理,你会发现不少的东西的。


J2EE应用程序组件:

  J2EE程序猿编写J2EE组件。J2EE组件是一个功能齐全的软件单元。将其他的应用程序组件组装到J2EE的应用程序和接口中。J2EE规范中定义例如以下的应用程序组件:


应用程序客户组件


Enterprise JavaBean组件


Servlet和JavaServer Pages组件(也叫做Web组件)


Applet

  在本样例中,我们创建了一个J2EE的应用程序和两个J2EE的组件:一个Servlet和一个Session Bean。Servlet和HTML文件是捆绑在一个WAR(WEB Archive)文件里。Session Bean的类和接口捆绑到了一个JAR文件里。然后再把WAR文件和JAR文件加到J2EE的应用程序,捆绑到一个EAR(Enterprise Archive)文件里。并验证測试产品环境的配置。

  在这全部的步骤中。实际上运行了非常多的不用的角色的功能。编写Session Bean和Servlet是开发工作。而创建一个J2EE的应用程序,将J2EE组件组装到应用程序中是应用程序的组装工作。实际上,这些工作能够在不同的地方由不用的人员来做。

创建一个HTML页面:

这个页面名字为bonus.html。HTML代码例如以下:

  代码中,让人感兴趣的是用别名来调用BonusServlet.class。由于在后面提到的应用程序的组装的时候,将它映射到了这个别名BonusServlet上

<HTML>

<BODY BGCOLOR = "WHITE">

<BLOCKQUOTE>

<H3>Bonus Calculation</H3>

<FORM METHOD="GET" ACTION="BonusAlias">

<P>

Enter social security Number:

<P>

<INPUT TYPE="TEXT" NAME="SOCSEC"></INPUT>

<P>

Enter Multiplier:

<P>

<INPUT TYPE="TEXT" NAME="MULTIPLIER"></INPUT>

<P>

<INPUT TYPE="SUBMIT" VALUE="Submit">

<INPUT TYPE="RESET">

</FORM>

</BLOCKQUOTE>

</BODY>

</HTML>

  这个HTML文件有两个数据域,用户能够输入社会保险号和一个乘数。当用户单击了Submit按纽。BonusServlet就得到了终端用户的数据。然后寻找Session Bean。将用户数据传递给Session Bean。Session Bean计算出奖金,把结果返回给Servlet。Servlet再通过还有一个HTML页面将奖金结果返回给用户。



创建Servlet:

样例假定BonusServlet.java文件是在C:/J2EE/Client-Code文件夹以下。在运行的时候,Servlet代码运行例如以下操作:


获得用户数据


查找Session Bean


将用户数据传递给Session Bean


在得到Session Bean的返回结果以后,创建一个HTML页面将结果返回给客户。


Servlet代码例如以下:

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import javax.naming.*;

import javax.rmi.PortableRemoteObject;

import Beans.*;

public class BonusServlet extends HttpServlet {

CalcHome homecalc;

public void init(ServletConfig config)

throws ServletException{

//Look up home interface

try{

InitialContext ctx = new InitialContext();

Object objref = ctx.lookup("calcs");

homecalc =

(CalcHome)PortableRemoteObject.narrow(

objref,

CalcHome.class);

} catch (Exception NamingException) {

NamingException.printStackTrace();



}

public void doGet (HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

String socsec = null;

int multiplier = 0;

double calc = 0.0;

PrintWriter out;

response.setContentType("text/html");

String title = "EJB Example";

out = response.getWriter();

out.println("<HTML><HEAD><TITLE>");

out.println(title);

out.println("</TITLE></HEAD><BODY>");

try{

Calc theCalculation;

//Get Multiplier and Social Security Information

String strMult =

request.getParameter("MULTIPLIER");

Integer integerMult = new Integer(strMult);

multiplier = integerMult.intValue();

socsec = request.getParameter("SOCSEC");

//Calculate bonus.10 AUGUST 28, 2000

double bonus = 100.00;

theCalculation = homecalc.create();

calc =

theCalculation.calcBonus(multiplier, bonus);

} catch(Exception CreateException){

CreateException.printStackTrace();

}

//Display Data

out.println("<H1>Bonus Calculation</H1>");

out.println("<P>Soc Sec: " + socsec + "<P>");

out.println("<P>Multiplier: " +

multiplier + "<P>");

out.println("<P>Bonus Amount: " + calc + "<P>");

out.println("</BODY></HTML>");

out.close();

}

public void destroy() {

System.out.println("Destroy");

}

}


  在import子句中,javax.servlet包括了Servlet Class的协议。Java.io是系统输入输出包。Javax.naming里面包括了Java名字文件夹服务APIs。Javax.rmi是用来Session Bean的home接口和Remote对象的通信使用的。

  在BonusServlet.init方法中,查找Session Bean的home接口。而且产生它的实例。方法使用了JNDI在组件的组装中的指定的名字calcs。用它来得到home接口的reference。然后就把这个reference和home接口类传递给PortableRemoteObject.narrow方法。来保证把reference转化为CalcHome类型。

  DoGet()方法有两个參数。一个是request对象,还有一个是reponse对象。浏览器发送一个request对象给Servlet。而Servlet返回一个response对象给浏览器。方法訪问request对象里面的信息,能够发现是谁在发出的请求、请求的数据在什么表单里面、是哪个HTTP头被发送。并使用reponse对象产生一个HTML页面来响应浏览器的请求。

  当方法处理请求的时候,假设产生输入输出错误,就抛出一个IOException异常。假设不能处理请求,就会抛出一个ServletException异常。为了计算奖金值,doGet()创建了一个home接口,调用它的calcBonus。


创建Session Bean:

  Session Bean代表了与客户的一个短暂的会话。假设服务或者客户有一方崩溃了。数据就消失了。相反,Entity Bean代表了数据库中一段持久的数据。假设服务或者客户又一方崩溃了,底层的服务保证数据能被保存下来。

  由于这个Enterprise Bean仅仅是应BonusServlet的请求,运行了一个简单的计算。假设发生崩溃,能够又一次初始化计算。这样,我们在本样例中就选择Session Bean来实现这个计算。

 在组装配置好以后,Servlet组件和Session Bean组件怎样在一个J2EE应用程序中协同工作。容器是Session Bean和支持Session Bean的底层平台之间的接口。容器是在配置期间产生的。

  本样例假定CalcBean.java、Calc.java和CalcHome.java文件都放在C:/J2EE/Beans文件夹以下。CalcHome.java文件前面的Package名字 Beans和文件夹Beans的名字应该是一样的。当这些文件被编译的时候,是从Beans文件夹中编译,其名字是包的名字后面加一个斜线在加上类或者接口的名字。


 

CalcHome.java文件:

package Beans;

import java.rmi.RemoteException;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

public interface CalcHome extends EJBHome {

Calc create() throws CreateException, RemoteException;

}

  BonusServlet并不直接同Session Bean通信。而是通过产生一个CalcHome的实例。这个Home接口扩展了EJBHome接口。有一个Create()方法,用来在容器中产生一个Session Bean。假设无法产生Session Bean,将会抛出一个CreateException异常。假设不能与Session Bean的远程方法通信,就会抛出一个RemoteException异常。


Calc.java文件:

package Beans;

import javax.ejb.EJBObject;

import java.rmi.RemoteException;

public interface Calc extends EJBObject {

public double calcBonus(int multiplier,

double bonus)

throws RemoteException;

}

  产生一个Home接口以后,J2EE应用程序就创建一个Remote接口和一个Session Bean。Remote接口扩展了EJBObject接口。而且声明了一个calcBonus()方法来计算奖金值。方法须要抛出javax.rmi.RemoteException异常。方法的实如今CalcBean类里面。


CalcBean.java文件:

package Beans;

import java.rmi.RemoteException;

import javax.ejb.SessionBean;

import javax.ejb.SessionContext;

public class CalcBean implements SessionBean { 

public double calcBonus(int multiplier,

double bonus) {

double calc = (multiplier*bonus);

return calc;

}

public void ejbCreate() { }

public void setSessionContext(

SessionContext ctx) { }

public void ejbRemove() { }

public void ejbActivate() { }

public void ejbPassivate() { }

public void ejbLoad() { }

public void ejbStore() { }

}

  本Session Bean类实现了SessionBean接口,提供了CalcBonus()方法的行为。在BonusServlet调用CalcHome的Create()方法以后,依次调用setSessionContext()方法和ejbCreate()方法。

  这些空的方法是从SessionBean中来的。由容器负责调用。除非在Bean的创建或者删除里面,你须要附加一些你自己的操作。否者,你并不须要提供这些方法的行为。
七、     
posted @ 2014-08-08 19:32  hrhguanli  阅读(1494)  评论(0编辑  收藏  举报