Tomcat非常模块化,其总体架构本人归纳为以下:
话说 有这样的一家人 被称为Tomcat:
(一)Server
父亲被称为Server,其权利可谓至高无上,控制家中每对孩子;作为每对夫妻的生存环境,掌控整个Tomcat生命周期;其标准实现类StandardServer中的一个重要方法addService:
/**
* Add a new Service to the set of defined Services.
*
* @param service The Service to be added
*/
@Override
public void addService(Service service) {
service.setServer(this);
synchronized (servicesLock) {
//Server使用一个数组来管理Service,每添加一个Service就把原来的Service拷贝到一个新的数组中,再把新的Service放入Service数组中。
Service results[] = new Service[services.length + 1];
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results;
if (getState().isAvailable()) {
try {
service.start();
} catch (LifecycleException e) {
// Ignore
}
}
// Report this property change to interested listeners
support.firePropertyChange("service", null, service);
}
}
(二)Service
Service就是连接每对小夫妻婚姻的结婚证,每个结婚证都被父亲Server保管,其标准实现类StandardService中的两个重要方法addConnector及setContainer:
/**
* Add a new Connector to the set of defined Connectors, and associate it
* with this Service's Container.
*
* @param connector The Connector to be added
*/
@Override
public void addConnector(Connector connector) {
synchronized (connectorsLock) {
//类似动态数组 不是List集合
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
if (getState().isAvailable()) {
try {
connector.start();
} catch (LifecycleException e) {
log.error(sm.getString("standardService.connector.startFailed",connector), e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
}
@Override
public void setContainer(Engine engine) {
Engine oldEngine = this.engine;
// 如果已经关联 则去掉旧关联关系
if (oldEngine != null) {
oldEngine.setService(null);
}
//如果未关联 便进行关联
this.engine = engine;
if (this.engine != null) {
this.engine.setService(this);
}
if (getState().isAvailable()) {
//若未关联 则将engine启动
if (this.engine != null) {
try {
this.engine.start();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.engine.startFailed"), e);
}
}
// Restart MapperListener to pick up new engine.
try {
mapperListener.stop();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.mapperListener.stopFailed"), e);
}
try {
mapperListener.start();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.mapperListener.startFailed"), e);
}
if (oldEngine != null) {
try {
oldEngine.stop();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.engine.stopFailed"), e);
}
}
}
// Report this property change to interested listeners
support.firePropertyChange("container", oldEngine, this.engine);
}
(三)Connector
Connector作为每对小夫妻中的男人,主要负责外部交流;对浏览器发过来的TCP连接请求通过Request及Response对象进行和Container交流。
那Request及Response对象在一次请求中是如何变化的呐???
其中在Connector及Container之间Request、Response类的关系在代码上的展示如下:
---------------上下Request及Response两类关系中均使用到门面设计模式,详情另见-----------
那么在一次请求中如何根据这个URL到达正确的Container容器呐???
这种映射工作在Tomcat 5 以前是通过org.apache.tomcat.util.http.Mapper类来完成,这个类保存了Container中所有子容器的信息,org.apache.catalina.connector.Request类在进入Container容器之前,Mapper类将会根据此次请求的hostname及contestpath将Host和Context设置到该Request的mappingData属性中,所以Request在进入Container容器之前已经确定要访问哪个子容器;在Tomcat 5以后该Mapper类的功能被移到Request类中。
通过将MapperListener类作为一个监听者加到整个Container容器中每个子容器上,来确保任何一个子容器变化,相应的保存容器关系的MapperListener的mapper属性同时被修改,如下MapperListener类中源码:
@Override
public void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
Engine engine = service.getContainer();
if (engine == null) {
return;
}
findDefaultHost();
addListeners(engine);
Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
}
/**
* Add this mapper to the container and all child containers
*
* @param container
*/
private void addListeners(Container container) {
container.addContainerListener(this);
container.addLifecycleListener(this);
for (Container child : container.findChildren()) {
addListeners(child);
}
}
private void findDefaultHost() {
Engine engine = service.getContainer();
String defaultHost = engine.getDefaultHost();
boolean found = false;
if (defaultHost != null && defaultHost.length() >0) {
Container[] containers = engine.findChildren();
for (Container container : containers) {
Host host = (Host) container;
if (defaultHost.equalsIgnoreCase(host.getName())) {
found = true;
break;
}
String[] aliases = host.findAliases();
for (String alias : aliases) {
if (defaultHost.equalsIgnoreCase(alias)) {
found = true;
break;
}
}
}
}
if(found) {
mapper.setDefaultHostName(defaultHost);
} else {
log.warn(sm.getString("mapperListener.unknownDefaultHost",defaultHost, service));
}
}
/**
* Register host.
*/
private void registerHost(Host host) {
String[] aliases = host.findAliases();
mapper.addHost(host.getName(), aliases, host);
for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {
registerContext((Context) container);
}
}
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerHost",host.getName(), domain, service));
}
}
(四)Servlet容器Container
Container作为每对小夫妻中的女人,主要负责内部事务处理;该容器父接口逻辑上由4个父子关系的子容器组件构成:Engine、Host、Context,而Context又包含Wrapper。Container容器作为逻辑核心组件,其实并不存在;其Engine引擎作为top容器,无父容器,如以下server.xml文件中可以看出:
<Server port="" shutdown="SHUTDOWN">
<Service name="Catalina">
<Executor ...... />
<Connector port="" ... />
<Connector port="" ... />
<Engine name="Catalina" ... >
<Host name="localhost" appBase="webapps" ... >
<Valve className="" .../>
<Context docBase="" path="" ... />
</Host>
</Engine>
</Service>
</Server>
appBase :所指向的目录应该是准备用于存放这一组Web应用程序的目录,而不是具体某个 Web 应用程序的目录本身(即使该虚拟主机只由一个 Web 应用程序组成);默认是webapps。
path:是虚拟目录,访问的时候用127.0.0.1:8080/welcome/*.html访问网页,welcome前面要加/;
docBase:是网页实际存放位置的根目录,映射为path虚拟目录。
(五)Engine引擎
每个Service只能包含一个Engine引擎,其作用主要是决定从Connector连接器过来的请求应该交由哪一个Host处理,一个Engine代表一套完整的Servlet引擎;其标准实现类StandardEngine的addChild方法:
/**
* Add a child Container, only if the proposed child is an implementation
* of Host.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
//其添加的子容器类型也只能是Host类型
if (!(child instanceof Host))
throw new IllegalArgumentException(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
(六)Host
一个Host在Engine中代表一台虚拟主机,虚拟主机的作用就是运行多个应用,并负责安装和展开这些应用及对这些应用的区分。同时Host并不是必需的,但是要运行war程序,就必须要使用Host;因为在war中必有web.xml,该文件的解析必需使用Host。其标准实现类StandardHost的addChild方法同理Engine:
/**
* Add a child Container, only if the proposed child is an implementation
* of Context.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
child.addLifecycleListener(new MemoryLeakTrackingListener());
if (!(child instanceof Context))
throw new IllegalArgumentException(sm.getString("standardHost.notContext"));
super.addChild(child);
}
(七)Context
Context容器具备Servlet基本的运行环境,所以真正管理Servlet的容器是Context;一个Context对应一个Web工程,简单的Tomcat可以没有Engine及Host,只要有Context容器就能运行Servlet。
添加一个Web应用时:
/**
* @param host The host in which the context will be deployed
* @param contextPath The context mapping to use, "" for root context. 访问路径
* @param docBase Base directory for the context, for static files. 存放的物理路径
* Must exist, relative to the server home
* @param config Custom context configurator helper
* @return the deployed context
* @see #addWebapp(String, String)
*/
public Context addWebapp(Host host, String contextPath, String docBase,LifecycleListener config) {
silence(host, contextPath);
// 添加一个Web应用时便会创建一个StandardContext实现类对象
Context ctx = createContext(host, contextPath);
ctx.setPath(contextPath);
ctx.setDocBase(docBase);
ctx.addLifecycleListener(getDefaultWebXmlListener());
ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
ctx.addLifecycleListener(config);
if (config instanceof ContextConfig) {
// prevent it from looking ( if it finds one - it'll have dup error )
((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
}
if (host == null) {
getHost().addChild(ctx);
} else {
host.addChild(ctx);
}
return ctx;
}
private Context createContext(Host host, String url) {
String contextClass = StandardContext.class.getName();
if (host == null) {
host = this.getHost();
}
if (host instanceof StandardHost) {
contextClass = ((StandardHost) host).getContextClass();
}
try {
return (Context) Class.forName(contextClass).getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException
| ClassNotFoundException e) {
throw new IllegalArgumentException(
"Can't instantiate context-class " + contextClass
+ " for host " + host + " and url "+ url, e);
}
}
之后进行Web应用的初始化,主要就是解析web.xml文件;将解析后的相应属性保存在WebXml对象,并将该对象内容设置到Context容器中。
在server.xml文件中Context容器配置时当reloadable设为true时,war被修改后Tomcat会自动加载这个应用:
源码详解:
reload方法按照先调用stop方法再调用start方法,完成一次Context的一次重新加载。
backgroundProcess方法是在StandardContext的父类ContainerBase类中的内部类ContainerBackgroundProcessor中被周期调用,这个内部类类运行在一个后台线程中:
/**
* Private thread class to invoke the backgroundProcess method
* of this container and its children after a fixed delay.
*/
protected class ContainerBackgroundProcessor implements Runnable {
@Override
public void run() {
Throwable t = null;
String unexpectedDeathMessage = sm.getString("containerBase.backgroundProcess.unexpectedThreadDeath",
Thread.currentThread().getName());
try {
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
processChildren(ContainerBase.this);
}
}
} catch (RuntimeException|Error e) {
t = e;
throw e;
} finally {
if (!threadDone) {
log.error(unexpectedDeathMessage, t);
}
}
}
protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;
try {
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
// Loader will be null for FailedContext instances
if (loader == null) {
return;
}
// Ensure background processing for Contexts and Wrappers
// is performed under the web app's class loader
originalClassLoader = ((Context) container).bind(false, null);
}
container.backgroundProcess();
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i]);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
}
(八)Wrapper
Wrapper负责管理一个Servlet,包括Servlet的装载、初始化、执行及销毁;最底层的容器,无子容器,及无addChild方法;其实现类StandardWrapper中的一个重要方法loadServlet:
/**
* Load and initialize an instance of this servlet, if there is not already 加载并初始化Servlet实例
* at least one initialized instance. This can be used, for example, to
* load servlets that are marked in the deployment descriptor to be loaded
* at server startup time.
* @return the loaded Servlet instance
* @throws ServletException for a Servlet load error
*/
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException(sm.getString("standardWrapper.notClass", getName()));
}
// 获取InstanceManager实例
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
// 加载:获取servletClass,然后交给InstanceManager去创建一个基于servletClass.class的Servlet实例
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException(sm.getString("standardWrapper.notServlet", servletClass),e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException(sm.getString("standardWrapper.instantiate", servletClass),e);
}
if (multipartConfigElement == null) {
MultipartConfig annotation = servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement = new MultipartConfigElement(annotation);
}
}
// Special handling for ContainerServlet instances
// Note: The InstanceManager checks if the application is permitted
// to load ContainerServlets
if (servlet instanceof ContainerServlet) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
// 初始化Servlet实例
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
private synchronized void initServlet(Servlet servlet)throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// Call the initialization method of this servlet
try {
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init",servlet,classType,args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(servlet);
}
}
} else {
//核心:调用Servlet的init方法,并将StandardWrapper对象的门面类对象StandardWrapperFacade作为ServletConfig参数传入
servlet.init(facade);
}
instanceInitialized = true;
} catch (UnavailableException f) {
unavailable(f);
throw f;
} catch (ServletException f) {
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw f;
} catch (Throwable f) {
ExceptionUtils.handleThrowable(f);
getServletContext().log("StandardWrapper.Throwable", f );
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw new ServletException(sm.getString("standardWrapper.initException",getName()),f);
}
}
在该Servlet加载之前,先确定是否属于JSP,如果是,则会加载并初始化一个JspServlet实例:
与Servlet主动关联的类有三个:ServletConfig、ServletRequest、ServletResponse;ServletConfig在Servlet初始化的时候作为参数进入Servlet;其他两个都是当请求到达时调用Servlet传递过来的,那ServletContext呐,与ServletConfig有何异同???
Servlet的运行是典型的“握手型的交互式”运行模式,可以理解为:模块之间的数据交换要准备一个交易场景,这个场景一直跟随这个交易过程直到交易结束;而交易场景的初始化需要一些指定参数,都存放在配置类ServletConfig中;交易场景由ServletContext来表示,同时可以通过ServletConfig来获取;ServletRequest、ServletResponse通常作为运输工具来传递要交互的数据。
(九)组件的生命线——“Lifecycle”
Tomcat中组件的生命周期都是通过Lifecycle接口控制,组件只要继承这个接口并实现其中的方法则可以统一控制其子组件,所以最高级别的组件Server就可以一层一层地控制所有组件的生命周期,如StandardServer中:
或者 StandardService中: