How Tomcat Works学习笔记
转载自:http://www.cnblogs.com/timlearn/p/4132783.html
0、servlet容器是如何工作的
servlet容器是一个复杂的系统,但是,它有3个基本任务,对每个请求,servlet容器会为其完成以下三个操作: 1:创建一个request对象,用可能会在Servlet中使用到的信息填充该request对象,如参数、头、cookie、查询字符串、URI等。request对象是javax.servlrt.ServletRequest接口或javax.servlet.http.ServletRequest接口的一个实例。
2:创建一个response对象,用来向Web客户端发送响应,response对象是javax.servlrt.ServletResponse接口或javax.servlet.http.ServletResponse接口的一个实例。
3:调用Servlet的service方法,将request对象和response对象作为参数传入,Servlet从request对象中读取信息,并通过response对象发送响应信息。
1、一个简单的Web容器
首先创建一个HttpServer对象,这个对象最重要的方法是await方法,该方法会创建一个ServerSocket对象,然后调用该对象的accept()方法等待客户端连接,如果有客户端连接,则相应创建Request和Response对象,并且通过response的sendStaticResource()方法向客户端发送响应。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
public class HttpServer { public static final String WEB_ROOT = System.getProperty( "user.dir" ) + File.separator + "webroot" ; private static final String SHUTDOWN_COMMAND = "/SHUTDOWN" ; private boolean shutdown = false ; public static void main(String[] args) { HttpServer server = new HttpServer(); server.await(); } public void await() { ServerSocket serverSocket = null ; int port = 8080 ; try { serverSocket = new ServerSocket(port, 1 , InetAddress.getByName( "127.0.0.1" )); } catch (IOException e) { e.printStackTrace(); System.exit( 1 ); } while (!shutdown) { Socket socket = null ; InputStream input = null ; OutputStream output = null ; try { socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); Request request = new Request(input); Response response = new Response(output); response.setRequest(request); response.sendStaticResource(); socket.close(); shutdown = request.getUri().equals(SHUTDOWN_COMMAND); } catch (Exception e) { e.printStackTrace(); continue ; } } } } |
Request对象如下,parse()方法用于得到客户端的输入并打印输出,parseUri()方法用于得到客户端的请求路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public class Request { private InputStream input; private String uri; public Request(InputStream input) { this .input = input; } public void parse() { StringBuffer request = new StringBuffer( 2048 ); int i; byte [] buffer = new byte [ 2048 ]; try { i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = - 1 ; } for ( int j = 0 ; j < i; j++) { request.append(( char ) buffer[j]); } System.out.print(request.toString()); uri = parseUri(request.toString()); } private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf( ' ' ); if (index1 != - 1 ) { index2 = requestString.indexOf( ' ' , index1 + 1 ); if (index2 > index1) return requestString.substring(index1 + 1 , index2); } return null ; } public String getUri() { return uri; } } |
Response对象最重要的方法是sendStaticResource()方法,该方法通过调用Request对象的getUri()方法得到客户端请求文件路径,然后读取文件并发送给用户:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public class Response { private static final int BUFFER_SIZE = 1024 ; Request request; OutputStream output; public Response(OutputStream output) { this .output = output; } public void setRequest(Request request) { this .request = request; } public void sendStaticResource() throws IOException { byte [] bytes = new byte [BUFFER_SIZE]; FileInputStream fis = null ; try { File file = new File(HttpServer.WEB_ROOT, request.getUri()); if (file.exists()) { fis = new FileInputStream(file); int ch = fis.read(bytes, 0 , BUFFER_SIZE); while (ch != - 1 ) { output.write(bytes, 0 , ch); ch = fis.read(bytes, 0 , BUFFER_SIZE); } } else { String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>" ; output.write(errorMessage.getBytes()); } } catch (Exception e) { System.out.println(e.toString()); } finally { if (fis != null ) fis.close(); } } } |
启动HttpServer,然后在浏览器输入localhost:8080/index.html即可看到一个静态网页输出。
2、一个简单的servlet容器
《How Tomcat Works》第二章,太长了,不写了。
3、连接器
本章的应用程序包含三个模块:连接器模块、启动模块和核心模块。
启动模块就只有一个类Bootstrap,负责启动应用程序。
核心模块有两个类,servletProcessor类和StaticResourceProcessor类,前者处理Servlet请求,后者处理静态资源请求。
连接器模块是这章的重点,包含以下5个类型:
1:连接器及其支持类(HttpConnector和HttpProcessor);
2:表示HTTP请求的类(HttpRequest)及其支持类;
3:表示HTTP响应的类(HttpResponse)及其支持类;
4:外观类(HttpRequestFacade和HTTPResponseFacade);
5:常量类。
Bootstrap类是整个应用程序的启动类,该类定义如下:
1
2
3
4
5
6
|
public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); connector.start(); } } |
Bootstrap类通过实例化HttpConnector类并调用其start()方法启动应用程序,HttpConector类的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public class HttpConnector implements Runnable { boolean stopped; private String scheme = "http" ; public String getScheme() { return scheme; } public void run() { ServerSocket serverSocket = null ; int port = 8080 ; try { serverSocket = new ServerSocket(port, 1 ,InetAddress.getByName( "127.0.0.1" )); } catch (IOException e) { e.printStackTrace(); System.exit( 1 ); } while (!stopped) { Socket socket = null ; try { socket = serverSocket.accept(); } catch (Exception e) { continue ; } HttpProcessor processor = new HttpProcessor( this ); processor.process(socket); } } public void start() { Thread thread = new Thread( this ); thread.start(); } } |
HttpConector主要做以下事情:等待HTTP请求,为每个请求创建一个HttpProcessor实例,调用HttpProcessor对象的process()方法。HttpProcessor的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
public class HttpProcessor { public HttpProcessor(HttpConnector connector) { this .connector = connector; } private HttpConnector connector = null ; private HttpRequest request; private HttpRequestLine requestLine = new HttpRequestLine(); private HttpResponse response; protected String method = null ; protected String queryString = null ; protected StringManager sm = StringManager.getManager( "ex03.pyrmont.connector.http" ); public void process(Socket socket) { SocketInputStream input = null ; OutputStream output = null ; try { input = new SocketInputStream(socket.getInputStream(), 2048 ); output = socket.getOutputStream(); request = new HttpRequest(input); response = new HttpResponse(output); response.setRequest(request); response.setHeader( "Server" , "Pyrmont Servlet Container" ); parseRequest(input, output); parseHeaders(input); if (request.getRequestURI().startsWith( "/servlet/" )) { ServletProcessor processor = new ServletProcessor(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } socket.close(); } catch (Exception e) { e.printStackTrace(); } } private void parseHeaders(SocketInputStream input) throws IOException, ServletException { while ( true ) { HttpHeader header = new HttpHeader();; input.readHeader(header); if (header.nameEnd == 0 ) { if (header.valueEnd == 0 ) { return ; } else { throw new ServletException (sm.getString( "httpProcessor.parseHeaders.colon" )); } } String name = new String(header.name, 0 , header.nameEnd); String value = new String(header.value, 0 , header.valueEnd); request.addHeader(name, value); if (name.equals( "cookie" )) { Cookie cookies[] = RequestUtil.parseCookieHeader(value); for ( int i = 0 ; i < cookies.length; i++) { if (cookies[i].getName().equals( "jsessionid" )) { if (!request.isRequestedSessionIdFromCookie()) { request.setRequestedSessionId(cookies[i].getValue()); request.setRequestedSessionCookie( true ); request.setRequestedSessionURL( false ); } } request.addCookie(cookies[i]); } } else if (name.equals( "content-length" )) { int n = - 1 ; try { n = Integer.parseInt(value); } catch (Exception e) { throw new ServletException(sm.getString( "httpProcessor.parseHeaders.contentLength" )); } request.setContentLength(n); } else if (name.equals( "content-type" )) { request.setContentType(value); } } //end while } private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException { input.readRequestLine(requestLine); String method = new String(requestLine.method, 0 , requestLine.methodEnd); String uri = null ; String protocol = new String(requestLine.protocol, 0 , requestLine.protocolEnd); if (method.length() < 1 ) { throw new ServletException( "Missing HTTP request method" ); } else if (requestLine.uriEnd < 1 ) { throw new ServletException( "Missing HTTP request URI" ); } int question = requestLine.indexOf( "?" ); if (question >= 0 ) { request.setQueryString( new String(requestLine.uri, question + 1 , requestLine.uriEnd - question - 1 )); uri = new String(requestLine.uri, 0 , question); } else { request.setQueryString( null ); uri = new String(requestLine.uri, 0 , requestLine.uriEnd); } if (!uri.startsWith( "/" )) { int pos = uri.indexOf( "://" ); if (pos != - 1 ) { pos = uri.indexOf( '/' , pos + 3 ); if (pos == - 1 ) { uri = "" ; } else { uri = uri.substring(pos); } } } String match = ";jsessionid=" ; int semicolon = uri.indexOf(match); if (semicolon >= 0 ) { String rest = uri.substring(semicolon + match.length()); int semicolon2 = rest.indexOf( ';' ); if (semicolon2 >= 0 ) { request.setRequestedSessionId(rest.substring( 0 , semicolon2)); rest = rest.substring(semicolon2); } else { request.setRequestedSessionId(rest); rest = "" ; } request.setRequestedSessionURL( true ); uri = uri.substring( 0 , semicolon) + rest; } else { request.setRequestedSessionId( null ); request.setRequestedSessionURL( false ); } String normalizedUri = normalize(uri); ((HttpRequest) request).setMethod(method); request.setProtocol(protocol); if (normalizedUri != null ) { ((HttpRequest) request).setRequestURI(normalizedUri); } else { ((HttpRequest) request).setRequestURI(uri); } if (normalizedUri == null ) { throw new ServletException( "Invalid URI: " + uri + "'" ); } } protected String normalize(String path) { if (path == null ) return null ; String normalized = path; if (normalized.startsWith( "/%7E" ) || normalized.startsWith( "/%7e" )) normalized = "/~" + normalized.substring( 4 ); if ((normalized.indexOf( "%25" ) >= 0 ) || (normalized.indexOf( "%2F" ) >= 0 ) || (normalized.indexOf( "%2E" ) >= 0 ) || (normalized.indexOf( "%5C" ) >= 0 ) || (normalized.indexOf( "%2f" ) >= 0 ) || (normalized.indexOf( "%2e" ) >= 0 ) || (normalized.indexOf( "%5c" ) >= 0 )) { return null ; } if (normalized.equals( "/." )) return "/" ; if (normalized.indexOf( '\\' ) >= 0 ) normalized = normalized.replace( '\\' , '/' ); if (!normalized.startsWith( "/" )) normalized = "/" + normalized; while ( true ) { int index = normalized.indexOf( "//" ); if (index < 0 ) break ; normalized = normalized.substring( 0 , index) + normalized.substring(index + 1 ); } while ( true ) { int index = normalized.indexOf( "/./" ); if (index < 0 ) break ; normalized = normalized.substring( 0 , index) + normalized.substring(index + 2 ); } while ( true ) { int index = normalized.indexOf( "/../" ); if (index < 0 ) break ; if (index == 0 ) return ( null ); // Trying to go outside our context int index2 = normalized.lastIndexOf( '/' , index - 1 ); normalized = normalized.substring( 0 , index2) + normalized.substring(index + 3 ); } if (normalized.indexOf( "/..." ) >= 0 ) return ( null ); return (normalized); } } |
对于HttpProcessor对象,我们只需要重点关注process()方法即可,其他方法都是是有方法,供process()方法调用,该类的process()方法完成了以下4个操作:创建一个HttpRequest对象,创建一个HttpResponse对象,调用parseRequest()方法和parseHeader()方法填充HttpRequest对象,将HttpRequest对象和HttpResponse对象传递给ServletProcessor对象或StaticResourceProcessor对象的process()方法。
接下来就是讲解Request和Response的实现了,但是代码太长,不写了。其实Request和Response对象的实现就是对HTTP协议的实现,此处略。接下来我们查看ServletProcessor的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public class ServletProcessor { public void process(HttpRequest request, HttpResponse response) { String uri = request.getRequestURI(); String servletName = uri.substring(uri.lastIndexOf( "/" ) + 1 ); URLClassLoader loader = null ; try { URL[] urls = new URL[ 1 ]; URLStreamHandler streamHandler = null ; File classPath = new File(Constants.WEB_ROOT); String repository = ( new URL( "file" , null , classPath.getCanonicalPath() + File.separator)).toString() ; 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(); HttpRequestFacade requestFacade = new HttpRequestFacade(request); HttpResponseFacade responseFacade = new HttpResponseFacade(response); servlet.service(requestFacade, responseFacade); ((HttpResponse) response).finishResponse(); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } } } |
此处,使用类加载器加载了一个类,然后调用newInstance()方法进行初始化类,初始化后调用该类的service()方法。再查看StaticResourceProcessor的实现,该类直接调用Response的sendStaticResource()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class StaticResourceProcessor { public void process(HttpRequest request, HttpResponse response) { try { response.sendStaticResource(); } catch (IOException e) { e.printStackTrace(); } } } /* This method is used to serve a static page */ public void sendStaticResource() throws IOException { byte [] bytes = new byte [BUFFER_SIZE]; FileInputStream fis = null ; try { File file = new File(Constants.WEB_ROOT, request.getRequestURI()); fis = new FileInputStream(file); int ch = fis.read(bytes, 0 , BUFFER_SIZE); while (ch!=- 1 ) { output.write(bytes, 0 , ch); ch = fis.read(bytes, 0 , BUFFER_SIZE); } } catch (FileNotFoundException e) { String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>" ; output.write(errorMessage.getBytes()); } finally { if (fis!= null ) fis.close(); } } |
4、Tomcat的默认连接器
上一节讲的连接器已经可以工作,但是那个只是一个学习工具,本节将介绍并直接使用Tomcat 4的默认连接器。本节的应用程序将直接使用默认连接器,该应用程序包含两个类:SimpleContainer和Bootstrap类。SimpleContainer类实现了org.apache.catalina.Container接口,这样它就可以与默认连接器关联:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
public class SimpleContainer implements Container { public static final String WEB_ROOT = System.getProperty( "user.dir" ) + File.separator + "webroot" ; public SimpleContainer() { } public String getInfo() { return null ; } public Loader getLoader() { return null ; } public void setLoader(Loader loader) { } public Logger getLogger() { return null ; } public void setLogger(Logger logger) { } public Manager getManager() { return null ; } public void setManager(Manager manager) { } public Cluster getCluster() { return null ; } public void setCluster(Cluster cluster) { } public String getName() { return null ; } public void setName(String name) { } public Container getParent() { return null ; } public void setParent(Container container) { } public ClassLoader getParentClassLoader() { return null ; } public void setParentClassLoader(ClassLoader parent) { } public Realm getRealm() { return null ; } public void setRealm(Realm realm) { } public DirContext getResources() { return null ; } public void setResources(DirContext resources) { } public void addChild(Container child) { } public void addContainerListener(ContainerListener listener) { } public void addMapper(Mapper mapper) { } public void addPropertyChangeListener(PropertyChangeListener listener) { } public Container findChild(String name) { return null ; } public Container[] findChildren() { return null ; } public ContainerListener[] findContainerListeners() { return null ; } public Mapper findMapper(String protocol) { return null ; } public Mapper[] findMappers() { return null ; } public void invoke(Request request, Response response) throws IOException, ServletException { String servletName = ( (HttpServletRequest) request).getRequestURI(); servletName = servletName.substring(servletName.lastIndexOf( "/" ) + 1 ); URLClassLoader loader = null ; try { URL[] urls = new URL[ 1 ]; URLStreamHandler streamHandler = null ; File classPath = new File(WEB_ROOT); String repository = ( new URL( "file" , null , classPath.getCanonicalPath() + File.separator)).toString() ; 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((HttpServletRequest) request, (HttpServletResponse) response); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } } public Container map(Request request, boolean update) { return null ; } public void removeChild(Container child) { } public void removeContainerListener(ContainerListener listener) { } public void removeMapper(Mapper mapper) { } public void removePropertyChangeListener(PropertyChangeListener listener) { } } |
可以看到,这个类只是Container接口的简单实现,所以里面很多方法都未实现,只实现了一个invoke()方法。Bootstrap类的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); SimpleContainer container = new SimpleContainer(); connector.setContainer(container); try { connector.initialize(); connector.start(); // make the application wait until we press any key. System.in.read(); } catch (Exception e) { e.printStackTrace(); } } } |
注意,现在代码中去除了处理静态资源的代码,通过链接http://localhost:8080/servlet/ModernServlet可以查看Servlet内容。
5、Servlet容器
在Tomcat中共有4种容器,分别是Engine,Host,Context和Wrapper。
管道任务
Tomcat4中管道任务相关的类有以下几个:Pipeline、Value、ValueContext、Contained。管道任务类似于servlet中的过滤器,Pipeline是过滤链,Value类似过滤器,可以通过编辑server.xml动态添加阀(Value),当一个阀(Value)调用完后会调用下一个阀(Value),基础阀总是最后一个调用。一个Servlet容器可以有一条管道(Pipeline),当调用容器的invoke方法后,容器会调用管道的invoke()方法,管道再调用阀的invoke()方法。同时Tomcat还引入例如ValueContext接口用于控制阀的遍历。
Context应用程序
一个Context容器代表一个应用程序,本节的例子将介绍一个包含两个Wrapper实例的Context实例。这一节最精髓的就是Bootstrap类,该类的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public final class Bootstrap2 { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); Wrapper wrapper1 = new SimpleWrapper(); wrapper1.setName( "Primitive" ); wrapper1.setServletClass( "PrimitiveServlet" ); Wrapper wrapper2 = new SimpleWrapper(); wrapper2.setName( "Modern" ); wrapper2.setServletClass( "ModernServlet" ); Context context = new SimpleContext(); context.addChild(wrapper1); context.addChild(wrapper2); Valve valve1 = new HeaderLoggerValve(); Valve valve2 = new ClientIPLoggerValve(); ((Pipeline) context).addValve(valve1); ((Pipeline) context).addValve(valve2); Mapper mapper = new SimpleContextMapper(); mapper.setProtocol( "http" ); context.addMapper(mapper); Loader loader = new SimpleLoader(); context.setLoader(loader); context.addServletMapping( "/Primitive" , "Primitive" ); context.addServletMapping( "/Modern" , "Modern" ); connector.setContainer(context); try { connector.initialize(); connector.start(); System.in.read(); } catch (Exception e) { e.printStackTrace(); } } } |
首先创建了两个Wrapper对象,每个Wrapper对象有name和servletClass属性,然后把两个wrapper对象加入Context容器,为Context容器增加管道,接着创建映射器,说明每个请求对应的Servlet class。
还有需要注意的是,SimpleContext类和SimpleWrapper类的构造函数,在这两个构造函数中指定了基础阀:
1
2
3
4
5
6
|
public SimpleContext() { pipeline.setBasic( new SimpleContextValve()); } public SimpleWrapper() { pipeline.setBasic( new SimpleWrapperValve()); } |
6、生命周期
Catalina在设计上允许一个组件包含其他组件,例如servlet容器可以包含载入器、管理器等,父组件负责负责启动/关闭它的子组件。Lifecycle接口如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public interface Lifecycle { public static final String START_EVENT = "start" ; public static final String BEFORE_START_EVENT = "before_start" ; public static final String AFTER_START_EVENT = "after_start" ; public static final String STOP_EVENT = "stop" ; public static final String BEFORE_STOP_EVENT = "before_stop" ; public static final String AFTER_STOP_EVENT = "after_stop" ; public void addLifecycleListener(LifecycleListener listener); public LifecycleListener[] findLifecycleListeners(); public void removeLifecycleListener(LifecycleListener listener); public void start() throws LifecycleException; public void stop() throws LifecycleException; } |
本章的SimpleContext类与上一节的SimpleContext类十分类似,区别在于本章的SimpleContext实现了Lifecycle接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
public class SimpleContext implements Context, Pipeline, Lifecycle { public SimpleContext() { pipeline.setBasic( new SimpleContextValve()); } protected LifecycleSupport lifecycle = new LifecycleSupport( this ); public void addLifecycleListener(LifecycleListener listener) { lifecycle.addLifecycleListener(listener); } public LifecycleListener[] findLifecycleListeners() { return null ; } public void removeLifecycleListener(LifecycleListener listener) { lifecycle.removeLifecycleListener(listener); } public synchronized void start() throws LifecycleException { if (started) throw new LifecycleException( "SimpleContext has already started" ); lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null ); started = true ; try { if ((loader != null ) && (loader instanceof Lifecycle)) ((Lifecycle) loader).start(); Container children[] = findChildren(); for ( int i = 0 ; i < children.length; i++) { if (children[i] instanceof Lifecycle) ((Lifecycle) children[i]).start(); } if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); lifecycle.fireLifecycleEvent(START_EVENT, null ); } catch (Exception e) { e.printStackTrace(); } lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null ); } public void stop() throws LifecycleException { if (!started) throw new LifecycleException( "SimpleContext has not been started" ); lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null ); lifecycle.fireLifecycleEvent(STOP_EVENT, null ); started = false ; try { if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).stop(); } Container children[] = findChildren(); for ( int i = 0 ; i < children.length; i++) { if (children[i] instanceof Lifecycle) ((Lifecycle) children[i]).stop(); } if ((loader != null ) && (loader instanceof Lifecycle)) { ((Lifecycle) loader).stop(); } } catch (Exception e) { e.printStackTrace(); } lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null ); } } |
可以看到SimpleContext在start()/stop()时候也会把他所有的子组件全部启动/关闭。Bootstrap类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public final class Bootstrap { public static void main(String[] args) { Connector connector = new HttpConnector(); Wrapper wrapper1 = new SimpleWrapper(); wrapper1.setName( "Primitive" ); wrapper1.setServletClass( "PrimitiveServlet" ); Wrapper wrapper2 = new SimpleWrapper(); wrapper2.setName( "Modern" ); wrapper2.setServletClass( "ModernServlet" ); Context context = new SimpleContext(); context.addChild(wrapper1); context.addChild(wrapper2); Mapper mapper = new SimpleContextMapper(); mapper.setProtocol( "http" ); LifecycleListener listener = new SimpleContextLifecycleListener(); ((Lifecycle) context).addLifecycleListener(listener); context.addMapper(mapper); Loader loader = new SimpleLoader(); context.setLoader(loader); context.addServletMapping( "/Primitive" , "Primitive" ); context.addServletMapping( "/Modern" , "Modern" ); connector.setContainer(context); try { connector.initialize(); ((Lifecycle) connector).start(); ((Lifecycle) context).start(); // make the application wait until we press a key. System.in.read(); ((Lifecycle) context).stop(); } catch (Exception e) { e.printStackTrace(); } } } |
在Bootstrap类中,我们调用context的start()方法启动应用,同时我们也在SimpleContext类上增加了一个监听器。
7、日志记录器
日志记录器是用来记录消息的组件,本节应用程序与上节相比,多了以下内容:
1
2
3
4
5
6
7
8
9
|
// ------ add logger -------- System.setProperty( "catalina.base" , System.getProperty( "user.dir" )); FileLogger logger = new FileLogger(); logger.setPrefix( "FileLog_" ); logger.setSuffix( ".txt" ); logger.setTimestamp( true ); logger.setDirectory( "webroot" ); context.setLogger(logger); //--------------------------- |
8、载入器
对于类加载器,每个Context都有一个类加载器,出于安全考虑,我们不能直接使用系统类加载器,而应该实现自己的类加载器,只允许servlet加载WEB-INF/classes目录下的类。这一章的应用程序直接使用StandardContext类和WebappLoader类,可以看到,这两个Catalina类的使用方法与我们自己写的类使用方法差别并不大:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public final class Bootstrap { public static void main(String[] args) { System.setProperty( "catalina.base" , System.getProperty( "user.dir" )); Connector connector = new HttpConnector(); Wrapper wrapper1 = new SimpleWrapper(); wrapper1.setName( "Primitive" ); wrapper1.setServletClass( "PrimitiveServlet" ); Wrapper wrapper2 = new SimpleWrapper(); wrapper2.setName( "Modern" ); wrapper2.setServletClass( "ModernServlet" ); Context context = new StandardContext(); // StandardContext's start method adds a default mapper context.setPath( "/myApp" ); context.setDocBase( "myApp" ); context.addChild(wrapper1); context.addChild(wrapper2); context.addServletMapping( "/Primitive" , "Primitive" ); context.addServletMapping( "/Modern" , "Modern" ); LifecycleListener listener = new SimpleContextConfig(); ((Lifecycle) context).addLifecycleListener(listener); Loader loader = new WebappLoader(); context.setLoader(loader); connector.setContainer(context); try { connector.initialize(); ((Lifecycle) connector).start(); ((Lifecycle) context).start(); // now we want to know some details about WebappLoader WebappClassLoader classLoader = (WebappClassLoader) loader.getClassLoader(); System.out.println( "Resources' docBase: " + ((ProxyDirContext)classLoader.getResources()).getDocBase()); String[] repositories = classLoader.findRepositories(); for ( int i= 0 ; i<repositories.length; i++) { System.out.println( " repository: " + repositories[i]); } // make the application wait until we press a key. System.in.read(); ((Lifecycle) context).stop(); } catch (Exception e) { e.printStackTrace(); } } } |
9、Session管理
Catalina通过一个称为Session管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口表示,Session管理器需要与一个Context容器相关联,且必须与一个Context容器关联,在该组件内部会有一个hashmap用于存储session:
1
2
|
HashMap sessions = new HashMap(); sessions.put(session.getId(), session); |
这一节的Bootstrap类中增加了Session管理器:
1
2
3
|
// add a Manager Manager manager = new StandardManager(); context.setManager(manager); |
同时,为了支持request.getSession操作,需要修改SimpleWrapperValue基础阀,在request中设置Context属性:
1
2
3
|
//-- new addition ----------------------------------- Context context = (Context) wrapper.getParent(); request.setContext(context); |
10、安全性
这一章讲解的功能从没用过,略。
11、StandardWrapper
讲解StandardWrapper类的一些细节,具体内容还是看书吧。
12、StandardContext
讲解StandardContext类的细节,不存在代码变更。
13、Host和Engine
如果需要在Tomcat上部署多个Context容器,那就需要使用Host容器。Engine表示整个servlet引擎,一个Engine可以有多个Host子容器。下面是一个使用Engine作为顶层容器的简单例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public final class Bootstrap2 { public static void main(String[] args) { System.setProperty( "catalina.base" , System.getProperty( "user.dir" )); Connector connector = new HttpConnector(); Wrapper wrapper1 = new StandardWrapper(); wrapper1.setName( "Primitive" ); wrapper1.setServletClass( "PrimitiveServlet" ); Wrapper wrapper2 = new StandardWrapper(); wrapper2.setName( "Modern" ); wrapper2.setServletClass( "ModernServlet" ); Context context = new StandardContext(); context.setPath( "/app1" ); context.setDocBase( "app1" ); context.addChild(wrapper1); context.addChild(wrapper2); LifecycleListener listener = new SimpleContextConfig(); ((Lifecycle) context).addLifecycleListener(listener); Host host = new StandardHost(); host.addChild(context); host.setName( "localhost" ); host.setAppBase( "webapps" ); Loader loader = new WebappLoader(); context.setLoader(loader); context.addServletMapping( "/Primitive" , "Primitive" ); context.addServletMapping( "/Modern" , "Modern" ); Engine engine = new StandardEngine(); engine.addChild(host); engine.setDefaultHost( "localhost" ); connector.setContainer(engine); try { connector.initialize(); ((Lifecycle) connector).start(); ((Lifecycle) engine).start(); System.in.read(); ((Lifecycle) engine).stop(); } catch (Exception e) { e.printStackTrace(); } } } |
14、服务器组件和服务组件
服务器组件是非常有用的,因为它使用了一种优雅的方法来启动/关闭整个系统,不需要再对连接器和容器分别启动/关闭。
15、Digester类
Digester是Apache的开源项目,使用Digester可以把XML文档中的元素转换成Java对象,这样就可以基于XML文件进行对象配置了。
16、关闭钩子
JVM在关闭之前会启动所有已注册的关闭钩子,利用这种机制,我们可以确保Tomcat完成所有善后清理工作。
17、启动Tomcat
本文主要讲解启动Tomcat的shell脚本内容。
18、部署器
讲解Host是如何不是Context的,必须清楚的是,真正在Tomcat中,我们不是像上面一样通过硬编码部署进去的。
19、Manager应用程序的servlet类
本章主要讲解如何使用Tomcat自带的Manager应用程序管理已经部署的Web应用程序。
20、基于JMX的管理
本文介绍如何使用JMX管理Tomcat。