【读书笔记/源码】How Tomcat Works 笔记- c11~c13

  • chapter11: standardwrapper
  • chapter12: 无程序

第十章 安全性

servlet容器是通过一个名为验证器的阀来支持安全限制的。当servlet容器启动时,验证器阀会被添加到Context容器的管道中。

验证器阀会调用Context容器的领域对象的authenticate()方法,传入用户输入的用户名和密码,来对用户进行身份验证。领域对象可以访问有效用户的用户名和密码的集合。

servlet编程中安全性功能的概念包括主题、角色、领域和登陆配置等。可以阅读《Java for the Web with Senlets, JSP, and EJB》一书

10.1 领域

  • 领域对象用来对用户进行身份验证的组件。领域对象通常都会与一个Context容器相关联,而一个Context容器也只能有一个领域对象。
  • 领域对象如何验证用户身份呢?它保存了所有有效用户的用户名和密码对,或者它会访问存储这些数据的存储器。在Tomcat中,有效信息默认存储在tomcat-user.xml文件中。但是也可以使用其他领域对象来实现。
  • 在Catalina中,领域对象是org.apache.catalina.Realm接口的实例。

10.2 GenericPrincipal类

  • 主体对象是java.seurity.Principal接口的实例。该接口在Catalina中的实现是org.apache.catalina.realm.GenericPrincipal类。GenericPrincipal实例必须始终与一个领域对象相关联。

10.3 LoginConfig类

登录配置是final型的org.apache.catalina.deploy.LoginConfig类的实例,其中包含一个领域对象的名字。LoginConfig实例封装了领域对象名和所要使用的身份验证方法。

在实际部署中,Tomcat在启动时需要读取web.xml文件的内容。如果web.xml文件包含login-config元素的配置,则Tomcat会创建一个LoginConfig对象。

10.4 Authenticator

验证器是Authenticator接口的实例。Authenticator接口本身并没有声明方法。只是起到了一个标记作用,这样其他组件就可以使用instanceof关键字检查某个组件是不是一个验证器。

Catalina提供了Authenticator接口的一个基本实现,AuthenticatorBase类。除了实现Authenticator接口外,AuthenticatorBase类还扩展了org.apache.catalina.values.ValveBase类。这就是说AuthenticatorBase类也是一个阀。

10.5 安装验证器阀

在部署描述器中,login-config元素仅能出现一次。login-config元素包含一个auth-method元素来指定身份验证方法。也就是说,一个Context实例只能有一个LoginConfig实例和利用一个验证类的实现。

由于使用的验证器类是在运行时才确定的,因此该类是动态载入的。StandardContext类使用org.apache.catalina.startup.ContextConfig类来对StandardContext实例的属性进行设置。这些设置包括实例化一个验证器类,并将该实例与Context实例相关联。本章示例中SimpleContextConfig类的实例负责动态载入BasicAuthenticator类,实例化其对象,并将其作为一个阀安装到StandardContext实例中。

10.6 应用程序

simple-realm

com.lab.tomcat.Bootstrap#main

  • ...
  • org.apache.catalina.core.StandardContext#start
    • ...
    • org.apache.catalina.core.ContainerBase#start
      • ...
      • com.lab.tomcat.core.SimpleContextConfig#lifecycleEvent
        • com.lab.tomcat.core.SimpleContextConfig#authenticatorConfig
          • 检查是否有安全限制 context.findConstraints();
          • 如果有安全限制,检查是否有LoginConfig对象 context.getLoginConfig();
          • 一个Context实例只能有一个验证器,当发现某个阀是验证器后,直接返回
          • 检查当前Context实例是否有关联的领域对象 context.getRealm()
          • 若找到了领域对象,则动态载入BasicAuthenticator类,创建该类的一个实例,并作为阀添加到StandardContext中。

simple-user-database-realm

com.lab.tomcat.realm.SimpleUserDatabaseRealm#authenticate

  • ...

浏览器请求后

org.apache.catalina.connector.http.HttpProcessor#run

  • org.apache.catalina.connector.http.HttpProcessor#process
    • org.apache.catalina.connector.http.HttpProcessor#parseHeaders
      • 获取header上的authorization参数
      • org.apache.catalina.connector.RequestBase#setAuthorization
    • org.apache.catalina.core.StandardContext#invoke
      • org.apache.catalina.core.StandardPipeline#invoke
        • org.apache.catalina.core.StandardPipeline#invokeNext
          • org.apache.catalina.authenticator.AuthenticatorBase#invoke
            • org.apache.catalina.authenticator.BasicAuthenticator#authenticate
              • 从request中取出authorization参数 org.apache.catalina.Request#getAuthorization
              • com.lab.tomcat.realm.SimpleUserDatabaseRealm#authenticate

