初探Tomcat的架构设计

源码共读

共 8508字,需浏览 18分钟

 · 2019-11-27

66151cbcd7fb17b60a01234a117c2893.webp

黑客技术点击右侧关注,了解黑客的世界!

003f83b44c17d90a9fd1ac97162c3f1f.webp

Java开发进阶点击右侧关注,掌握进阶之路!

882475c04701475d8e312ca0c161ff2b.webp

Python开发点击右侧关注,探讨技术话题!


作者丨ytao
来源丨ytao(ytao-blog)

858f112d468d001dc7534f404a5622f1.webp

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代表该组件可允许存在多个。

606c5765c67d75eb35fdfbbb3b5ba8f2.webp

如上图所描述的是: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)。当它的父容器启动时,会调用它子容器的启动,停止也是一样的。

8ea3d2e08278d61fff79f8e1f8551b56.webp

上图中,还可以看到,Engine,Host,Context,Wrapper 都继承自 Container。它有个 backgroundProcess()方法,后台异步处理,所以继承它后可以方便的创建异步线程。在 Tomcat7 中,有看到 Service 持有的是 Container,而不是 Engine。估计这也是为什么在当前版本中添加 Engine 方法名叫 setContainer

Server

Tomcat 源码中有提供 org.apache.catalina.Server接口,对应的默认实现类为 org.apache.catalina.core.StandardServer,接口里面提供有如下图方法。

0658815e2d3b7ef77ba333de6aa12cbd.webp

上图中可以知道 Server 做的工作:对 Service,Address,Port,Catalina 以及全局命名资源的管理操作。Server 在进行初始化的时候,会加载我们 server.xml 中配置的数据。

54e1ef46180ab23541953e61b07049c2.webp

这里对其中的 Service 操作的 addService向定义的服务集添加新服务进行分析:

  1. // 保存服务的服务集

  2. privateService services[] = newService[0];


  3. finalPropertyChangeSupport support = newPropertyChangeSupport(this);


  4. @Override

  5. publicvoid addService(Service service) {

  6. // 相互关联

  7. service.setServer(this);


  8. // 利用同步锁,防止并发访问 来源:https://ytao.top

  9. synchronized(servicesLock) {

  10. Service results[] = newService[services.length + 1];

  11. // copy 旧的服务到新的数组中

  12. System.arraycopy(services, 0, results, 0, services.length);

  13. // 添加新的 service

  14. results[services.length] = service;

  15. services = results;


  16. // 如果当前 server 已经启动,那么当前添加的 service 就开始启动

  17. if(getState().isAvailable()) {

  18. try{

  19. service.start();

  20. } catch(LifecycleException e) {

  21. // Ignore

  22. }

  23. }


  24. // 使用观察者模式,当被监听对象属性值发生变化时通知监听器,remove 是也会调用。

  25. support.firePropertyChange("service", null, service);

  26. }


  27. }

源码中可以看到,向服务器中添加服务后,随机会启动服务,实则也服务启动入口。

Service

Service 的主要职责就是将 Connector 和 Engine 的组装在一起。两者分开的目的也就是使请求监听和请求处理进行解耦,能拥有更好的扩展性。每个 Service 都是相互独立的,但是共享一个JVM和系统类库。这里提供了 org.apache.catalina.Service接口和默认实现类 org.apache.catalina.coreStandardService

fc66c62dcdfc3260e0adf4d2f0102875.webp

