《深入剖析Tomcat》一2.2 应用程序 1

    xiaoxiao2022-05-04  152

    2.2 应用程序 1

    下面从servlet容器的角度审视servlet程序的开发。简单来说,对一个Servlet的每个HTTP请求,一个功能齐全的servlet容器有以下几件事要做: 当第一次调用某个servlet时,要载入该servlet类,并调用其init()方法(仅此一次); 针对每个request请求,创建一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例; 调用该servlet的service()方法,将servletRequest对象和servletResponse对象作为参数传入; 当关闭该servlet类时,调用其destroy()方法,并卸载该servlet类。本章所要建立的servlet容器是一个很小的容器,没有实现所有的功能。因此,它只能运行非常简单的servlet,而且也会不调用servlet的init()和destroy()方法。它会做以下几件事: 等待HTTP请求; 创建一个servletRequest对象和一个servletResponse对象; 若请求的是一个静态资源,则调用StaticResourceProcessor对象的process()方法,传入servletRequest对象和servletResponse对象; 若请求的是servlet,则载入相应的servlet类,调用其service()方法,传入servletRequest对象和servletResponse对象。注意 在该servlet容器中,每次请求servlet都会载入相应的servlet类。本节的应用程序包括6个类: HttpServer1 Request Response StaticResourceProcessor ServletProcessor1 Constants图2-1展示了本节中的servlet容器的UML类图。该应用程序的入口点(静态main()方法)在类HttpServer1中。main()方法创建HttpServer1的一个实例,然后调用其await()方法。await()方法会等待HTTP请求,为接收到的每个请求创建一个Request和一个Response对象,并根据该HTTP请求的是静态资源或是servlet,将该HTTP请求分发给一个StaticResourceProcessor实例或一个ServletProcessor实例。Constants类中定义了静态final WEB_ROOT,供其他的类引用。WEB_ROOT指定了该servlet容器中使用的PrimitiveServlet类和静态资源的位置。HttpServer1类的实例会一直等待HTTP请求,直到接收到一条关闭命令。可以使用第1章介绍的方法来发布关闭命令。该应用程序中的各个类会在接下来的几节中逐个说明。

    2.2.1 HttpServer1类

    应用程序1中的HttpServer1类与第1章中简单Web服务器应用程序中的HttpServer类似。但是,该应用程序中的HttpServer1类既可以对静态资源请求,也可以对于servlet资源请求。若要请求一个静态资源,可以在浏览器的地址栏或URL框中输入如下格式的URL:

    http://machineName:port/staticResource

    这与第1章的Web服务器应用程序中对静态资源的请求相同。若要请求servlet资源,可以使用如下格式的URL:

    http://machineName:port/servlet/servletClass

    因此,若要请求本地浏览器上的名为PrimitiveServlet的servlet,可以在浏览器的地址栏或URL框中输入如下的URL:

    http://localhost:8080/servlet/Primitiveservlet

    应用程序1中的servlet容器会处理对PrimitiveServlet的请求。但是,若要调用其他的servlet(如ModernServlet),则servlet容器抛出异常。在后面的章节中,你将学会如何构建可以兼具两种功能的servlet容器。HttpServer1类的定义在代码清单2-2中。

    该类的await()方法会一直等待HTTP请求,直到接收到一条关闭命令,这点与第1章中的await()方法类似。区别在于,本章中的await()方法可以将HTTP请求分发给StaticResourceProcessor对象或ServletProcessor对象来处理。当URI包含字符串“/servlet/”时,会把请求转发给servletProcessor对象处理。否则的话,把HTTP请求传递给StaticResourceProcessor对象处理。注意代码清单2-2中灰色的部分。

    2.2.2 Request类

    servlet的service方法会从servlet容器中接收一个javax.servlet.ServletRequest实例一个和javax.servlet.ServletResponse实例。即,对每个HTTP请求来说,servlet容器必须创建一个ServletRequest对象和一个ServletResponse对象,并将它们作为参数传给它服务的servlet的service()方法。ex02.pyrmont.Request类表示被传递给servlet的service()方法的一个request对象。它必须实现javax.Servlet.servletRequest接口中声明的所有方法。但为了简单起见,这里只给出了部分方法的实现,其余方法的实现会在后面的章节给出。为了能够编译Request类,需要将未实现的方法留空。代码清单2-3中给出了Request类的定义,其签名返回obejct实例的所有方法都会返回null。

    此外,Request类还包括了在第1章中介绍过的parse()和getUri()方法。

    2.2.3 Response类

    ex02.pyrmont.Response类实现javax.servlet.servletResponse接口,类定义参见代码清单2-4。该类提供了servletResponse接口中声明的所有方法的实现。与Request类类似,除了getWriter()方法以外,大部分方法的实现都留空。

    在getWriter()方法中,PrintWriter类的构造函数的第2个参数是一个布尔值,表示是否启用autoFlush。对第2个参数传入true表示对println()方法的任何调用都会刷新输出。但是调用print()方法不会刷新输出。因此,如果在servlet的service()方法的最后一行调用print()方法,则该输出内容不会被发送给浏览器。这个bug会在后续的版本中修改。Response类中仍然保留了第1章中介绍过的sendStaticResource()方法。

    2.2.4  StaticResourceProcessor类

    ex02.pyrmont.StaticResourceProcessor类用于处理对静态资源的请求。该类只有一个方法,即process()方法。代码清单2-5给出了StaticResourceProcessor类的定义。

    process()方法接收两个参数:一个ex02.pyrmont.Request实例和一个ex02.pyrmont.Response实例。该方法仅仅调用Response对象的sendStaticResource()方法。

    2.2.5 servletProcessor1类

    ex02.pyrmont.servletProcessor1类的定义参见代码清单2-6,该类用于处理对servlet资源的HTTP请求。代码清单2-6 servletProcessor1类的定义

    package ex02.pyrmont; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.io.File; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class ServletProcessor1 { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); URLClassLoader loader = null; try { // create a URLClassLoader URL[] urls = new URL[1]; URLStreamHandler streamHandler = null; File classPath = new File(Constants.WEB_ROOT); // the forming of repository is taken from the // createClassLoader method in // org.apache.catalina.startup.ClassLoaderFactory String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ; // the code for forming the URL is taken from // the addRepository method in // org.apache.catalina.loader.StandardClassLoader. urls[0] = new URL(null, repository, streamHandler); loader = new URLClassLoader(urls); } catch (IOException e) { System.out.println(e.toString() ); } Class myClass = null; try { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; try { servlet = (servlet) myClass.newInstance(); servlet.service((ServletRequest) request, (ServletResponse) response); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } } }

    servletProcessor1类很简单,只有一个方法:process()方法。该方法接收两个参数,一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。该方法通过调用getRequestUri()方法从ServletRequest对象中获取URI:

    String uri = request.getUri();

    记住,URI的格式如下所示:

    /servlet/servletName

    其中,servletName是请求的servlet资源的类名。为了载入servlet类,需要从URI中获取servlet的类名。可以使用process()方法的下一行语句获取servlet的类名:

    String servletName = uri.substring(uri.lastIndexOf("/") + 1);

    接下来,porcess()方法会载入该servlet类。为了载入类,需要创建一个类载入器,并且指明到哪里查找要载入的类。对于本节的servlet容器,类载入器会到Constant.WEB_ROOT指定的工作目录下的webroot目录中查找要载入的类。注意 有关类载入器的详细内容将在第8章中介绍。为了载入一个servlet类,可以使用java.net.URLClassLoader类来完成,该类是java.lang.ClassLoader类的一个直接子类。一旦创建了URLClassLoader类的实例后,就可以使用它的loadClass()方法来载入servlet类。实例化URLClassLoader类很简单。该类有三个构造函数,其中比较简单的一个构造函数的签名如下所示:

    public URLClassLoader(URL[] urls);

    其中,urls是一个java.net.URL对象数组,当载入一个类时每个URL对象都指明了类载入器要到哪里查找类。若一个URL以“/”结尾,则表明它指向的是一个目录。否则,URL默认指向一个JAR文件,根据需要载入器会下载并打开这个JAR文件。注意 在servlet容器中,类载入器查找servlet类的目录称为仓库(repository)。在应用程序中,类载入器只需要查找一个位置,即工作目录下的webroot目录。因此,需要先创建只有一个URL的一个数组。URL类提供了一系列构造函数,因此有很多种方法可以创建URL对象。对于本应用程序,使用与Tomcat中另一个类中使用的相同构造函数,该构造函数的签名如下所示:

    public URL(URL context, java.lang.String spec, URLStreamHandler hander) throws MalformedURLException

    可以为第2个参数指定一个目录,指定第1个和第3个参数为null,这样就可以使用构造函数了。但是还有一个构造函数,它接受3个参数:public URL(java.lang.String protocol, java.lang.String host, java.lang.String file) throws MalformedURLException因此,若只使用如下语句,编译器就无法知道要调用哪个构造函数了,并且会报错:

    new URL(null, aString, null);

    因此,可以使用下面的代码,对于编译器指明第三个参数的类型:

    URLStreamHandler streamHandler = null; new URL(null, aString, streamHandler);

    第2个参数中的字符串指明了仓库的路径,也就是查找servlet类的目录。可以使用下面的代码生成仓库:

    String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;

    将上述代码综合到一起,就得到了创建URLCLassLoader实例的process()方法的部分代码:

    注意 生成仓库后会调用org.apache.catalina.startup.ClassLoaderFactory类的createClassLoader()方法,生成URL对象后会调用org.apache.catalina.loader.StandardClassLoader类的addRepository()方法。这些方法将在后续章节中介绍。有了类载入器后,就可以通过调用loadClass()方法来载入servlet类:

    接下来,process()方法会创建已载入的servlet类的一个实例,将其向下转型为javax.servlet.servlet,并调用其service()方法:

    2.2.6 运行应用程序

    要在Windows平台上运行该程序,可以在工作目录下执行如下命令:

    java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1

    在Linux平台上,需要用冒号分割两个库文件:

    java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1

    若想测试应用程序,可以在浏览器的地址栏或者URL框中输入如下地址:

    http://localhost:8080/index.html

    http://localhost:8080/servlet/Primitiveservlet

    当调用PrimitiveServlet类时,可以在浏览器中看到如下输出:

    Hello. Roses are red.

    注意,你是看不到第2个字符串“Violets are blue”的,因为只有第1个字符串会发送到浏览器。这个问题将在第3章解决。

    相关资源:JAVA WEB 开发详解:XML XSLT SERVLET JSP 深入剖析与实例应用.part2

    最新回复(0)