Tomcat剖析(二)

Tomcat剖析(二)

目录:

  1. Tomcat剖析(一):一个简单的Web服务器
  2. Tomcat剖析(二):一个简单的Servlet服务器

这一节基于 《深度剖析Tomcat》第二章: 一个简单的Servlet服务器 总结而成。

上一节,我们了解了一个简单的Web服务器的总体处理流程是怎样的;这一节,我们开始搭建一个简单的Servlet容器,也就是增加实现了servlet的简单加载执行,而不仅仅是将文件内容输出到浏览器上。当然,这一节的servlet的实现是最简单的,用来了解整个Servlet的大致工作流程。

推荐先到我的github(地址在本文最后)上下载本书相关的代码,同时去上网下载这本书。

说到servlet,首先得先了解javax.servlet.Servlet接口,Servlet接口中的方法描述了servlet的生命周期方法,即init()、service()和destroy()。我们自己所有的servlet必须实现这个接口或者继承实现了这个接口的类(最一般的情况就是继承HttpServlet)。对于servlet生命周期的方法就不在这里具体描述了,可以自行查询关于“servlet生命周期”的资料。在这里只要知道Servlet接口可以完成“在请求第一次到来时初始化servlet、对每次请求调用service方法执行请求、servlet关闭时调用destroy方法销毁servlet”3个功能就够了。

一个Http请求过来时,Servlet服务器经历以下过程

1.Server创建一个serverSocket对象,等待Client发送请求

2.Client发送请求后,Server获取用户socket,从而得到请求的输入输出流

3.从输入输出流中创建request和response对象

4.解析request,如果是静态资源就创建StaticResourceProcessor实例,传递请求和响应给对应的方法,具体方法就是:从request中获取URI,判断文件是否存在,如果不存在就响应404 ,存在则将文件内容写到浏览器;如果是servlet请求就创建ServletProcessor1实例,同样传递请求和响应给对应的方法,完成加载servlet和调用Servlet的service方法执行。

5.关闭用户socket

6.判断URI是否为/SHUTDOWN,如果不是则重新进入等待请求状态,回到第2步,否则关闭服务器。

整个流程主要包含5个类,HttpServer1、ServletProcessor1、StaticResourceProcessor、Request、Response。

HttpServer1.java:完成创建ServerSocket对象,获取Socket对象及其输入输出流,解析请求,创建Request对象和Response对象,并将不同类型的请求分派给不同的Processor处理

ServletProcessor1.java:当请求servlet时创建的对象,用于加载和执行servlet。

StaticResourceProcessor.java:当请求的资源是静态资源时创建的对象,调用Response对象的sendStaticResource()处理静态资源

Request.java:与上一节基本一样,是对输入流解析实现,获取URI。不同之处在于实现 javax.servlet.ServletRequest 接口,这一节中除了解析请求的方法外,其它未实现的方法置为默认值。

Reponse.java:与上一节基本一样,完成对浏览器的响应,包含对请求文件存在与不存在的处理。不同之处在于实现 javax.servlet.ServletResponse接口,这一节中除了getWriter方法外,其它未实现的方法置为默认值。

注意:在这一节中有些东西看起来是不合理的,以后的章节中会改进:

  1.对Servlet接口 仅仅是调用了service方法,没有调用int()和destroy()

  2.每一个servlet被请求时,servlet类就被加载一次

代码陈述更方便,以下开始将结合代码讲解

 HttpServer1.java

这个类和上一节HttpServer的非常相似。

不同之处只有在判断请求的类型时进行if else处理,而不是直接用上一节的response.sendStaticResource()直接将文件内容写到浏览器

对应的,如果判断确实是请求静态资源(即URL不以/servlet/开头)就调用StaticResourceProcessor处理器的process方法,显示文件到浏览器中

反之,如果判断是servlet,调用ServletProcessor1的process方法加载和执行servlet

 1 package ex02.pyrmont;
 2
 3 import java.net.Socket;
 4 import java.net.ServerSocket;
 5 import java.net.InetAddress;
 6 import java.io.InputStream;
 7 import java.io.OutputStream;
 8 import java.io.IOException;
 9
