Spring Web: HTTP 请求处理(施工中)

碰巧最近在对接压力测试,在 HTTP 压测接口时还是产生了诸多疑问。考虑到网上大部分都仅解释从 DispatcherServlet 后开始的逻辑,这里只尽个人能力进行实践和整理,以便于日后使用 Arthas 分析应用时能够有更清晰的认知。

下面是 DEMO:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
@Data
public class HelloEntity {

    private String key;
    private String value;
}
@RequestMapping("/demo")
@RestController
public class HelloController {

    @Autowired
    HelloService helloService;

    @GetMapping(value = "/hello")
    public HelloEntity hello() {
        return helloService.hello();
    }
}
public interface HelloService {

    HelloEntity hello();
}
@Service
public class HelloServiceImpl implements HelloService {

    @Override
    public HelloEntity hello() {
        HelloEntity hello = new HelloEntity();
        hello.setKey("hello");
        hello.setValue("world!");
        return hello;
    }
}

服务启动

别动不动跟着面试八股去看 DispatcherServlet 了,Spring Boot 启动的时候也有很多信息需要关注的:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.3.12.RELEASE)

2023-02-24 15:39:00.270  INFO 21420 --- [           main] c.example.springMvcDemo.DemoApplication  : Starting DemoApplication on DESKTOP-BSTI8OH with PID 21420 (D:\AWork\spring-mvc-demo\target\classes started by wujun in D:\AWork\spring-mvc-demo)
2023-02-24 15:39:00.274  INFO 21420 --- [           main] c.example.springMvcDemo.DemoApplication  : No active profile set, falling back to default profiles: default
2023-02-24 15:39:01.057  INFO 21420 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8888 (http)
2023-02-24 15:39:01.065  INFO 21420 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-02-24 15:39:01.065  INFO 21420 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.46]
2023-02-24 15:39:01.142  INFO 21420 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-02-24 15:39:01.142  INFO 21420 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 791 ms
2023-02-24 15:39:01.850  INFO 21420 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2023-02-24 15:39:02.087  INFO 21420 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8888 (http) with context path ''
2023-02-24 15:39:02.101  INFO 21420 --- [           main] c.example.springMvcDemo.DemoApplication  : Started DemoApplication in 2.532 seconds (JVM running for 3.512)
2023-02-24 15:45:13.890  INFO 21420 --- [nio-8888-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-02-24 15:45:13.891  INFO 21420 --- [nio-8888-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-02-24 15:45:13.899  INFO 21420 --- [nio-8888-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms

众所周知 Spring Boot 已内嵌了 Tomcat。在 INFO 中关注几个关键信息:

  1. TomcatWebServer
  2. StandardService
  3. StandardEngine
  4. ServletWebServerApplicationContext
  5. ThreadPoolTaskExecutor
  6. DemoApplication
  7. DispatcherServlet

既然方向都指明了,不妨一个一个都看看。

TomcatWebServer

WebServer,可用于控制 Tomcat Web 服务器。
通常应该使用 TomcatServletWebServerFactory 的 TomcatServletWebServerFactory 创建此类,不能直接创建。

可以在 initialize() 中看到日志来源,里面很多方法并不需要深入研究,仅通过命名和注释了解它的基本作用。

private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            // 将实例Id添加到引擎名称
            addInstanceIdToEngineName();
            // 查找 Context
            Context context = findContext();
            // 增加生命周期监听器到此组件
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                    // 删除服务连接器,以便在服务启动时不会发生协议绑定
                    removeServiceConnectors();
                }
            });

            // 启动 Server (注意不是启动 WebServer)以触发初始化监听器
            this.tomcat.start();

            // 我们可以直接在主线程中重新抛出失败异常
            rethrowDeferredStartupExceptions();

            try {
                ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
            }
            catch (NamingException ex) {
                // Naming is not enabled. Continue
            }

            // 与Jetty不同,所有 Tomcat 线程都是守护线程
            // 我们创建一个阻塞的非守护线程来停止立即关闭
            startDaemonAwaitThread();
        }
        catch (Exception ex) {
            stopSilently();
            destroySilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
    }
}

注意在当前类中除了 initialize() 方法外还有一个 start() 方法,对应第二条 TomcatWebServer 的 INFO 日志,在后面说明。

初识 StandardService

官方注释:
Service 接口的标准实现。关联的容器通常是 Engine 的实例,但不是必需的。

