我们在Servlet解析(一)讨论了Servlet、Servlet容器以及一个Servlet容器-Tomcat(包括安装与配置,目录结构,运行,启动分析,体系结构和如何管理程序)。而本篇,我们继续论述Servlet技术包括Servlet UML 类图结构,Servlet容器如何处理请求,如何寻找正确的servlet,为什么要采用映射机制,一个简单的Web程序实例以及Servlet接口。首先,我们介绍一下开发Servlet需要用到的主要接口和类,其UML类图如图1所示。
图1 Servlet API中主要的接口与类的UML类图
Servlet容器处理请求的过程
在开始正文之前,让我们复习并细化一下Servlet容器处理请求的过程。
1)用户点击一个链接,指向了一个servlet而不是一个静态页面。
2)容器“看出”这个请求是一个Servlet,所以它创建了两个对象HttpServletRequest和HttpServletResponse。
3)容器根据请求中的URL找到正确的Servlet,为这个请求创建或分配一个线程,并把请求和响应对象传递给这个Servlet线程。
4)容器调用Servlet的service()方法。根据请求的不同类型,service()方法会调用doGet()或doPost()方法。这里假设调用doGet()方法。
5)doGet()方法生成动态页面,并把这个页面“塞到”响应对象里,需要注意的是,容器还有响应对象的一个引用!
6)线程结束,容器把响应对象转换为一个HTTP响应,并把它发回给客户,然后删除请求和响应对象。
Servlet为什么可以成为Servlet?
我们可以通过对一个Servlet的代码进行分析而得出结论。如图2所示。
图2 一个简单的Servlet实现代码
Servlet容器如何寻找正确的Servlet?
我们刚讨论Servlet容器处理请求的过程时曾经讲到过,Servlet容器会寻找正确的Servlet,那么它是如何寻找的呢?
其实,用户请求的URL会以某种方法映射到服务器上一个特定的Servlet。一个Servlet会有多个名字,,主要针对三个不同的对象。
1)对于用户来说,他所知道的,是一个公共的URL名。用户看到html中对应一个Servlet的URL,但并不知道这个Servlet名字如何映射到服务器上的目录和文件。这个URL名只是一个虚拟的名字,方便用户使用。
2)对于部署人员来说,他们可以创建一个名字,这个名字只有他自己以及实际操作环境中的其他人知道。同样的,这也是一个虚拟的名字,只用于部署Servlet。这个内部名字不一定与用户使用的公共URL名匹配,也不必与Servlet类的实际文件和路径名一样。
3)对于开发人员来说,Servlet类有一个完全限定的名字,其中包括类名和包名。Servlet类文件有一个实际的路径和文件名,这取决于服务器上包目录结构所在的位置。
为什么要采用映射机制?
想想看我们都可以采用什么办法让Servlet容器寻找正确的Servlet?有一种办法是把真实路径名和文件名硬编码到所有使用该Servlet的JSP和其他HTML页面中,但如果你重新组织了你的应用,这时候目录改变了怎么办?而如果采用映射的方法,就会有很大的灵活性,如果改变了应用的目录,那么只需要在映射中修改即可。再说基于安全考虑,你总不希望用户对你服务器上的目录了如指掌吧?所以通过映射机制,让用户访问一个虚拟的名字,这样会提高安全性。
我们通过一个典型的MVC实例来了解一下整个Web开发机制好了。MVC指的是Model,View,Controller。即模型,视图,控制器。MVC将业务逻辑从Servlet中抽取出来,放到一个模型中(即一个可重用的Java类)。模型是业务数据和方法的组合。视图使用JSP,HTML等实现用户和系统的交互。在MVC中,视图负责表示。它从控制器得到模型的状态(不是真实得到,控制器会把模型数据能找到的一个地方)。另外,视图还要获得用户输入,交给控制器。控制器从请求获得用户输入,并明确这些输入对模型有什么影响,告诉模型自行更新,并且还要让视图(JSP)能得到新的模型状态。模型用于存放实际的业务逻辑和状态。它知道用什么规则来得到和更新状态。另外,模型还负责和数据库通信。
下面我们看看web应用的开发与部署环境,分别如图3和图4所示。
图3 Web应用的开发环境
图4 Web应用的部署环境
一个简单的Web应用程序
MyDemo是一个简单的Web应用程序。主要功能是用户选择一个颜色的英文单词,然后“专家”会给出一个对应的中文词语。Servlet即控制器的代码如下:
package com.shan.web; import com.shan.model.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** ** A simple Servlet Demo ** @author shan **/ public class MyDemoServlet extends HttpServlet{ public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException { response.setContentType("text/html; charset=UTF-8"); PrintWriter out= response.getWriter(); String color = request.getParameter("color"); out.println("You select color:" + color); Expert expert = new Expert(); out.println(expert.getResult(color)); out.println("<br/>Expert's result:" + expert.getResult(color)); } }模型代码如下所示:
package com.shan.model; import java.util.*; public class Expert{ private Map<String, String> results = new HashMap<String, String>(); public Expert() { results.put("White","白色"); results.put("Black","黑色"); results.put("Blue","蓝色"); results.put("Red","红色"); results.put("Green","绿色"); } public String getResult(String color) { return results.get(color); } public static void main(String[] args) { Expert expert = new Expert(); System.out.println(expert.getResult("White")); } }部署描述文件(DD)如下所示:
<?xml version='1.0' encoding='utf-8'?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true"> <description> My Demo. </description> <servlet> <servlet-name>selectServlet</servlet-name> <servlet-class>com.shan.web.MyDemoServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>selectServlet</servlet-name> <url-pattern>/SelectColor.do</url-pattern> </servlet-mapping> </web-app>有关web.xml文件的具体解释图5所示。
<html> <head> <title>Form</title> </head> <body> <h1 align="center">Selection Page</h1> <form align="center" method="post" action="SelectColor.do"> Select color: <br/> <select name="color" size="1"> <option>White</options> <option>Black</options> <option>Blue</options> <option>Red</options> <option>Green</options> </select> <input type="submit" value="Submit"> </form> </body> </html>由于例子简单,在此就不再放运行结果了。有兴趣的话可以按照上面的所讲的开发与部署工程,然后自己运行。运行时需要编译servlet。可以win+r,输入cmd,切换到工程所在目录。输入语句:
javac -d classes src\com\shan\model\Expert.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar;classes -d .\classes src\com\shan\web\MyDemoServlet.java
编译完成之后,将classes文件夹等文件复制到图4所示的位置,之后输入startup启动Tomcat(注:Tomcat的配置按照上篇所述配置即可)。在浏览器网址栏输入:localhost:8080/MyDemo/form.html,选择一个颜色,点确认即可得到结果。
好了,讲完了这个简单的例子。让我们继续看看服务器的整个运行过程。
1)用户(浏览器)请求得到form.html页面2)容器获得form.html页面3)容器把这个页面返回给浏览器,用户在表单上回答问题4)浏览器把请求数据发送给容器5)容器根据URL找到正确的servlet,并把请求传递给这个servlet 6)serlvet调用Expert寻求帮助7)Expert类返回一个回答,servlet将这个回答增加到请求对象8)servlet将请求转发给JSP9)JSP从请求对象得到回答10)JSP为容器生成一个页面11)容器把这个页面(最终结果)返回给用户。
在本例中,容器寻找正确servlet的过程是这样的:
1)form.html中给出了调用的方法是post,给出了action是SelectColor.do,所以用户填写了表单后,会生成以下URL:localhost:8080/MyDemo/SelectColor.do,其中SelectColor.do是逻辑资源名。
2)容器搜索部署描述文件,找到<url-pattern>与/SelectColor.do匹配的一个<servlet-mapping>
3)容器看到对应的这个<url-pattern>的servlet-name>是SelectServlet,但这不是实际的servlet类文件的名字。SelectServlet只是一个servlet名,不是servlet类的名字。
4)容器查找servlet-name>是SelectServlet的<servlet>标记。
5)根据<servlet>标记中的<servlet-class>,容器知道了由哪个servlet类负责处理这个请求,如果这个servlet类还没有初始化,就会加载类,并初始化servlet。
6)容器开启一个新的线程来处理这个请求,并把请求传递给线程(传递给servlet的service()方法,由于用户发出了一个HTTP post请求,所以service()方法会调用servlet的doPost()方法,把请求和响应对象作为参数传递给它)。
7)service()方法结束,所以线程要么撤销,要么返回到容器管理的一个线程池。请求和响应对象引用已经出了作用域,所以会被垃圾回收机制回收。容器把响应(通过web服务器)发回给用户。
Servlet接口
在Java中我们学过Java Applet即Java小应用程序,它运行在客户端的浏览器中。在此,我们可以类比一下Java Applet与Java Servlet,比较其异同点加深我们对Java Servlet的理解。
两者的共同点:
它们都不是独立的应用程序,都没有main()方法。
它们都不是由用户或程序员直接调用,而是生存在容器中,由容器管理。Applet运行在浏览器中,Servlet运行在Servlet容器中。
它们都有生命周期,都包含了init()和destroy()方法。
两者的不同点:
Java Applet具有图形界面,运行在客户端的浏览器中;Java Servlet没有图形界面,运行在服务器端的Servlet容器中。
要编写一个Java Applet,需要从java.applet.Applet类派生一个子类;和Java Applet类似,要编写一个Java Servlet,需要实现javax.servlet.Servlet接口。
javax.servlet.Servlet接口接口定义了如下5个方法:
public void init(ServletConfig config) throws ServletException public void service(ServletRequest req, ServletResponse res)throws ServletException, java.io.IOException public void destroy() public ServletConfig getServletConfig() public java.lang.String getServletInfo()
下面介绍一下这5个方法的作用。
init():在Servlet实例化之后,Servlet容器会调用init()方法,来初始化该对象,主要是为了让Servlet对象在处理客户请求前可以完成一些初始化的工作,例如,建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只能被调用一次。init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用ServletConfig对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对象获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。
service():容器调用service()方法来处理客户端的请求。要注意的是,在service()方法被容器调用之前,必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。在service()方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
destroy():当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destroy()方法,以便让Servlet对象可以释放它所使用的资源,保存数据到持久存储设备中,例如,将内存中的数据保存到数据库中,关闭数据库的连接等。当需要释放内存或者容器关闭时,容器就会调用Servlet对象的destroy()方法。在Servlet容器调用destroy()方法前,如果还有其他的线程正在service()方法中执行,容器会等待这些线程执行完毕或等待服务器设定的超时值到达。一旦Servlet对象的destroy()方法被调用,容器不会再把其他的请求发送给该对象。如果需要该Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端的请求。在destroy()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,该对象会被Java的垃圾收集器所回收。
getServletConfig():该方法返回容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数。
getServletInfo():返回一个String类型的字符串,其中包括了关于Servlet的信息,例如,作者、版本和版权。该方法返回的应该是纯文本字符串,而不是任何类型的标记(HTML、XML等)。
每个请求都在一个单独的实例中运行
由上文我们知道,在servlet实例创建之后,在servlet能为客户请求提供服务之前,容器会在servlet实例上调用init()方法。如果你有初始化代码,就应该覆盖servlet类的init()方法,否则会调用GenericServlet的init()方法。而对应每个客户请求(无论是谁,无论是不是同一个人,只针对请求),容器都会创建一对新的请求和响应对象,创建一个新的线程/栈。任何servlet类都不会有多个实例,除非一种特殊情况(SingleThreadModel,其实很糟糕),我们不讨论这种特殊状况。
转载请注明出处:http://blog.csdn.net/iAm333