第十一章 StandardWrapper

11.1 方法调用序列

对于每个引入的HTTP请求,连接器都会调用与其关联的servlet容器的invoke()方法。然后servlet容器会调用其所有子容器的invoke()方法。

回忆下第五章内容,servlet容器包含一条管道和一个或多个阀。

具体过程如下

  1. 连接器创建请求和响应对象
  2. 连接器调用 StandardContext 的 invoke 方法
  3. StandardContext 实例的 invoke 方法调用其pipeline的 invoke 方法,即调用其基础阀 StandardContextValve 的 invoke 方法
  4. StandardContextValve 的 invoke 方法得到合适的Wrapper实例处理HTTP请求,调用Wrapper实例的 invoke 方法
  5. StandardWrapper 是Wrapper接口的标准实现,StandardWrapper 实例的 invoke 方法其pipeline对象的 invoke 方法
  6. StandardWrapper的pipeline对象的基础阀是StandardWrapperValve类的实例。 因此,会调用StandardWrapperValve 的 invoke 方法,StandardWrapperValve 的 invoke 方法会调用Wrapper实例的 allocate 方法获得一个 servlet 的实例。
  7. allocate 方法调用 load 方法来加载一个 servlet类,若已经载入则无需重复
  8. load 方法调用 servlet 的 init 方法
  9. StandardWrapperValve调用servlet实例的service()方法

StandardContext的构造函数中会设置StandardContextValue做基础阀

StandardWrapper的构造函数中会设置StandardWrapperValve做基础阀

本章着重说明servlet实例时如何调用的。 先对SingleThreadModel接口进行介绍,该接口是理解Wrapper如何载入servlet类的关键。

11.2 SingleThreadModel

servlet类可以实现javax.servlet.SingleThreadModel接口,这样的servlet类也称作SingleThreadModel(STM)类。根据Servlet规范,实现此接口的目的是保证servlet实例一次只处理一个请求。

实现SingleThreadModel接口的servlet类只能保证在同一时刻,只有一个线程在执行该servlet实例的service()方法,但为了提高性能,servlet容器会创建多个STM servlet实例。也就是说,STM servlet实例的service()方法会在多个STM servlet实例中并发执行。如果servlet实例需要访问静态类变量或类外的某些资源的话,就有可能引起同步问题。

(在Servlet2.4规范中,SingleThreadModel接口已经弃用了)

11.3 StandardWrapper

StandardWrapper对象的主要任务是载入它所代表的servlet类,并进行实例化。但是,StandardWrapper类并不调用servlet的service方法,而由StandardWrapperValue对象(StandardWrapper实例的pipeline基础阀)完成。StandardWrapperValue对象调用allocate()方法从StandardWrapper实例中获取servlet实例。之后调用servlet实例的service方法。

至于当StandardWrapperValue实例请求servlet实例时,StandardWrapper实例必须考虑到该servlet类是否实现了SingleThreadModel(STM)接口。

对于那些没有实现STM接口的servlet类,只会被StandardWrapper载入一次。之后的请求都访问该类的同一实例。StandardWrapper类不需要多个servlet实例,因为它假设该servlet类的service()方法在多线程环境中是线程安全的。如果必要的话,由servlet程序员来负责同步对共享资源的访问。

而对于一个STM servlet类。StandardWrapper实例必须保证每个时刻只能有一个线程在执行STM servlet类的service()方法。

但是为了更好的性能,StandardWrapper实例会维护一个STM servlet实例池。

Wrapper实例负责准备一个javax.servlet.servletConfig实例,后者在servlet实例内部可以获取到。

  • 分配servlet实例
  • 载入servlet类

StandardWrapper类不仅实现了Wrapper接口,还实现了javax.servlet.ServletConfig接口

StandardWrapper类并不将自身传递给servlet实例的init()方法。相反,它会在一个StandardWrapperFacade实例中包装自身,将其大部分的公共方法对servlet程序员隐藏起来。