首先还是要找到 INFO 日志的打印位置作为切入点,观察 startInternal() 方法:

// 启动嵌套组件 Executors、Connectors 和 Containers 并实现 
// org.apache.catalina.util.LifecycleBasestartInternal() 的要求
@Override
protected void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);

    // 第一步,启动 Container
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    // 启动 Executor
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();

    // 第二步,启动 Connector
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

我们不妨在 log.info(sm.getString(“standardService.start.name”, this.name)); 处打断点,debug 可以看到,StandardService 的 INFO 日志即在此打印。

注意这里使用了 StringManager (sm) 去获取异常信息,这是 Tomcat 用于解耦异常信息所采取的方式。如果我们在此直接使用 log.info(“Exception String”),非常不利于信息内容的修改。此外,搭配 Resource Bundle 可以方便地实现信息内容的国际化。

StringManager 实例化后,能够获取 LocalString.properties 文件中的存储的异常信息。比如打开 org.apache.tomcat.embed:tomcat-embed-core:9.0.46.jar 包中的 org.apache.catalina.core.LocalString.properties 文件可以找到如下内容:

同理,后续的 standardEngine:start 也一样:

现在继续往下看代码,可以看到分别启动了 —— Container, Executor, Connector,又是一些非常陌生的类,但是我们可以找出一些共性方便我们去理解,比如说它们都调用了 start() 方法,所以它们是有实现一个共同的接口吗?

没错,我们在这里暂时跳出 StandardService,去看一下 org.apache.catalina.Lifecycle 的接口描述:

Common interface for component life cycle methods.
Catalina components may implement this interface (as well as the appropriate interface(s) for the functionality they support) in order to provide a consistent mechanism to start and stop the component.

组件生命周期方法的通用接口。
Catalina 组件可以实现该接口(以及它们支持的功能的适当接口),以便提供一致的启动和停止组件的机制。

Tomcat 的各个组件都遵循一套生命周期逻辑,因而抽出了一个通用的 Lifecycle 接口用于控制各个组件的活动。核心方法包括:

  • init
  • start
  • stop
  • destroy

Tomcat 的组件大多继承 Lifecycle 接口,当然也包括 startInternal() 方法中所见到的三个:

所以非常好理解,StandardService 的 startInternal() 方法按顺序启动了 Tomcat 中的一些组件。

这里还要再加点题外话,虽然我们切入是通过 StandardService 输出的 INFO 日志,但调用 StandardService 并非是 Tomcat 的启动流程的开始。Tomcat 启动的最开始在 org.apache.catalina.startup.Bootstrap,我们可以在此类的 main() 方法注释中看到:

Main method and entry point when starting Tomcat via the provided scripts.
通过提供的脚本启动 Tomcat 时的主要方法和入口点。

所以这里有必要把 StandardService 之前发生的事情也交代一下。

Bootstrap

主方法:

public static void main(String args[]) {
    // 加锁
    synchronized (daemonLock) {
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                // 主要关注 init() 方法
                // 该方法用于初始化守护线程
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            // 当作为服务运行时,停止调用命令将在新线程上进行
            // 因此确保使用了正确的类加载器来防止一系列类未找到的异常。
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
    }

    // 以下为根据 args 给的参数执行对应逻辑
    try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }

        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {
            daemon.setAwait(true);
            daemon.load(args);
            daemon.start();
            if (null == daemon.getServer()) {
                System.exit(1);
            }
        } else if (command.equals("stop")) {
            daemon.stopServer(args);
        } else if (command.equals("configtest")) {
            daemon.load(args);
            if (null == daemon.getServer()) {
                System.exit(1);
            }
            System.exit(0);
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist.");
        }
    } catch (Throwable t) {
        // Unwrap the Exception for clearer error reporting
        if (t instanceof InvocationTargetException &&
                t.getCause() != null) {
            t = t.getCause();
        }
        handleThrowable(t);
        t.printStackTrace();
        System.exit(1);
    }
}

main 方法下的 init() 方法

// 初始化守护线程
public void init() throws Exception {

    // 初始化类加载器,包含 commonLoader, catalinaLoader, sharedLoader
    initClassLoaders();

    // 设置当前线程的类加载器为 catalinaLoader
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class");

    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    // 实例化一个 Catalina 对象 
    Object startupInstance = startupClass.getConstructor().newInstance();

    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");

    // 设置共享扩展类加载器
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);

    // 使用反射调用 setParentClassLoader
    // 等同于 startupInstance.setParentClassLoader(sharedLoader);
    method.invoke(startupInstance, paramValues);

    // 拿到实例化后的对象给 (ClassLoader) catalinaDaemon
    catalinaDaemon = startupInstance;
}

