首页 > 代码库 > How Tomcat Works学习笔记
How Tomcat Works学习笔记
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()方法向客户端发送响应。
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()方法用于得到客户端的请求路径:
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()方法得到客户端请求文件路径,然后读取文件并发送给用户:
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类是整个应用程序的启动类,该类定义如下:
public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); connector.start(); }}
Bootstrap类通过实例化HttpConnector类并调用其start()方法启动应用程序,HttpConector类的定义如下:
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的定义如下:
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 = http://www.mamicode.com/new String(header.value, 0, header.valueEnd);"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的实现:
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()方法:
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接口,这样它就可以与默认连接器关联:
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类的实现如下:
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类,该类的实现如下:
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类的构造函数,在这两个构造函数中指定了基础阀:
public SimpleContext() { pipeline.setBasic(new SimpleContextValve()); } public SimpleWrapper() { pipeline.setBasic(new SimpleWrapperValve()); }
6、生命周期
Catalina在设计上允许一个组件包含其他组件,例如servlet容器可以包含载入器、管理器等,父组件负责负责启动/关闭它的子组件。Lifecycle接口如下:
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接口:
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类如下:
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、日志记录器
日志记录器是用来记录消息的组件,本节应用程序与上节相比,多了以下内容:
// ------ 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类的使用方法与我们自己写的类使用方法差别并不大:
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:
HashMap sessions = new HashMap();sessions.put(session.getId(), session);
这一节的Bootstrap类中增加了Session管理器:
// add a Manager Manager manager = new StandardManager(); context.setManager(manager);
同时,为了支持request.getSession操作,需要修改SimpleWrapperValue基础阀,在request中设置Context属性:
//-- 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作为顶层容器的简单例子:
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。
How Tomcat Works学习笔记