在实现类 StandardService 中,主要分析 setContaineraddConnector两个方法。

  1. privateEngine engine = null;


  2. protectedfinalMapperListener mapperListener = newMapperListener(this);


  3. @Override

  4. publicvoid setContainer(Engine engine) {

  5. Engine oldEngine = this.engine;

  6. // 判断当前 Service 是否有关联 Engine

  7. if(oldEngine != null) {

  8. // 如果当前 Service 有关联 Engine,就去掉当前关联的 Engine

  9. oldEngine.setService(null);

  10. }

  11. // 如果当前新的 Engine 不为空,那么 Engine 关联当前 Service,这里是个双向关联

  12. this.engine = engine;

  13. if(this.engine != null) {

  14. this.engine.setService(this);

  15. }

  16. // 如果当前 Service 启动了,那么就开始启动当前新的 Engine

  17. if(getState().isAvailable()) {

  18. if(this.engine != null) {

  19. try{

  20. this.engine.start();

  21. } catch(LifecycleException e) {

  22. log.error(sm.getString("standardService.engine.startFailed"), e);

  23. }

  24. }

  25. // 重启 MapperListener ,获取一个新的 Engine ,一定是当前入参的 Engine

  26. try{

  27. mapperListener.stop();

  28. } catch(LifecycleException e) {

  29. log.error(sm.getString("standardService.mapperListener.stopFailed"), e);

  30. }

  31. try{

  32. mapperListener.start();

  33. } catch(LifecycleException e) {

  34. log.error(sm.getString("standardService.mapperListener.startFailed"), e);

  35. }


  36. // 如果当前 Service 之前有 Engine 关联,那么停止之前的 Engine

  37. if(oldEngine != null) {

  38. try{

  39. oldEngine.stop();

  40. } catch(LifecycleException e) {

  41. log.error(sm.getString("standardService.engine.stopFailed"), e);

  42. }

  43. }

  44. }


  45. // Report this property change to interested listeners

  46. support.firePropertyChange("container", oldEngine, this.engine);

  47. }


  48. /**

  49. * 实现方式和 StandardServer#addService 类似,不在细述

  50. * 注意,Connector 这里没有像 Engine 一样与 Service 实现双向关联

  51. */

  52. @Override

  53. publicvoid addConnector(Connector connector) {


  54. synchronized(connectorsLock) {

  55. connector.setService(this);

  56. Connector results[] = newConnector[connectors.length + 1];

  57. System.arraycopy(connectors, 0, results, 0, connectors.length);

  58. results[connectors.length] = connector;

  59. connectors = results;


  60. if(getState().isAvailable()) {

  61. try{

  62. connector.start();

  63. } catch(LifecycleException e) {

  64. log.error(sm.getString(

  65. "standardService.connector.startFailed",

  66. connector), e);

  67. }

  68. }


  69. // Report this property change to interested listeners

  70. support.firePropertyChange("connector", null, connector);

  71. }


  72. }

Connector

Connector 主要用于接收请求,然后交给 Engine 处理请求,处理完后再给 Connector 去返回给客户端。当前使用版本支持的协议有:HTTP,HHTP/2,AJP,NIO,NIO2,APR主要的功能包括:

  • 监听服务器端口来读取客户端的请求。

  • 解析协议并交给对应的容器处理请求。

  • 返回处理后的信息给客户端

Connector 对应服务器 server.xml 中配置信息的例子:

  1. port="8080"protocol="HTTP/1.1"

  2. connectionTimeout="20000"

  3. redirectPort="8443"/>

这里通过配置监听的端口号 port,指定处理协议 protocol,以及重定向地址 redirectPort。协议处理类型通过实例化连接器时设置:

  1. publicConnector() {

  2. // 无参构造,下面 setProtocol 中默认使用HTTP/1.1

  3. this(null);

  4. }


  5. publicConnector(String protocol) {

  6. // 设置当前连接器协议处理类型

  7. setProtocol(protocol);

  8. // 实例化协议处理器,并保存到当前 Connector 中

  9. ProtocolHandler p = null;

  10. try{

  11. Class clazz = Class.forName(protocolHandlerClassName);

  12. p = (ProtocolHandler) clazz.getConstructor().newInstance();

  13. } catch(Exception e) {

  14. log.error(sm.getString(

  15. "coyoteConnector.protocolHandlerInstantiationFailed"), e);

  16. } finally{

  17. this.protocolHandler = p;

  18. }


  19. if(Globals.STRICT_SERVLET_COMPLIANCE) {

  20. uriCharset = StandardCharsets.ISO_8859_1;

  21. } else{

  22. uriCharset = StandardCharsets.UTF_8;

  23. }

  24. }


  25. /**

  26. * 这个设置再 tomcat9 中被移除,改为必配项

  27. */

  28. publicvoid setProtocol(String protocol) {


  29. boolean aprConnector = AprLifecycleListener.isAprAvailable() &&

  30. AprLifecycleListener.getUseAprConnector();


  31. // 这里指定了默认协议和 HTTP/1.1 一样

  32. if("HTTP/1.1".equals(protocol) || protocol == null) {

  33. if(aprConnector) {

  34. setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");

  35. } else{

  36. setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");

  37. }

  38. } elseif("AJP/1.3".equals(protocol)) {

  39. if(aprConnector) {

  40. setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");

  41. } else{

  42. setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");

  43. }

  44. } else{

  45. // 最后如果不是通过指定 HTTP/1.1,AJP/1.3 类型的协议,就通过类名实例化一个协议处理器

  46. setProtocolHandlerClassName(protocol);

  47. }

  48. }