当 command 为 start 时,会调用 load() 和 start() 方法,基本上就是通过反射调用 catalina 实例的对应方法

// 加载 catalina daemon
private void load(String[] arguments) throws Exception {

    // Call the load() method
    String methodName = "load";
    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled()) {
        log.debug("Calling startup class " + method);
    }
    // 使用反射调用 catalinaDaemon 的 load 方法,并传入参数
    method.invoke(catalinaDaemon, param);
}
// 开始 catalina deamon
public void start() throws Exception {
    // 确保 catalinaDaemon 不为空,不然重新调用 init()
    if (catalinaDaemon == null) {
        init();
    }

    // 使用反射调用 catalinaDaemon 的 start 方法,并传入参数
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}

那么 Bootstrap 作为一个启动类就看完了。主要的疑问就转移到了 org.apache.catalina.startup.Catalina 下。

Catalina

Catalina 下 load 方法的主要内容:

// 开启一个新的服务实例
public void load() {

    // 判断已加载标志
    if (loaded) {
        return;
    }
    loaded = true;

    // 记录时间
    long t1 = System.nanoTime();

    // 此方法内容为空,已被移除
    initDirs();

    // 初始化名称(可能需要)
    initNaming();

    // 解析 server.xml
    // 主要内容,下面会解释
    parseServerXml(true);

    // 拿到 server
    Server s = getServer();
    if (s == null) {
        return;
    }

    // 将当前 catalina 实例赋给 server
    getServer().setCatalina(this);
    // 从 Bootsrap 中获取到 File 并设置给 server
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // 流重定向
    initStreams();

    // 开启新服务(调用 server 的 init 方法)
    try {
        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
            log.error(sm.getString("catalina.initError"), e);
        }
    }

    if(log.isInfoEnabled()) {
        log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
    }
}

对于 parseServerXml 的代码如果嫌麻烦可以直接看后面