无法单独使用一个Wrapper实例来表示一个servlet类的定义。Wrapper实例必须驻留在某个Context容器中,这样当调用其父容器的getServletContext()方法时才能返回ServletContext类的一个实例。

Wrapper的父容器只能是Context类的实现。

11.4 StandardWrapperFacade类

StandardWrapper实例会调用其所载入的servlet实例的init()方法。init()方法需要一个javax.servlet.ServletConfig实例,而StandardWrapper类本身实现了javax.servlet.ServletConfig接口,所以,理论上StandardWrapper对象可以将自己传入init()方法。但是StandardWrapper需要将大部分公共方法对servlet程序员隐藏起来。为了实现该目的,StandardWrapper类将自身实例包装成StandardWrapperFacade类的一个实例。

当创建StandardWrapper对象调用servlet实例的init()方法时,它会传入StandardWrapperFacade类的一个实例。这样,在servlet实例内调用ServletConfig类的getServletName()、getInitParameter()和getInitParameterNames()方法会直接传递给StandardWrapper类实现的相应方法。

11.5 StandardWrapperValue类

StandardWrapperValue类是StandardWrapper实例中的基础阀,要完成:

  1. 执行与该servlet实例关联的全部过滤器
  2. 调用servlet实例的service()方法

11.6 FilterConfig类

过滤器定义

11.7 ApplicationFilterConfig类

ApplicationFilterConfig类用于管理Web应用程序第一次启动时创建的所有过滤器实例。

11.8 ApplicationFilterChain类

StandardWrapperValue类的invoke()方法会创建ApplicationFilterChain类的一个实例,并调用其doFilter()方法。

11.9 应用程序

org.apache.catalina.core.StandardWrapper#allocate

  • 对于非STM类,定义了instance属性。
    • instance为null则
      • org.apache.catalina.core.StandardWrapper#loadServlet 载入相关servlet类
      • ++this.countAllocated;
    • 若StandardWrapper表示的servlet类是STM servlet类,则试图从对象池instancePool中返回一个servlet实例。
      • 只要STM servlet实例数不超过指定的最大值,返回一个STM servlet实例。
java 复制代码
...
synchronized(this.instancePool) {
    while(this.countAllocated >= this.nInstances) {
        if (this.nInstances < this.maxInstances) {
            try {
                this.instancePool.push(this.loadServlet());
                ++this.nInstances;
            } catch (ServletException var6) {
                throw var6;
            } catch (Throwable var7) {
                throw new ServletException(ContainerBase.sm.getString("standardWrapper.allocate"), var7);
            }
        } else {
            try {
                this.instancePool.wait();
            } catch (InterruptedException var8) {
            }
        }
    }

    if (this.debug >= 2) {
        this.log("  Returning allocated STM instance");
    }

    ++this.countAllocated;
    return (Servlet)this.instancePool.pop();
}

(StandardWrapperValve的方法挺一目了然的)

org.apache.catalina.core.StandardWrapperValve#invoke

  1. 调用StandardWrapper实例的allocate()方法获取该StandardWrapper实例所表示的servlet实例
  2. 调用私有方法createFilterChain(),创建过滤器链
  3. 调用过滤器链的doFilter()方法,其中包括调用servlet实例的service()方法。
  4. 释放过滤器链
  5. 调用Wrapper实例的deallocate()方法
  6. 若该servlet类再也不会被使用到,则调用Wrapper实例的unload()方法。

第十二章 StandardContext类

本章无程序

12.1 StandardContext的配置

  • 正确设置后,StandardContext对象才能读取并解析默认的web.xml文件,该文件位于%CATALINA_HOME%目录下,该文件的内容会应用到所有部署到Tomcat中的应用程序。
  • start()方法要做的一件事,是触发一个生命周期事件。该事件调用监听器。
  • 用生命周期监听器配置StandardContext实例,当配置成功后,监听器会将其configured属性置为true。

tomcat5中start()方法与Tomcat4相似,但包含了一些与JMX相关的代码。

  • start()方法需要完成
    1. 触发 BEFORE_START 事件
    2. 设置 availability 属性为 false
    3. 设置 configured 属性为 false
    4. 设置资源
    5. 设置载入器
    6. 设置Session管理器
    7. 初始化字符集映射器
    8. 启动与该Context容器相关联的组件
    9. 启动子容器
    10. 启动管道对象
    11. 启动Session管理器
    12. 触发 START 事件,在这里监听器(ContextConfig实例)会进行一系列配置操作,配置成功后,ContextConfig实例会将 StandardContext 实例的 configured 属性设置 为 true
    13. 检查 configured 属性的值,如果为 true,调用 postWelcomPages方法,加载需要在启动时就载入的子容器,即Wrapper实例,并将available属性设置为true。如果 configured 属性为 false,调用 stop 方法
    14. 触发 AFTER_START 事件

12.2 StandardContextWrapper类

  • 对于每个引入的HTTP请求,都会调用StandardContext实例的管道对象的基础阀的invoke()方法来处理。
  • 对于每个引入的HTTP请求,StandardContextValue实例调用Context容器的map()方法,并传入一个org.apache.catalina.Request对象。map()方法(实际上是定义在父类ContainerBase中的)会针对某个特定的协议调用findMapper()方法返回一个映射器对象,然后调用映射器对象的map()方法获取Wrapper实例。
  • 在Tomcat4中用Mapper映射servlet
  • Tomcat5中,Mapper接口以及其相关类已经移除了。事实上,StandardContextValue类的invoke()方法会从request对象中获取适合的Wrapper实例。
    • Wrapper wrapper = request.getWrapper();
    • 该Wrapper实例指明了封装在request对象中的映射信息。

12.3 对重载的支持

  • StandardContext类是通过其载入器实现应用程序重载的。在Tomcat4中,StandardContext对象中的WebappLoader类实现了Loader接口,并使用另一个线程检查WEB-INF目录中的所有类和JAR文件的时间戳。只需要调用其setContainer()方法将WebappLoader对象与StandardContext对象相关联就可以启动该检查线程。

12.4 backgroundProcess()方法

  • 这个共享线程在ContainerBase对象中创建。ContainerBase类在其Start()方法中(即,当该容器启动时)嗲用其threadStart()方法启动该后台线程。
  • ContainerBackgroundProcessor类时间上是ContainerBase类的内部类。在其run()方法中是一个while循环,周期性地调用其processChildren方法。而processChildred方法会调用自身对象地backgroundProcess()方法和其每个子容器地processChildren()方法。通过实现backgroundProcess()方法,ContainerBase类的子类可以使用一个专用线程来执行周期性任务,例如检查类的时间戳或检查session对象的超过时间。

第十三章 Host和Engine

如果想在同一个Tomcat上部署运行多个Context容器的话,你就需要使用Host容器。当只有一个Context容器时,理论上不需要使用Host容器。在Context接口的描述中有一段话。

Context容器的父容器通常是Host容器,也有可能是其他实现,或者如果不必要,就可以不使用父容器。

13.1 Host接口

  • 比较重要的是map方法,该方法返回一个用来处理引入的HTTP请求的Context容器的实例。

13.2 StandardHost类

  • 每引入一个HTTP请求,都会调用Host实例的invoke方法。由于StandardHost没有提供invoke方法实现,其会调用父类ContainerBase类的invoke方法,父类方法进而调用StandardHost实例的基础阀StandardHostValue实例的invoke方法。此外,StandardHostValue类的invoke方法会调用StandardHost类的map()方法来获取相应的Context实例来处理HTTP请求。

13.3 StandardHostMapper类

  • 在Tomcat4中,ContainerBase类(也就是StandardHost的父类)会调用其addDefaultMapper()方法创建一个默认映射器。默认映射器的类型由mapperClass属性的值决定。
  • 此外,StandardHost类的start()方法会在方法末尾调用父类的start()方法,确保默认映射器的创建完成。

Tomcat4中StandardContext类创建默认映射器的方法略有不同。其start()方法不会调用父类的start()方法。相反,StandardContext类的start()方法会调用addDefaultMapper()方法,并传入mapperClass变量来创建默认映射器。

13.4 StandardHostValue类

  • 在Tomcat4中,invoke方法调用StandardHost实例的map()方法来获取一个相应Context实例
  • 然后,invoke方法会获取与该request对象相关联的session的对象,并调用其access()方法。access()方法会修改session对象的最后访问时间。