10 public class HttpServer1 {
11
12     // 用于判断是否需要关闭服务器,默认是false
13     private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
14     private boolean shutdown = false;
15
16     public static void main(String[] args) {
17         HttpServer1 server = new HttpServer1();
18         server.await();
19     }
20
21     public void await() {
22
23         ServerSocket serverSocket = null;
24         int port = 8080;
25         try {
26             //创建服务器端的ServerSocket
27             serverSocket = new ServerSocket(port, 1,
28                     InetAddress.getByName("127.0.0.1"));
29         } catch (IOException e) {
30             e.printStackTrace();
31             System.exit(1);
32         }
33
34         while (!shutdown) {
35             Socket socket = null;
36             InputStream input = null;
37             OutputStream output = null;
38             try {
39                 socket = serverSocket.accept();//Server一直等待直到Client发送请求
40                 input = socket.getInputStream();//接收请求后获取输入输出流
41                 output = socket.getOutputStream();
42
43                 //创建request对象,传入input参数用于获取输入流的参数
44                 Request request = new Request(input);
45                 request.parse();//解析request对象,这一节同样也只是获取请求中请求行的URI
46
47                 //创建response对象,传入output对象用于获取Writer对象
48                 Response response = new Response(output);
49                 response.setRequest(request);
50
51                 //下面是这个类的关键所在
52                 //当URL是以/servlet/开头时说明请求servlet,使用ServletProcessor1处理器处理
53                 //否则说明是请求静态资源,由StaticResourceProcessor处理器处理
54                 if (request.getUri().startsWith("/servlet/")) {
55                     ServletProcessor1 processor = new ServletProcessor1();
56                     processor.process(request, response);
57                 } else {
58                     StaticResourceProcessor processor = new StaticResourceProcessor();
59                     processor.process(request, response);
60                 }
61
62                 //关闭用户socket
63                 socket.close();
64                 //如果URI是/SHUTDOWN说明需要关闭服务器
65                 shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
66             } catch (Exception e) {
67                 e.printStackTrace();
68                 System.exit(1);
69             }
70         }
71     }
72 }

HttpServer1.java的详细说明可以参照第一节的HttpServer类的详细说明

由此类可以看出:

要请求一个静态资源,就像是在第 1 节提到的,你可以在你的浏览器地址栏或者网址框里边敲入一个 URL:
  http://machineName:port/staticResource
为了请求一个 servlet,你可以使用下面的 URL:
  http://machineName:port/servlet/servletClass

 ServletProcessor1.java

这个类是这一节的关键类,

仅仅只有一个process方法,但方法内部却没那么简单。

先上代码

 1 package ex02.pyrmont;
 2
 3 import java.net.URL;
 4 import java.net.URLClassLoader;
 5 import java.net.URLStreamHandler;
 6 import java.io.File;
 7 import java.io.IOException;
 8
 9 import javax.servlet.Servlet;
10 import javax.servlet.ServletRequest;
11 import javax.servlet.ServletResponse;
12
13 public class ServletProcessor1 {
14
15     public void process(Request request, Response response) {
16
17         String uri = request.getUri();// 获取URI,如/servlet/className
18         // 要知道servlet名,截取第二段,即可获得className
19         String servletName = uri.substring(uri.lastIndexOf("/") + 1);
20         URLClassLoader loader = null;
21
22         try {
23             //try块中用于创建URLClassLoader对象
24             URL[] urls = new URL[1];
25             URLStreamHandler streamHandler = null;
26             //Constants类存储静态常量,在本节最后贴上。
27             //Constants.WEB_ROOT即System.getProperty("user.dir") + File.separator  + "webroot";
28             File classPath = new File(Constants.WEB_ROOT);
29             String repository = (new URL("file", null,
30                     classPath.getCanonicalPath() + File.separator)).toString();
31             urls[0] = new URL(null, repository, streamHandler);
32             //经过以上过程后可以得到类似“file:E:/java/tomcat/servletName/”的路径
33             //最后URLClassLoader对象根据这个url获取到相应路径下serlvet的类加载器
34             loader = new URLClassLoader(urls);
35
36         } catch (IOException e) {
37             System.out.println(e.toString());
38         }
39         Class myClass = null;
40         try {
41             myClass = loader.loadClass(servletName);  //根据反射获取Class对象
42         } catch (ClassNotFoundException e) {
43             System.out.println(e.toString());
44         }
45
46         Servlet servlet = null;
47         try {
48             servlet = (Servlet) myClass.newInstance();//创建Servlet实例
49             servlet.service((ServletRequest) request,//执行servlet
50                     (ServletResponse) response);
51         } catch (Exception e) {
52             System.out.println(e.toString());
53         } catch (Throwable e) {
54             System.out.println(e.toString());
55         }
56
57     }
58 }