// 解析 server.xml 文件
protected void parseServerXml(boolean start) {
    // 设置配置文件的路径
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
    File file = configFile();

    // Digester 用于解析 server.xml 文件
    // 如果选择使用生成代码替代配置文件,并且尚未生成 Loader
    if (useGeneratedCode && !Digester.isGeneratedCodeLoaderSet()) {
        // 加载 Loader
        String loaderClassName = generatedCodePackage + ".DigesterGeneratedCodeLoader";
        try {
            // 使用反射获得一个实例,并配置到 Digester 中
            Digester.GeneratedCodeLoader loader =
                    (Digester.GeneratedCodeLoader) Catalina.class.getClassLoader().loadClass(loaderClassName).newInstance();
            Digester.setGeneratedCodeLoader(loader);
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.info(sm.getString("catalina.noLoader", loaderClassName), e);
            } else {
                log.info(sm.getString("catalina.noLoader", loaderClassName));
            }
            // 没有 Loader,因此无法进行代码替代
            useGeneratedCode = false;
        }
    }

    // 初始化
    File serverXmlLocation = null;
    String xmlClassName = null;
    // 加载代码,需要用到 xmlClassName,而保存生成的代码,需要记录当前类已被生成
    if (generateCode || useGeneratedCode) {
        xmlClassName = start ? generatedCodePackage + ".ServerXml" : generatedCodePackage + ".ServerXmlStop";
    }
    // 要生成 Tomcat 内嵌代码,如果流程顺利会准备好 serverXmlLocation
    if (generateCode) {
        // 地址参数不为空
        if (generatedCodeLocationParameter != null) {
            // 读取文件
            generatedCodeLocation = new File(generatedCodeLocationParameter);
            // 非完全路径,表示相对路径,则根据 HomeFile 找到子文件
            if (!generatedCodeLocation.isAbsolute()) {
                generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), generatedCodeLocationParameter);
            }
        } else {
            // generatedCodeLocationParameter 为空,则直接使用 HomeFile
            generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), "work");
        }
        // 根据 generatedCodeLocation 找名称为 catalinaembedded 的文件
        // 这里的 generatedCodePackage 值为 catalinaembedded
        serverXmlLocation = new File(generatedCodeLocation, generatedCodePackage);
        // 如果路径不为文件夹且无法创造文件夹,无法进行代码生成
        if (!serverXmlLocation.isDirectory() && !serverXmlLocation.mkdirs()) {
            log.warn(sm.getString("catalina.generatedCodeLocationError", generatedCodeLocation.getAbsolutePath()));
            // Disable code generation
            generateCode = false;
        }
    }
    
    ServerXml serverXml = null;
    // 如果选择用生成代码替代配置文件,则使用 Digester 加载之前的 xmlClassName,创建 serverXml 对象
    if (useGeneratedCode) {
        serverXml = (ServerXml) Digester.loadGeneratedClass(xmlClassName);
    }
    // 如果成功,则加载,不成功,则表示替代失败
    if (serverXml != null) {
        serverXml.load(this);
    } else {
        // 尝试从 ConfigFileLoader 的源路径获取 ServerXml 的资源
        try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
            // 创建并执行 Digester,设置解析规则
            // createStartDigester() 定义了各个 XML 节点对应的接口的实现类
            Digester digester = start ? createStartDigester() : createStopDigester();
            // 读取流
            InputStream inputStream = resource.getInputStream();
            InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
            inputSource.setByteStream(inputStream);
            // 将新对象推到对象堆栈的顶部
            digester.push(this);
            // 如果要生成 Tomcat 内嵌代码
            if (generateCode) {
                digester.startGeneratingCode();
                generateClassHeader(digester, start);
            }
            // 使用 Digester 分析指定输入源的内容。返回对象堆栈中的根元素(如果有)
            digester.parse(inputSource);
            // 如果 从配置文件生成 Tomcat 嵌入代码
            if (generateCode) {
                // 保证 generateCode 的格式
                generateClassFooter(digester);
                // 写入 generateCode, 名称为 "ServerXml.java" 或 "ServerXmlStop.java"
                // 存放的路径为前面拿到的 serverXmlLocation
                try (FileWriter writer = new FileWriter(new File(serverXmlLocation,
                        start ? "ServerXml.java" : "ServerXmlStop.java"))) {
                    writer.write(digester.getGeneratedCode().toString());
                }
                // 结束生成代码
                digester.endGeneratingCode();
                // 标记已生成的 xmlClass
                Digester.addGeneratedClass(xmlClassName);
            }
        } catch (Exception e) {
            log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
            if (file.exists() && !file.canRead()) {
                log.warn(sm.getString("catalina.incorrectPermissions"));
            }
        }
    }
}

这里还是简要的总结一下 load() 所做的工作。其实大致分成了两步,第一步是解析 server.xml 文件,第二步是调用了 server.init()。

比较繁琐的是 parseServerXml 的工作,大体来说有两个关键参数,generateCode 表示是否通过配置文件生成 Tomcat 的内嵌代码,useGeneratedCode 是否使用生成的代码替换配置文件(可以生成内嵌代码但不替换),默认值都为 false。

关于为什么要增加这两个配置,单纯的注释给的信息太少,我这里的猜测是生成代码后保存在文件夹内,再次启动时可以读取之前生成的代码快速加载。这里给出 ChatGPT 的回答:

首先此方法要去读取 server 文件,然后创建 Digester,设置解析规则,定义各个 XML 节点对应的接口的实现类。其实还可以更简单粗暴的理解这个方法,就是删除所有和 generateCode/useGeneratedCode 相关的逻辑:

// 解析 server.xml 文件
protected void parseServerXml(boolean start) {
    // 设置配置文件的路径
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));

    // 初始化源
    ServerXml serverXml = null;

    // 没有使用生成代码或生成代码失败
    if (serverXml != null) {
        serverXml.load(this);
    } else {
        // 尝试从 ConfigFileLoader 的源路径获取 ServerXml 的资源
        try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
            // 创建并执行 Digester,设置解析规则
            // createStartDigester() 定义了各个 XML 节点对应的接口的实现类
            Digester digester = start ? createStartDigester() : createStopDigester();
            // 读取流
            InputStream inputStream = resource.getInputStream();
            InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
            inputSource.setByteStream(inputStream);
            // 将新对象推到对象堆栈的顶部
            digester.push(this);
            // 使用 Digester 分析指定输入源的内容。返回对象堆栈中的根元素(如果有)
            digester.parse(inputSource);
        } catch (Exception e) {
            ...
        }
    }
}

然后来看 Catalina#start() 方法:

public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        log.fatal(sm.getString("catalina.noServer"));
        return;
    }

    long t1 = System.nanoTime();

    // 调用 server.start() 方法
    try {
        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    if (log.isInfoEnabled()) {
        log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
    }

    if (generateCode) {
        // 生成加载程序,该加载程序将加载所有生成的类
        generateLoader();
    }

    // 注册关闭挂钩
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // 如果正在使用 JULI,请禁用 JULI 的关闭挂钩,因为关闭挂钩并行运行
        // 如果JULI的挂钩在 CatalinaShutdownHook() 之前完成,则日志消息可能会丢失
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }

    if (await) {
        await();
        stop();
    }
}

这里我画一个图来梳理一下到目前的逻辑:

StandardServer

我们能看到 Bootstrap 的 main() 方法如果刨去初始化过程和各种状态/异常判断,执行顺利的话,就是调用了 StandardServer 的 initInternal() 和 startInternal()。

// 启动前初始化。这用于在 Unix 操作环境下允许 Connector 绑定到受限端口
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    // 初始化实用程序执行器
    reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
    register(utilityExecutor, "type=UtilityExecutor");

    // 注册全局 String 缓存
    // 请注意,虽然缓存是全局的,但如果 JVM 中存在多个服务器(可能在嵌入时发生)
    // 则同一缓存将以多个名称注册
    onameStringCache = register(new StringCache(), "type=StringCache");

    // 注册 MBeanFactory
    // factory 的容器为当前 Server
    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    onameMBeanFactory = register(factory, "type=MBeanFactory");

    // 注册名称资源
    globalNamingResources.init();

    // 使用来自公共和共享类加载器的 JAR 填充扩展验证器
    if (getCatalina() != null) {
        ClassLoader cl = getCatalina().getParentClassLoader();
        // 遍历类加载器层次结构,在系统类加载器处停止。
        // 这将添加共享(如果存在)和公共类加载器
        while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
            if (cl instanceof URLClassLoader) {
                URL[] urls = ((URLClassLoader) cl).getURLs();
                for (URL url : urls) {
                    if (url.getProtocol().equals("file")) {
                        try {
                            File f = new File (url.toURI());
                            if (f.isFile() &&
                                    f.getName().endsWith(".jar")) {
                                ExtensionValidator.addSystemResource(f);
                            }
                        } catch (URISyntaxException | IOException e) {
                            // Ignore
                        }
                    }
                }
            }
            cl = cl.getParent();
        }
    }
    // 初始化定义的 Service
    for (Service service : services) {
        service.init();
    }
}

这里完成了很多资源和信息的注册,最后调用 Service 的 init(),再看看 StandardServer 的 startInternal() 方法:

// 启动嵌套组件 Service 并实现 org.apache.catalina.util.LifecycleBase#startInternal() 的要求
@Override
protected void startInternal() throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    // 保存和管理在 J2EE 企业命名上下文及其关联的 JNDI 上下文中定义的命名资源
    globalNamingResources.start();

    // 启动我们定义的服务
    synchronized (servicesLock) {
        for (Service service : services) {
            service.start();
        }
    }

    if (periodicEventDelay > 0) {
        monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
                () -> startPeriodicLifecycleEvent(), 0, 60, TimeUnit.SECONDS);
    }
}

同样的,主要是一些状态的处理,并调用了 Service 的 start() 。我们已经知道 Bootstrap 里面调用 Server 的 start(),实际上会去调用实现类 StandardServer 的 startInternal()。

初识 StandardService 时,StandardService 里面也有实现一个 startInternal()。

是不是开始感受到一些结构设计了。我们知道 Server 和 Service 的 start() 方法都是由 Lifecycle 定义的。而 Lifecycle 接口实现了一个抽象类 LifecycleBase,由 LifecycleBase 类在 start() 方法内对当前组件的状态进行判断,并在内部调用抽象方法 startInternal()。StandardServer 和 StandardService 都是继承了 LifecycleBase 的子类,因此也就都需要实现 startInternal() 这个抽象方法。

所以这里 startInternal() 方法最终由最底层的实现类 StandardService 实现。

这里我表达一下自己对这种设计的优点的理解,为了简化模型方便理解,我们只看 Server 和 Service,先来看一下接口和类之间的关系:

就只看 start() 方法,在 LifecycleBase 中,对 start() 进行了实现,实际将 start() 分成了两部分(外层和内层),startInternal() 部分是内层,依然是交由子类实现,而外层是状态控制逻辑,依据组件在调用 start() 方法时的当前状态,采取不同的处理逻辑。