13.5 为什么必须要有一个Host容器

  • 在Tomcat4和5的实际部署中,若一个Context实例使用ContextConfig对象进行设置,就必须使用一个Host对象。原因:
    • 使用ContextConfig对象需要知道应用程序web.xml文件的位置。在其applicationConfig()方法中它会试图打开web.xml文件。
    • 其中,Constants.ApplicationWebXml的值为"/WEB-INF/web.xml",web.xml文件的相对路径,servletContext是一个ApplicationContext类型(实现了servletContext接口)的对象。
    • ApplicationContext的getResource()代码中限制了如果要使用ContextConfig实例来进行配置的话,Context实例必须有一个Host实例作为父容器。即,除非自己实现ContextConfig类,否则必须使用一个Host容器。

13.7 Engine接口

  • 在Engine容器中,可以设置一个默认Host或一个默认Context容器。注意,Engine容器可以与一个服务实例相关联。

13.8 StandardEngine类

  • 相比于StandardContext类和StandardHost类,StandardEngine类相对小一些。
  • Engine容器可以有子容器,只能是Host容器。

13.9 StandardEngineValue类

  • 验证了request和response类型后,invoke()方法得到Host实例,用于处理请求。invoke方法会通过调用Engine实例的map()方法获取Host对象

感觉链路模式差不多,不进行代码调试记录了

第十四章 服务器组件和服务组件

暂略

第十五章 Digester库

暂略

第十六章 关键钩子

例如,在 Tomcat 部署通过实例化一个Server并调用它的 start 方法来启动一个 servlet 容器,该方法又调用其他组件的 start 方法。正常的情况下,可以通过一个关闭命令来让服务器关闭所有组件(如 14 章中介绍)。如果突然地关闭程序,如关闭运行中程序的控制台可能会发生意想不到的事情。

Java提供了方法可以在关闭过程中执行一些代码。

在 Java 中,虚拟机遇到两种事件的时候会关闭虚拟机:

  • 应用程序正常退出如System.exit方法被调用,或者最后一个非守护进程线程退出。
  • 用户突然强制终止虚拟机,例如键入 CTRL+C 或者在未关闭 Java程序的情况下从系统退出。

幸运的是,虚拟机在执行关闭操作的时候,会有经过以下两个阶段:

  1. 虚拟机启动所有注册的关闭钩子。关闭钩子是实现在 Runtime 上面注册的线程。所有的关闭钩子会并发执行直到完成任务。
  2. 虚拟机根据情况调用所有的未调用的终结器(finalizers)

本章重点说明第一个阶段,它允许虚拟机在应用程序中执行一些清理代码。关闭钩子是 java.lang.Thread 类的子类实例,可以如下创建一个关闭钩子:

  1. 写一个类继承 Thread 类
  2. 提供你的实现类中的 run 方法。该方法是应用程序被关闭的时候会执行的代码,无论是正常退出还是非正常退出。
  3. 在应用程序中,实例化关闭钩子类
  4. 使用当前的 Runtime 类的 addShutdownHook 方法来注册该关闭钩子。

你可能已经注意到,不需要像启动其他线程一样调用关闭钩子的start方法。虚拟机会在它运行其关闭序列时启动并执行关闭钩子。

第十七章 启动Tomcat

windows和linux脚本编写

暂略

第十八章 部署器

暂略

第十九章 Manager应用程序的servlet类

暂略

第二十章 基于JMX的管理

暂略

相关推荐
Pandaconda3 小时前
【Golang 面试题】每日 3 题(二十一)
开发语言·笔记·后端·面试·职场和发展·golang·go
Zhichao_973 小时前
【UE5 C++课程系列笔记】21——弱指针的简单使用
笔记·ue5
快乐非自愿4 小时前
一文解秘Rust如何与Java互操作
java·开发语言·rust
Naiva4 小时前
ESP32-C3 入门笔记08:多帧数据解析
笔记·notepad++
小万编程4 小时前
基于SpringBoot+Vue毕业设计选题管理系统(高质量源码,提供文档,免费部署到本地)
java·vue.js·spring boot·计算机毕业设计·java毕业设计·web毕业设计
m0_748235074 小时前
使用rustDesk搭建私有远程桌面
java
快乐是5 小时前
发票打印更方便
java
文浩(楠搏万)5 小时前
Java内存管理:不可达对象分析与内存泄漏优化技巧 Eclipse Memory Analyzer
java·开发语言·缓存·eclipse·内存泄漏·不可达对象·对象分析
圆蛤镇程序猿5 小时前
【什么是MVCC?】
java·数据库·oracle
m0_748256785 小时前
【SQL】掌握SQL查询技巧:数据分组与排序
java·jvm·sql