ServletProcess1.java详细说明:
要加载 servlet,你可以使用 java.net.URLClassLoader 类,它是 java.lang.ClassLoader类的一个直接子类。

public URLClassLoader(URL[] urls); 这里 urls 是一个 java.net.URL 的对象数组,这些对象指向了加载类时候查找的位置。任何以/结尾的 URL 都假设是一个目录。

类加载器必须查找的地方只有一个,如工作目录下面的 webroot目录。因此,我们首先创建一个单个 URL 组成的数组。

URL 类提供了一系列的构造方法,所以有很多构造一个 URL 对象的方式。

一旦你拥有一个 URLClassLoader 实例,你使用它的 loadClass 方法去加载一个 servlet 类。(实在不懂怎么用可以自己查看API或者百度谷歌一下)

然后,process 方法创建一个 servlet 类加载器的实例,  把它向下转换(downcast) 为
javax.servlet.Servlet, 并调用 servlet 的 service 方法

 StaticResourceProcessor.java

这个类只有一个process方法,但确实很简单,如果理解了上一节,现在就没什么难理解了。

 1 package ex02.pyrmont;
 2
 3 import java.io.IOException;
 4
 5 public class StaticResourceProcessor {
 6
 7     //存粹的打印文件内容到浏览器
 8     public void process(Request request, Response response) {
 9         try {
10
11             response.sendStaticResource();
12         } catch (IOException e) {
13             e.printStackTrace();
14         }
15     }
16 }

Request.java和Response.java内容很多而且不是本节关键代码,大家可以根据上面这两个类的简单介绍

并结合我github上的Tomcat4的代码找到ex02.pyrmont包下的这两个类,

最后与上一节这两个类的代码(对应ex01.pyrmont包)对比以下。应该就没什么大问题,这里就不说了

下面的折叠代码是Constants.java,打消大家的疑虑

package ex02.pyrmont;

import java.io.File;

public class Constants {
  public static final String WEB_ROOT =
    System.getProperty("user.dir") + File.separator  + "webroot";
}

最后说说上面搭建的代码的安全性问题

在 ServletProcessor1 类的 process 方法,你向上转换ex02.pyrmont.Request 实例为 javax.servlet.ServletRequest ,并作为第一个参数传递给servlet 的 service 方 法 。 你 也 向 下 转 换 ex02.pyrmont.Response 实 例 为javax.servlet.ServletResponse,并作为第二个参数传递给 servlet 的 service 方法。

try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request,(ServletResponse) response);
}

这会危害安全性。知道这个 servlet 容器的内部运作的 Servlet 程序员可以分别把ServletRequest 和 ServletResponse 实 例 向 下 转 换 为 ex02.pyrmont.Request 和
ex02.pyrmont.Response,并调用他们的公共方法。拥有一个 Request 实例,它们就可以调用 parse方法。拥有一个 Response 实例,就可以调用 sendStaticResource 方法。

为了解决这个问题,我们增加了两个 façade 类: RequestFacade 和 ResponseFacade。RequestFacade 实现了 ServletRequest 接口并通过在构造方法中传递一个引用了
ServletRequest 对象的 Request 实例作为参数来实例化。ServletRequest 接口中每个方法的实现都调用了 Request 对象的相应方法。然而 ServletRequest 对象本身是私有的,并不能在类的外部访问。我们构造了一个 RequestFacade 对象并把它传递给 service 方法,而不是向下转换Request 对象为 ServletRequest 对象并传递给 service 方法。 Servlet 程序员仍然可以向下转换ServletRequest 实例为 RequestFacade,不过它们只可以访问 ServletRequest 接口里边的公共方法。现在 parseUri 方法就是安全的了。

其实我也不想放上这两个类,但是还是折叠给上吧。