比如当前组件的状态是 STARTING_PREP, STARTING, STARTED,则当前组件就不需要再次启动。如果当前组件的状态是 NEW,则应该在启动前补充一次初始化。

由于所有组件都遵循一致的生命周期模型,所以这里的状态控制逻辑可以被所有子类(对应组件的实现类)复用。

再看 StandardService

在 StandardServer 中,对多个 Service 进行了 start()。根据之前总结的规律我们知道,如果 start() 方法顺利调用,经过状态控制逻辑后,便会执行 startInternal(),而在初次看 StandardService 时我们已经知道其中的 startInternal() 方法内会分别启动 Engine(Container), Executor, Connector 的 start() 方法。

这个时候需要继续进入查看 Engine Executor 和 Connector 。

StandardEngine

我们先来到 StandardEngine 类,首先继续关注其 initInternal() 方法和 startInternal() 方法,因为对于一个新的 Engine,在其启动顺利的情况下,必然会先后执行这两个方法。首先来看 initInternal():

// StandardEngine
@Override
protected void initInternal() throws LifecycleException {
    // 保证启动前存在 Realm,获取已配置的 Realm,如果没有也会提供默认实现
    getRealm();
    // 这里需要继续去看 ContainerBase 和 LifecycleMBeanBase 的 initInternal() 方法
    super.initInternal();
}

这里需要解释一下 Realm:

// ContainerBase
@Override
protected void initInternal() throws LifecycleException {
    // 通过 getStartStopThreads() 获得当前配置的用于启动停止与此容器关联的子级的线程数
    // 重新配置 StartStopExecutor
    // StartStopExecutor 是一个线程池,用于启动和停止容器
    // 容器启动/停止时,StartStopExecutor 用于异步地启动/停止容器中的组件
    reconfigureStartStopExecutor(getStartStopThreads());
    // 进入 LifecycleMBeanBase 的 initInternal() 方法
    super.initInternal();
}
// LifecycleMBeanBase
@Override
protected void initInternal() throws LifecycleException {
    // If oname is not null then registration has already happened via
    // preRegister().
    if (oname == null) {
        // getRegistry() 通过工厂类方法返回 Registry 实例
        // getMBeanServer() 通过工厂类方法返回 MBeanServer 实例,如果不存在则创建
        mserver = Registry.getRegistry(null, null).getMBeanServer();

        // 创建 oname,该名称将作为该组件在 MBeanServer 中的唯一标识符
        oname = register(this, getObjectNameKeyProperties());
    }
}

稍作总结,StandardEngine 的 initInternal() 整个流程简要做了哪些工作:

  1. 获取(或创建) Realm
  2. 重新配置(或创建) StartStopExecutor
  3. 获取(或创建) MBeanServer 和 ObjectName

之后来关注 StandardEngine 的 startInternal() 方法,可以发现除了打印日志之外,直接调用了父类的方法实现:

// StandardEngine
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Log our server identification information
    if (log.isInfoEnabled()) {
        log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
    }

    // Standard container startup
    super.startInternal();
}

之后来关注 StandardEngine 的 startInternal() 方法,可以发现除了打印日志之外,直接调用了父类的方法实现:

// StandardEngine
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Log our server identification information
    if (log.isInfoEnabled()) {
        log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
    }

    // Standard container startup
    super.startInternal();
}

同理,super.startInternal() 方法会进入 ContainerBase 的 startInternal() 方法中:

// ContainerBase
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Start our subordinate components, if any
    // 如果存在,启动附属组件
    logger = null;
    // 获取日志组件
    getLogger();
    // 提供集群组件连接到当前容器的方式
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    // 如果存在,启动子容器
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    MultiThrowable multiThrowable = null;

    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    if (multiThrowable != null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                multiThrowable.getThrowable());
    }

    // Start the Valves in our pipeline (including the basic), if any
    // 开放管道的阀门
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    // 设置当前生命周期状态
    setState(LifecycleState.STARTING);

    // Start our thread
    // 启动线程
    if (backgroundProcessorDelay > 0) {
        monitorFuture = Container.getService(ContainerBase.this).getServer()
                .getUtilityExecutor().scheduleWithFixedDelay(
                        new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
    }
}

在 ContainerBase 的 startInternal() 方法中,会启动相关的组件,例如:

  1. Logger
  2. Cluster
  3. Realm
  4. Pipeline