ProtocolHandler 是一个协议处理器,针对不同的请求,提供不同实现。实现类 AbstractProtocol 在初始化时,会在最后调用一个抽象类 AbstractEndpoint 初始化来启动线程来监听服务器端口,当接收到请求后,调用 Processor 读取请求,然后交给 Engine 处理请求。

Engine

Engine 对应的是, org.apache.catalina.Engine接口和 org.apache.catalina.core.StandardEngine默认实现类。Engine 的功能也比较简单,处理容器关系的关联。

ecbb0e91a0ac219b6fd636826d13100d.webp

但是实现类中的 addChild()不是指的子 Engine,而是只能是 Host。同时没有父容器, setParent是不允许操作设置的。

  1. @Override

  2. publicvoid addChild(Container child) {

  3. // 添加的子容器必须是 Host

  4. if(!(child instanceofHost))

  5. thrownewIllegalArgumentException

  6. (sm.getString("standardEngine.notHost"));

  7. super.addChild(child);

  8. }


  9. @Override

  10. publicvoid setParent(Container container) {


  11. thrownewIllegalArgumentException

  12. (sm.getString("standardEngine.notParent"));


  13. }

server.xml 可以配置我们的数据:

  1. name="Catalina"defaultHost="localhost"jvmRoute="jvm1">

Host

Host 表示一个虚拟主机。应为我们的服务器可设置多个域名,比如 demo.ytao.top,dev.ytao.top。那么我们就要设置两个不同 Host 来处理不同域名的请求。当过来的请求域名为 demo.ytao.top 时,那么它就会去找该域名 Host 下的 Context。所以我们的 server.xml 配置文件也提供该配置:

  1. name="localhost"appBase="webapps"

  2. unpackWARs="true"autoDeploy="true">

Context

到 Context 这里来,就拥有 Servlet 的运行环境,Engine,Host都是主要维护容器关系,不具备运行环境。我们暂且可将 Context 理解为一个应用,例如我们在根目录下有 ytao-demo-1 和 ytao-demo-2 两个应用,那么这里就是有两个 Context。这里主要介绍的 addChild方法,该添加的子容器是 Wrapper:

  1. @Override

  2. publicvoid addChild(Container child) {


  3. // Global JspServlet

  4. Wrapper oldJspServlet = null;


  5. // 这里添加的子容器只能时 Wrapper

  6. if(!(child instanceofWrapper)) {

  7. thrownewIllegalArgumentException

  8. (sm.getString("standardContext.notWrapper"));

  9. }


  10. // 判断子容器 Wrapper 是否为 JspServlet

  11. boolean isJspServlet = "jsp".equals(child.getName());


  12. // Allow webapp to override JspServlet inherited from global web.xml.

  13. if(isJspServlet) {

  14. oldJspServlet = (Wrapper) findChild("jsp");

  15. if(oldJspServlet != null) {

  16. removeChild(oldJspServlet);

  17. }

  18. }


  19. super.addChild(child);


  20. // 将servlet映射添加到Context组件

  21. if(isJspServlet && oldJspServlet != null) {

  22. /*

  23. * The webapp-specific JspServlet inherits all the mappings

  24. * specified in the global web.xml, and may add additional ones.

  25. */

  26. String[] jspMappings = oldJspServlet.findMappings();

  27. for(int i=0; jspMappings!=null&& i

  28. addServletMappingDecoded(jspMappings[i], child.getName());

  29. }

  30. }

  31. }

