欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-14 10:33:51
...

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章介绍的方法来发布关闭命令。
该应用程序中的各个类会在接下来的几节中逐个说明。

《深入剖析Tomcat》一2.2 应用程序 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中。
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1

该类的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。
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1

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

2.2.3 Response类

ex02.pyrmont.Response类实现javax.servlet.servletResponse接口,类定义参见代码清单2-4。该类提供了servletResponse接口中声明的所有方法的实现。与Request类类似,除了getWriter()方法以外,大部分方法的实现都留空。
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1

在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类的定义。
《深入剖析Tomcat》一2.2 应用程序 1
《深入剖析Tomcat》一2.2 应用程序 1

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类:
《深入剖析Tomcat》一2.2 应用程序 1

接下来,process()方法会创建已载入的servlet类的一个实例,将其向下转型为javax.servlet.servlet,并调用其service()方法:
《深入剖析Tomcat》一2.2 应用程序 1

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章解决。