摘要
对于独立运行的应用程序来说,都有一个入口,以便启动应用程序。Java应用程序的入口是类的main方法,在这里你可以初始化应用的上下文环境,然后创建应用组件并提供服务。对于简单的应用程序,可以直接将启动代码放在main方法中,但是,对于复杂的,或者可扩展的应用来说,这样做是不负责任的,也是不优雅的。那么,怎么做才是负责任的,优雅的?下面我们看看Tomcat是如何启动的,分析一下它的启动框架。希望从中能找到答案。
2.Tomcat启动框架
默认的情况下,在命令行下启动Tomcat,程序的入口是org.apache.catalina.startup.Bootstrap类的main方法,Bootstrap是一个final类,不允许扩展。下面看看main方法,
public static void main(String args[]) {
if (daemon == null) {
daemon = new Bootstrap();
try {
daemon.init();
} catch (Throwable t) {
t.printStackTrace();
return;
}
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[0] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[0] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
t.printStackTrace();
}
}
由代码可知,main方法很简单,就做2件事,一.实例化和初始化Bootstrap,并缓存起来,二.处理命令行参数,根据参数调用不同的操作,此之谓引导。看一下序列图更加清晰:
2.1引导过程
一般来说,在使用一个对象之前,要先初始化好这个对象的状态(就是属性的值),而Bootstrap的初始化也无非干了这些事。
首先初始化属性commonLoader,catalinaLoader,sharedLoader的值,在初始化的过程中先设置了环境变量${catalina.home}和${catalina.base},然后读取配置文件,创建ClassLoader并赋值给属性字段。
接着使用catalinaLoader加载org.apache.catalina.startup.Catalina类并实例化一个对象,并将该对象设置到属性catalinaDaemon中。
当Bootstrap对象初始化完了,就可以接受命令并引导服务了。不同的命令对应不同的Bootstrap动作,而Bootstrap动作委托给Catalina对象来处理。
2.2Catalina
我们知道,Tomcat是面向组件设计的典范,而且组件的装配是可配置的,那么在Tomcat实例启动之前,必须有一个地方来装配这些个组件。Catalina类正好担此重任。Catalina主要负责Tomcat的装配,并在Bootstrap的引导下操作Tomcat实例。
当Bootstrap接到“start”命令后,会引导Catalina对象的load动作装配一个新的Server实例,接着Bootstrap引导Catalina对象启动Server实例。由此可以看出,Catalina是Tomcat实例的装配工厂和引线
Tomcat启动时,首先启动Container,然后启动Connector。而在Connector启动时,会启动协议处理器和映射监听器。协议处理器用来处理具体的协议的,对于http/1.1,其处理器为Http11Protocol,而AJP则为AjpProtocol。当然在协议处理器启动时,会启动监听线程来监听指定端口,从而接受请求。
对于http/1.1协议的处理器Http11Protocol在启动时会创建一个线程对象Acceptor并启动它[http-8080-Acceptor-0],默认监听8080端口的Http请求。当接收到http请求,立即将Socket对象转交给处理线程Work对象[http-8080-1]并返回,准备接受下一个http请求。在如图:
在交由Container处理请求之前,需要先解析一下请求,然后将请求包装一下再往下穿。如果不加处理的将Socket传给Container处理,估计下面的人会疯掉的。如图为解析过程
Work线程将接受到得Socket传给协议处理器的链接处理器来处理,链接处理器创建一个协议处理单元(Http11Processor)来处理请求,并把Socket解析包装成org.apache.coyote.Request,然后交由CoyoteAdapter来处理。CoyoteAdapter将org.apache.coyote.Request包装成org.apache.catalina.connector.Request。之后,请求将org.apache.catalina.connector.Request交由Container来处理。
那么请求是如何被传递的呢?如下图
摘要
对于学习j2ee,且想提高自己Java编程设计水平的每个人来说,研究Tomcat的源码是一件很向往的事,这其中的获益不仅仅是Java编程水平的提高,还有很多其他方面,如j2ee规范的了解,设计模式的运用,产品构建及版本控制等等,这些个方面无一不是我们学习和模拟的对象。但是,当我们开始这段旅程时,往往会迷失于浩瀚的代码海洋之中,这个时候,我们多么希望有这么一盏明灯,指引我们向前进。
在Eclipse中构建Tomcat6.0源码工程
毫不夸张的说,Tomcat 6项目是最友好的项目,原因是它的源码工程构建起来很是方便,它没有使用很先进的maven工具来管理工程,也没有使用通用的IDE来构建工程,而它其实就是一个Eclipse的Java工程,构建时只使用ant。从这一点来看,好像特定了IDE,对贡献者的开发环境要求限制了,但是它却吸引了更多的贡献者。毕竟,像我这样的程序员还是很多,一没钱机子烂,通常只用Eclipse开发,看见Eclipse工程很亲切;二maven不熟练,看见mvn管理的工程就怕怕。
好了,如前文所说,Tomcat 6项目就是一个Eclipse的Java工程,那么我们直接用Eclipse中的SVN插件上http://svn.apache.org/repos/asf/tomcat/tc6.0.x/tags/TOMCAT_6_0_20/导出工程到本地的workspace中。
如果有错,那就是因为classpath中找不到ANT_HOME变量和TOMCAT_LIBS_BASE,重新设置一下ANT_HOME和TOMCAT_LIBS_BASE,如ANT_HOME=D:\JavaTeam\apache-ant-1.7.1,TOMCAT_LIBS_BASE=D:\JavaTeam\eclipse。
如果你没有SVN插件,也没关系,在http://tomcat.apache.org/download-60.cgi下载Tomcat 6.0.20的源码包,然后解压到workspace。
由于源码包没有.project文件和.classpath文件,所以我们要制作一个,什么?制作太麻烦,好吧,那下载一个,在http://svn.apache.org/repos/asf/tomcat/tc6.0.x/tags/TOMCAT_6_0_20/目录下,把.project文件和.classpath文件保存到解开的源码目录(apache-tomcat-6.0.20-src)下,然后,在Eclipse中导入该工程。ok,这下跟刚才的过程一样了。很简单,不是吗?
好了,接下来,就可以开始我们的旅程了哦。