RequestFacade.java

  1 package ex02.pyrmont;
  2
  3 import java.io.IOException;
  4 import java.io.BufferedReader;
  5 import java.io.UnsupportedEncodingException;
  6 import java.util.Enumeration;
  7 import java.util.Locale;
  8 import java.util.Map;
  9
 10 import javax.servlet.AsyncContext;
 11 import javax.servlet.DispatcherType;
 12 import javax.servlet.RequestDispatcher;
 13 import javax.servlet.ServletContext;
 14 import javax.servlet.ServletInputStream;
 15 import javax.servlet.ServletRequest;
 16 import javax.servlet.ServletResponse;
 17
 18 public class RequestFacade implements ServletRequest {
 19
 20     private ServletRequest request = null;
 21
 22     public RequestFacade(Request request) {
 23         this.request = request;
 24     }
 25
 26     /* implementation of the ServletRequest */
 27     public Object getAttribute(String attribute) {
 28         return request.getAttribute(attribute);
 29     }
 30
 31     public Enumeration getAttributeNames() {
 32         return request.getAttributeNames();
 33     }
 34
 35     public String getRealPath(String path) {
 36         return request.getRealPath(path);
 37     }
 38
 39     public RequestDispatcher getRequestDispatcher(String path) {
 40         return request.getRequestDispatcher(path);
 41     }
 42
 43     public boolean isSecure() {
 44         return request.isSecure();
 45     }
 46
 47     public String getCharacterEncoding() {
 48         return request.getCharacterEncoding();
 49     }
 50
 51     public int getContentLength() {
 52         return request.getContentLength();
 53     }
 54
 55     public String getContentType() {
 56         return request.getContentType();
 57     }
 58
 59     public ServletInputStream getInputStream() throws IOException {
 60         return request.getInputStream();
 61     }
 62
 63     public Locale getLocale() {
 64         return request.getLocale();
 65     }
 66
 67     public Enumeration getLocales() {
 68         return request.getLocales();
 69     }
 70
 71     public String getParameter(String name) {
 72         return request.getParameter(name);
 73     }
 74
 75     public Map getParameterMap() {
 76         return request.getParameterMap();
 77     }
 78
 79     public Enumeration getParameterNames() {
 80         return request.getParameterNames();
 81     }
 82
 83     public String[] getParameterValues(String parameter) {
 84         return request.getParameterValues(parameter);
 85     }
 86
 87     public String getProtocol() {
 88         return request.getProtocol();
 89     }
 90
 91     public BufferedReader getReader() throws IOException {
 92         return request.getReader();
 93     }
 94
 95     public String getRemoteAddr() {
 96         return request.getRemoteAddr();
 97     }
 98
 99     public String getRemoteHost() {
100         return request.getRemoteHost();
101     }
102
103     public String getScheme() {
104         return request.getScheme();
105     }
106
107     public String getServerName() {
108         return request.getServerName();
109     }
110
111     public int getServerPort() {
112         return request.getServerPort();
113     }
114
115     public void removeAttribute(String attribute) {
116         request.removeAttribute(attribute);
117     }
118
119     public void setAttribute(String key, Object value) {
120         request.setAttribute(key, value);
121     }
122
123     public void setCharacterEncoding(String encoding)
124             throws UnsupportedEncodingException {
125         request.setCharacterEncoding(encoding);
126     }
127
128     @Override
129     public int getRemotePort() {
130         // TODO Auto-generated method stub
131         return 0;
132     }
133
134     @Override
135     public String getLocalName() {
136         // TODO Auto-generated method stub
137         return null;
138     }
139
140     @Override
141     public String getLocalAddr() {
142         // TODO Auto-generated method stub
143         return null;
144     }
145
146     @Override
147     public int getLocalPort() {
148         // TODO Auto-generated method stub
149         return 0;
150     }
151
152     @Override
153     public ServletContext getServletContext() {
154         // TODO Auto-generated method stub
155         return null;
156     }
157
158     @Override
159     public AsyncContext startAsync() {
160         // TODO Auto-generated method stub
161         return null;
162     }
163
164     @Override
165     public AsyncContext startAsync(ServletRequest servletrequest,
166             ServletResponse servletresponse) {
167         // TODO Auto-generated method stub
168         return null;
169     }
170
171     @Override
172     public boolean isAsyncStarted() {
173         // TODO Auto-generated method stub
174         return false;
175     }
176
177     @Override
178     public boolean isAsyncSupported() {
179         // TODO Auto-generated method stub
180         return false;
181     }
182
183     @Override
184     public AsyncContext getAsyncContext() {
185         // TODO Auto-generated method stub
186         return null;
187     }
188
189     @Override
190     public DispatcherType getDispatcherType() {
191         // TODO Auto-generated method stub
192         return null;
193     }
194
195 }

