概述:Tomcat是如何工作的?一个Web工程在Tomcat中是如何启动的?Tomcat如何解析你在web.xml中定义的Servlet;用户的请求是如何被分配给指定的Servlet的;Servlet容器是如何管理Servlet的生命周期的;
从Servlet容器(Tomcat)说起
Servlet和Servlet容器通过标准化接口来互相协作;我们就先这些接口说起;
在Tomcat的容器模型中,Context容器直接管理Servlet在容器中的包装类Wapper,所以Context容器将直接影响Servlet的工作方式;
从上图可以看出,Tomcat的容器分为4个等级,真正管理Servlet的容器是Context容器,一个Context对应一个Web工程,在Tomcat配置文件中就可以发现这一点;
<Context path="/projectOne " docBase="D\projects\projectOne" reloadable="true"/>
Servlet容器的启动过程
Tomcat有个启动类org.apache.catalina.startup.Tomcat,创建一个实例对象并调用start方法就可以很容易地启动Tomcat;我们可以通过这个对象来增加和修改Tomcat的配置参数,如可以动态增加Context,Servlet等;
Tomcat tomcat = getTomcatInstance(); //1.创建Tomcat实例
File appDir = new File(getBuildDirectory(),"webapps/example"); //example是一个Web工程
tomcat.addWebapp(null,"/examples",appDir.getAbsolutePath()); //2.新增example这个Web工程
tomcat.start(); //3.启动Tomcat
ByteChunk res = getUrl("http://localhost:"+getPort()+"examples/servlets/setvlet/HelloWordExample");
//4.调用其中的一个HelloWorldExample这个Servlet,看有没有返回正确的数据
assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);
//Tomcat的addWebapp()
//一个Web应用对应一个Context容器(StandardContext),也就是Servlet运行时的Context容器
public Context addWebapp(Host host,String url,String path){
silence(url);
//1.创建一个StandardContext容器,并给做个容器设置参数
Context ctx = new StandardContext();
ctx.setPath(url); //url是这个web应用在Tomcat中的访问路径
ctx.setDocBase(path); //path是这个web应用的实际物理地址
if(defaultRealm == null){
initSimpleAuth();
}
ctx.setRealm(defaultRealm);
ctx.addLifecycleListener(new DefaultWebXmlLISTENER());
//2.ContextConfig将负责整个Web应用配置参数的解析
ContextConfig ctxCfg = new ContextConfig();
ctx.addLifecycleListener(ctxCfg);
ctx.setDefaultWebXml("org/apache/catalina/startup/NO_DEFAULT_XML");
//3.将这个Context容器添加到父容器Host中
if(host == null){
getHost().addChild(ctx);
} else {
host.addChild(ctx);
}
return ctx;
}
Context容器(StandardContext)初始化时,添加到Context容器的Listener将会被调用;ContextConfig继承了LifecycleListener接口,它是在调用Tomcat.addWebapp时被加入到StandardContext容器中的;
ContextConfig类会负责整个Web应用的配置文件的解析工作;它的init方法将完成以下工作:
1.创建用于解析XML配置文件的contextDigester对象;
2.读取默认的context.xml配置文件;
3.读取默认的Host配置文件;
4.读取默认的Context自身的配置文件;
5.设置Context的DocBase;
之后,Context容器就会执行startInternal方法,主要完成以下几部分:
1.创建读取资源文件的对象;
2.创建ClassLoader对象;
3.设置应用的工作目录;
4.启动相关的辅助类,如logger。resource等;
5.修改启动状态,通知观察者;
6.子容器的初始化;
7.获取ServletContext并设置必要的参数;
8.初始化“load on startup”的Servlet;
Web应用的初始化工作
Web应用的初始化工作是在ContextConfig的configureStart()方法中实现的,应用的初始化主要是解析web.xml文件,这个文件描述了一个Web应用的关键信息,也是一个Web应用的入口;
为什么要把Servlet包装成Context容器中的Wrapper(StandardWrapper)而不直接包装成Servlet对象?
StandardWapper是Tomcat容器中的一部分,它具有容器的特征,而Servlet作为作为一个独立的Web开发标准,不应该强耦合在Tomcat中;
除了将Servlet包装成StandardWrapper并作为子容器添加到Context中外,其他所有的web.xml属性都被解析到Context中,所以Context容器才是真正运行Servlet的Servlet容器;一个Web应用对应一个Context容器,容器的配置属性由应用的web.xml指定;
创建Servlet实例
到这里为止,我们已经完成了Servlet的解析工作了,并且被包装成StandardWrapper添加在Context容器中;我们还需要实例化Servlet,才能工作;
创建Servlet对象
如果Servlet的load-on-startup的配置项大于0,那么Context容器启动时就会被实例化;
创建Servlet实例的方法是从Wrapper.loadServlet开始的,loadServlet方法先获取servletClass,然后把它交给InstanceManager去创建一个基于servletClass.class的对象;
初始化Servlet
初始化Servlet在StandardWrapper的initServlet方法中,就是调用Servlet的init方法,同时把包装了StandardWrapper对象的StandardWrapperFacade作为ServletConfig传给Servlet;