Tomcat 作为 servlet 容器实现,它是基于 Java 语言开发的轻量级应用服务器。因为 Tomcat 作为应用服务器,它有着完全开源,轻量,性能稳定,部署成本低等优点,所以它成为目前 Java 开发应用部署的首选,几乎每个Java Web开发者都有使用过,但是,你对 Tomcat 的整体设计有进行过了解和思考吗?本文将基于 Tomcat8 进行分析,具体版本为 Tomcat8 当前官网最新修改(2019-11-21 09:28)的版本
v8.5.49
总体结构
Tomcat 的总体结构中有很多模块,下图列出我们将要进行分析结构中的主要模块。其中主要分析的是Service,Connector,Engine,Host,Context,Wrapper。为避免图层看着太乱,下图中 n
代表该组件可允许存在多个。如上图所描述的是:Server 是 tomcat 服务器,在 Server 中可以存在多个服务 Service 。每个服务中可有多个连接器和一个 Servlet 引擎 Engine,一个 Service 中多个连接器对应一个 Engine。每个 Engine 中,可存在多个域名,这里可用虚拟主机的概念来表示 Host。每个 Host 中可以存在多个应用 Context。Server,Service,Connector,Engine,Host,Context,Wrapper 它们之间的关系,除了Connector和Engine,它们是平行关系,其它的都是存在包含关系。同时,它们也都继承了 Lifecycle 接口,该接口提供的是生命周期的管理,里面包括:初始化(init),启动(start),停止(stop),销毁(destroy)。当它的父容器启动时,会调用它子容器的启动,停止也是一样的。上图中,还可以看到,Engine,Host,Context,Wrapper 都继承自 Container。它有个 backgroundProcess()
方法,后台异步处理,所以继承它后可以方便的创建异步线程。在 Tomcat7 中,有看到 Service 持有的是 Container,而不是 Engine。估计这也是为什么在当前版本中添加 Engine 方法名叫 setContainer
。Server
Tomcat 源码中有提供 org.apache.catalina.Server
接口,对应的默认实现类为 org.apache.catalina.core.StandardServer
,接口里面提供有如下图方法。上图中可以知道 Server 做的工作:对 Service,Address,Port,Catalina 以及全局命名资源的管理操作。Server 在进行初始化的时候,会加载我们 server.xml 中配置的数据。这里对其中的 Service 操作的 addService
向定义的服务集添加新服务进行分析:-
// 保存服务的服务集
-
privateService services[] = newService[0];
-
-
finalPropertyChangeSupport support = newPropertyChangeSupport(this);
-
-
@Override
-
publicvoid addService(Service service) {
-
// 相互关联
-
service.setServer(this);
-
-
// 利用同步锁,防止并发访问 来源:https://ytao.top
-
synchronized(servicesLock) {
-
Service results[] = newService[services.length + 1];
-
// copy 旧的服务到新的数组中
-
System.arraycopy(services, 0, results, 0, services.length);
-
// 添加新的 service
-
results[services.length] = service;
-
services = results;
-
-
// 如果当前 server 已经启动,那么当前添加的 service 就开始启动
-
if(getState().isAvailable()) {
-
try{
-
service.start();
-
} catch(LifecycleException e) {
-
// Ignore
-
}
-
}
-
-
// 使用观察者模式,当被监听对象属性值发生变化时通知监听器,remove 是也会调用。
-
support.firePropertyChange("service", null, service);
-
}
-
-
}
org.apache.catalina.Service
接口和默认实现类 org.apache.catalina.coreStandardService
。在实现类 StandardService 中,主要分析 setContainer
和 addConnector
两个方法。-
privateEngine engine = null;
-
-
protectedfinalMapperListener mapperListener = newMapperListener(this);
-
-
@Override
-
publicvoid setContainer(Engine engine) {
-
Engine oldEngine = this.engine;
-
// 判断当前 Service 是否有关联 Engine
-
if(oldEngine != null) {
-
// 如果当前 Service 有关联 Engine,就去掉当前关联的 Engine
-
oldEngine.setService(null);
-
}
-
// 如果当前新的 Engine 不为空,那么 Engine 关联当前 Service,这里是个双向关联
-
this.engine = engine;
-
if(this.engine != null) {
-
this.engine.setService(this);
-
}
-
// 如果当前 Service 启动了,那么就开始启动当前新的 Engine
-
if(getState().isAvailable()) {
-
if(this.engine != null) {
-
try{
-
this.engine.start();
-
} catch(LifecycleException e) {
-
log.error(sm.getString("standardService.engine.startFailed"), e);
-
}
-
}
-
// 重启 MapperListener ,获取一个新的 Engine ,一定是当前入参的 Engine
-
try{
-
mapperListener.stop();
-
} catch(LifecycleException e) {
-
log.error(sm.getString("standardService.mapperListener.stopFailed"), e);
-
}
-
try{
-
mapperListener.start();
-
} catch(LifecycleException e) {
-
log.error(sm.getString("standardService.mapperListener.startFailed"), e);
-
}
-
-
// 如果当前 Service 之前有 Engine 关联,那么停止之前的 Engine
-
if(oldEngine != null) {
-
try{
-
oldEngine.stop();
-
} catch(LifecycleException e) {
-
log.error(sm.getString("standardService.engine.stopFailed"), e);
-
}
-
}
-
}
-
-
// Report this property change to interested listeners
-
support.firePropertyChange("container", oldEngine, this.engine);
-
}
-
-
/**
-
* 实现方式和 StandardServer#addService 类似,不在细述
-
* 注意,Connector 这里没有像 Engine 一样与 Service 实现双向关联
-
*/
-
@Override
-
publicvoid addConnector(Connector connector) {
-
-
synchronized(connectorsLock) {
-
connector.setService(this);
-
Connector results[] = newConnector[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);
-
}
-
-
}
-
监听服务器端口来读取客户端的请求。
-
解析协议并交给对应的容器处理请求。
-
返回处理后的信息给客户端
-
<Connectorport="8080"protocol="HTTP/1.1"
-
connectionTimeout="20000"
-
redirectPort="8443"/>
port
,指定处理协议 protocol
,以及重定向地址 redirectPort
。协议处理类型通过实例化连接器时设置:-
publicConnector() {
-
// 无参构造,下面 setProtocol 中默认使用HTTP/1.1
-
this(null);
-
}
-
-
publicConnector(String protocol) {
-
// 设置当前连接器协议处理类型
-
setProtocol(protocol);
-
// 实例化协议处理器,并保存到当前 Connector 中
-
ProtocolHandler p = null;
-
try{
-
Class<?> clazz = Class.forName(protocolHandlerClassName);
-
p = (ProtocolHandler) clazz.getConstructor().newInstance();
-
} catch(Exception e) {
-
log.error(sm.getString(
-
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
-
} finally{
-
this.protocolHandler = p;
-
}
-
-
if(Globals.STRICT_SERVLET_COMPLIANCE) {
-
uriCharset = StandardCharsets.ISO_8859_1;
-
} else{
-
uriCharset = StandardCharsets.UTF_8;
-
}
-
}
-
-
/**
-
* 这个设置再 tomcat9 中被移除,改为必配项
-
*/
-
publicvoid setProtocol(String protocol) {
-
-
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
-
AprLifecycleListener.getUseAprConnector();
-
-
// 这里指定了默认协议和 HTTP/1.1 一样
-
if("HTTP/1.1".equals(protocol) || protocol == null) {
-
if(aprConnector) {
-
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
-
} else{
-
setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
-
}
-
} elseif("AJP/1.3".equals(protocol)) {
-
if(aprConnector) {
-
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
-
} else{
-
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
-
}
-
} else{
-
// 最后如果不是通过指定 HTTP/1.1,AJP/1.3 类型的协议,就通过类名实例化一个协议处理器
-
setProtocolHandlerClassName(protocol);
-
}
-
}
org.apache.catalina.Engine
接口和 org.apache.catalina.core.StandardEngine
默认实现类。Engine 的功能也比较简单,处理容器关系的关联。但是实现类中的 addChild()
不是指的子 Engine,而是只能是 Host。同时没有父容器, setParent
是不允许操作设置的。-
@Override
-
publicvoid addChild(Container child) {
-
// 添加的子容器必须是 Host
-
if(!(child instanceofHost))
-
thrownewIllegalArgumentException
-
(sm.getString("standardEngine.notHost"));
-
super.addChild(child);
-
}
-
-
@Override
-
publicvoid setParent(Container container) {
-
-
thrownewIllegalArgumentException
-
(sm.getString("standardEngine.notParent"));
-
-
}
-
<!-- 配置默认Host,及jvmRoute -->
-
<Enginename="Catalina"defaultHost="localhost"jvmRoute="jvm1">
-
<!-- name 设置的时虚拟主机域名 -->
-
<Hostname="localhost"appBase="webapps"
-
unpackWARs="true"autoDeploy="true">
addChild
方法,该添加的子容器是 Wrapper:-
@Override
-
publicvoid addChild(Container child) {
-
-
// Global JspServlet
-
Wrapper oldJspServlet = null;
-
-
// 这里添加的子容器只能时 Wrapper
-
if(!(child instanceofWrapper)) {
-
thrownewIllegalArgumentException
-
(sm.getString("standardContext.notWrapper"));
-
}
-
-
// 判断子容器 Wrapper 是否为 JspServlet
-
boolean isJspServlet = "jsp".equals(child.getName());
-
-
// Allow webapp to override JspServlet inherited from global web.xml.
-
if(isJspServlet) {
-
oldJspServlet = (Wrapper) findChild("jsp");
-
if(oldJspServlet != null) {
-
removeChild(oldJspServlet);
-
}
-
}
-
-
super.addChild(child);
-
-
// 将servlet映射添加到Context组件
-
if(isJspServlet && oldJspServlet != null) {
-
/*
-
* The webapp-specific JspServlet inherits all the mappings
-
* specified in the global web.xml, and may add additional ones.
-
*/
-
String[] jspMappings = oldJspServlet.findMappings();
-
for(int i=0; jspMappings!=null&& i<jspMappings.length; i++) {
-
addServletMappingDecoded(jspMappings[i], child.getName());
-
}
-
}
-
}
-
publicsynchronizedServlet loadServlet() throwsServletException{
-
-
// 如果已经实例化或者用实例化池,就直接返回
-
if(!singleThreadModel && (instance != null))
-
return instance;
-
-
PrintStream out = System.out;
-
if(swallowOutput) {
-
SystemLogHandler.startCapture();
-
}
-
-
Servlet servlet;
-
try{
-
long t1=System.currentTimeMillis();
-
// 如果 servlet 类名为空,直接抛出 Servlet 异常
-
if(servletClass == null) {
-
unavailable(null);
-
thrownewServletException
-
(sm.getString("standardWrapper.notClass", getName()));
-
}
-
-
// 从 Context 中获取 Servlet
-
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
-
try{
-
servlet = (Servlet) instanceManager.newInstance(servletClass);
-
} catch(ClassCastException e) {
-
unavailable(null);
-
// Restore the context ClassLoader
-
thrownewServletException
-
(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:
-
// https://bz.apache.org/bugzilla/show_bug.cgi?id=36630
-
if(log.isDebugEnabled()) {
-
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
-
}
-
-
// Restore the context ClassLoader
-
thrownewServletException
-
(sm.getString("standardWrapper.instantiate", servletClass), e);
-
}
-
-
// 加载声明了 MultipartConfig 注解的信息
-
if(multipartConfigElement == null) {
-
MultipartConfig annotation =
-
servlet.getClass().getAnnotation(MultipartConfig.class);
-
if(annotation != null) {
-
multipartConfigElement =
-
newMultipartConfigElement(annotation);
-
}
-
}
-
-
// 对 servlet 类型进行检查
-
if(servlet instanceofContainerServlet) {
-
((ContainerServlet) servlet).setWrapper(this);
-
}
-
-
classLoadTime=(int) (System.currentTimeMillis() -t1);
-
-
if(servlet instanceofSingleThreadModel) {
-
if(instancePool == null) {
-
instancePool = newStack<>();
-
}
-
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;
-
-
}
到目前为止,大致介绍了 Tomcat8 的主要组件,对 Tomcat 的整体架构也有个大致了解了,Tomcat 源码进行重构后,可读性确实要好很多,建议大家可以去尝试分析下,里面的使用的一些设计模式,我们在实际编码过程中,还是有一定的借鉴意义。