ResponseFacade.java

 1 package ex02.pyrmont;
 2
 3 import java.io.IOException;
 4 import java.io.PrintWriter;
 5 import java.util.Locale;
 6
 7 import javax.servlet.ServletResponse;
 8 import javax.servlet.ServletOutputStream;
 9
10 public class ResponseFacade implements ServletResponse {
11
12     private ServletResponse response;
13
14     public ResponseFacade(Response response) {
15         this.response = response;
16     }
17
18     public void flushBuffer() throws IOException {
19         response.flushBuffer();
20     }
21
22     public int getBufferSize() {
23         return response.getBufferSize();
24     }
25
26     public String getCharacterEncoding() {
27         return response.getCharacterEncoding();
28     }
29
30     public Locale getLocale() {
31         return response.getLocale();
32     }
33
34     public ServletOutputStream getOutputStream() throws IOException {
35         return response.getOutputStream();
36     }
37
38     public PrintWriter getWriter() throws IOException {
39         return response.getWriter();
40     }
41
42     public boolean isCommitted() {
43         return response.isCommitted();
44     }
45
46     public void reset() {
47         response.reset();
48     }
49
50     public void resetBuffer() {
51         response.resetBuffer();
52     }
53
54     public void setBufferSize(int size) {
55         response.setBufferSize(size);
56     }
57
58     public void setContentLength(int length) {
59         response.setContentLength(length);
60     }
61
62     public void setContentType(String type) {
63         response.setContentType(type);
64     }
65
66     public void setLocale(Locale locale) {
67         response.setLocale(locale);
68     }
69
70     @Override
71     public String getContentType() {
72         // TODO Auto-generated method stub
73         return null;
74     }
75
76     @Override
77     public void setCharacterEncoding(String s) {
78         // TODO Auto-generated method stub
79
80     }
81
82 }

最后根据上面的描述,ServletProcess1.java改成ServletProcess2.java,使用RequestFacade和ResponseFacade进行封装

改变的内容不多,如下

1  Servlet servlet = null;
2     RequestFacade requestFacade = new RequestFacade(request);
3     ResponseFacade responseFacade = new ResponseFacade(response);
4     try {
5       servlet = (Servlet) myClass.newInstance();
6       servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
7     }

当然,HttpServer1.java改为HttpServer2.java,改变之处只有一个地方,即创建ServletProcess1实例改成ServletProcess2实例

这一节就到这里,我们完成对servlet请求和静态资源请求分别处理的简单实现,相比上一节难度大了一些。下一节将开始Tomcat的连接器。

如果有什么疑问或错误,可以发表评论或者加我QQ:1096101803告知,谢谢。

附:

相应代码可以在我的github上找到下载,拷贝到eclipse,然后打开对应包的代码即可。

https://github.com/zebinlin/Tomcat4-src

如发现编译错误,可能是由于jdk不同版本对编译的要求不同导致的,可以不管,供学习研究使用。

时间: 2024-10-21 10:52:12

Tomcat剖析(二)的相关文章

Tomcat剖析(五):Tomcat 容器

Tomcat剖析(五):Tomcat 容器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器(1) 4. Tomcat剖析(三):连接器(2) 5. Tomcat剖析(四):Tomcat默认连接器(1) 6. Tomcat剖析(四):Tomcat默认连接器(2) 7. Tomcat剖析(五):容器 第一部分:概述 这一节基于<深度剖析Tomcat>第五章:容器 总结而成 一定要先到我的git

Tomcat剖析(四):Tomcat默认连接器(2)

Tomcat剖析(四):Tomcat默认连接器(2) 目录: Tomcat剖析(一):一个简单的Web服务器 Tomcat剖析(二):一个简单的Servlet服务器 Tomcat剖析(三):连接器(1) Tomcat剖析(三):连接器(2) Tomcat剖析(四):Tomcat默认连接器(1) Tomcat剖析(四):Tomcat默认连接器(2) 同样的,你需要先下载代码. https://github.com/zebinlin/Tomcat4-src 经过上一节的讲解,你已经理解了请求和响应对

Tomcat剖析(一)

Tomcat剖析(一) 这一节基于 <深度剖析Tomcat>第一章:一个简单的Web服务器 总结而成.写得不好之处,请见谅 对Tomcat而言,如果直接对其源码进行分析是困难的,所以这里被设计得足够简单使得你能理解一个 servlet 容器是如何工作的,没有对Tomcat本身的连接器和容器进行分析,本节旨在明白服务器的整个流程大致是如何进行的.需要知道如何完善Web服务器,可以参考这本书后面的章节,或者等我发表<Tomcat剖析(二)>. 文中细说明是从书中相关章节中copy下来的

Tomcat剖析(三):连接器

Tomcat剖析(三) 目录: Tomcat剖析(一):一个简单的Web服务器 Tomcat剖析(二):一个简单的Servlet服务器 Tomcat剖析(三):连接器 大家都知道Catalina 中有两个主要的模块:连接器和容器.本节将HttpServer2完善为HttpConnect,创建一个更好的请求和响应对象的连接器,不仅仅是简简单单的调用自己的await方法,而是用线程启动. 上一节是使用Reques对象,因为我们不知道请求的类型,而这一节中,如果是HTTP请求,那么Request对象中

Tomcat专题二: JDK安装以及tomcat基本环境安装和使用

tomcat专题二:jdk安装以及tomcat基本安装和使用 书接tomcat专题一,在这一节将介绍一下java运行环境的安装以及tomcat的基本安装和使用.可能有人会问安装tomcat跟java运行环境有什么关系?正像专题一介绍的那样,tomcat本身只是一个业务框架,一个WEB容器,其底层还是基于jvm虚拟机来运行java程序的字节码文件.可以认为tomcat负责接受上层的应用请求,然后将请求的java程序交与jvm虚拟机执行并返回结果,这之间形成了一个调用关系,这在下面的tomcat启动

libevent源码深度剖析二

libevent源码深度剖析二 --Reactor模式 张亮 前面讲到,整个libevent本身就是一个Reactor,因此本节将专门对Reactor模式进行必要的介绍,并列出libevnet中的几个重要组件和Reactor的对应关系,在后面的章节中可能还会提到本节介绍的基本概念. 1 Reactor的事件处理机制 首先来回想一下普通函数调用的机制:程序调用某函数?函数执行,程序等待?函数将结果和控制权返回给程序?程序继续处理. Reactor释义"反应堆",是一种事件驱动机制.和普通

boost.asio源码剖析(二) ---- 架构浅析

* 架构浅析 先来看一下asio的0层的组件图.                     (图1.0) io_object是I/O对象的集合,其中包含大家所熟悉的socket.deadline_timer等对象,主要功能是提供接口给用户使用. services服务是逻辑功能的实现者,其中包含提供定时功能的deadline_timer_service.提供socket相关功能的win_iocp_socket_service(windows平台)/reactive_socket_service(其他

【高性能服务器】Tomcat剖析

引言 Tomcat是一个流行的servlet容器,对于开发人员来说整体和容器打交道有必要花一些时间爱你了解其内部结构.本文将从一下几个方面来剖析其内部结构. 整体结构 连接器 初始化过程 如何处理一个请求 容器 Session管理 设计模式 Context Wrapper 整体结构 首先我们先来看一下Tomcat的整体结构.如下图所示,整个tomcat容器的顶层是server对象,下属多个service.每个service有自己的连接器与容器组合[1].其中连接器负责处理网络连接,并将包装过的数

JavaWeb(三)——Tomcat服务器(二)

一.打包JavaWeb应用 在Java中,使用"jar"命令来对将JavaWeb应用打包成一个War包,jar命令的用法如下: 范例:将JavaWebDemoProject这个JavaWeb应用打包成war包 执行完之后,就可以得到一个文件,平时开发完JavaWeb应用后,一般都会将JavaWeb应用打包成一个war包,然后将这个war包放到Tomcat服务器的webapps目录下,当Tomcat服务器启动时,就会自动将webapps目录下的war包解压. 比如现在将放到放到Tomca