这里也就是每个应用中的 Servlet 管理中心。

Wrapper

Wrapper 是一个 Servlet 的管理中心,它拥有 Servlet 的整个生命周期,它是没有子容器的,因为它自己就是最底层的容器了。这里主要对 Servlet 加载的分析:

  1. publicsynchronizedServlet loadServlet() throwsServletException{


  2. // 如果已经实例化或者用实例化池,就直接返回

  3. if(!singleThreadModel && (instance != null))

  4. return instance;


  5. PrintStream out = System.out;

  6. if(swallowOutput) {

  7. SystemLogHandler.startCapture();

  8. }


  9. Servlet servlet;

  10. try{

  11. long t1=System.currentTimeMillis();

  12. // 如果 servlet 类名为空,直接抛出 Servlet 异常

  13. if(servletClass == null) {

  14. unavailable(null);

  15. thrownewServletException

  16. (sm.getString("standardWrapper.notClass", getName()));

  17. }


  18. // 从 Context 中获取 Servlet

  19. InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();

  20. try{

  21. servlet = (Servlet) instanceManager.newInstance(servletClass);

  22. } catch(ClassCastException e) {

  23. unavailable(null);

  24. // Restore the context ClassLoader

  25. thrownewServletException

  26. (sm.getString("standardWrapper.notServlet", servletClass), e);

  27. } catch(Throwable e) {

  28. e = ExceptionUtils.unwrapInvocationTargetException(e);

  29. ExceptionUtils.handleThrowable(e);

  30. unavailable(null);


  31. // Added extra log statement for Bugzilla 36630:

  32. // https://bz.apache.org/bugzilla/show_bug.cgi?id=36630

  33. if(log.isDebugEnabled()) {

  34. log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);

  35. }


  36. // Restore the context ClassLoader

  37. thrownewServletException

  38. (sm.getString("standardWrapper.instantiate", servletClass), e);

  39. }


  40. // 加载声明了 MultipartConfig 注解的信息

  41. if(multipartConfigElement == null) {

  42. MultipartConfig annotation =

  43. servlet.getClass().getAnnotation(MultipartConfig.class);

  44. if(annotation != null) {

  45. multipartConfigElement =

  46. newMultipartConfigElement(annotation);

  47. }

  48. }


  49. // 对 servlet 类型进行检查

  50. if(servlet instanceofContainerServlet) {

  51. ((ContainerServlet) servlet).setWrapper(this);

  52. }


  53. classLoadTime=(int) (System.currentTimeMillis() -t1);


  54. if(servlet instanceofSingleThreadModel) {

  55. if(instancePool == null) {

  56. instancePool = newStack<>();

  57. }

  58. singleThreadModel = true;

  59. }


  60. // 初始化 servlet

  61. initServlet(servlet);


  62. fireContainerEvent("load", this);


  63. loadTime=System.currentTimeMillis() -t1;

  64. } finally{

  65. if(swallowOutput) {

  66. String log = SystemLogHandler.stopCapture();

  67. if(log != null&& log.length() > 0) {

  68. if(getServletContext() != null) {

  69. getServletContext().log(log);

  70. } else{

  71. out.println(log);

  72. }

  73. }

  74. }

  75. }

  76. return servlet;


  77. }

这里加载 Servlet,如果该 Servlet 没有被实例化过,那么一定要加载一个。

到目前为止,大致介绍了 Tomcat8 的主要组件,对 Tomcat 的整体架构也有个大致了解了,Tomcat 源码进行重构后,可读性确实要好很多,建议大家可以去尝试分析下,里面的使用的一些设计模式,我们在实际编码过程中,还是有一定的借鉴意义。


 推荐↓↓↓ 

?16个技术公众号】都在这里!

涵盖:程序员大咖、源码共读、程序员共读、数据结构与算法、黑客技术和网络安全、大数据科技、编程前端、Java、Python、Web编程开发、Android、iOS开发、Linux、数据库研发、幽默程序员等。

b5bcac6b2c378962f6cfe123e5472163.webp万水千山总是情,点个 “在看” 行不行
浏览 11
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报