Tomcat源码分析-启动分析(二) Catalina初始化

本文深入解析Tomcat源码,关注Engine与Connector的初始化过程。Engine初始化涉及Realm获取及ContainerBase的startStopExecutor线程池,用于容器启动和停止时的多线程操作。Connector初始化则包含Coyote适配器的实例化,ProtocolHandler的配置,特别是HTTP/1.1协议的ProtocolHandler初始化。ProtocolHandler作为接口,其子类如NIO协议的Http11NioProtocol在初始化时进行JMX注册及NioEndpoint的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Engine初始化

StandardEngine在init阶段,需要获取Realm,这个Realm是干嘛用的?

Realm(域)是用于对单个用户进行身份验证的底层安全领域的只读外观,并标识与这些用户相关联的安全角色。

StandardEngine初始化的代码如下:

@Override
protected void initInternal() throws LifecycleException {
    getRealm();
    super.initInternal();
}

public Realm getRealm() {
    Realm configured = super.getRealm();
    if (configured == null) {
        configured = new NullRealm();
        this.setRealm(configured);
    }
    return configured;

由前面的类图可知,StandardEngine继承至ContainerBase,而ContainerBase重写了initInternal()方法,用于初始化start、stop线程池,这个线程池有以下特点:
1、 core线程和max是相等的,默认为1
2、 允许core线程在超时未获取到任务时退出线程
3、 线程获取任务的超时时间是10s,也就是说所有的线程(包括core线程),超过10s未获取到任务,那么这个线程就会被销毁

这么做的初衷是什么呢?因为这个线程池只需要在容器启动和停止的时候发挥作用,没必要时时刻刻处理任务队列

ContainerBase的代码如下所示:

// 默认是1个线程
private int startStopThreads = 1;
protected ThreadPoolExecutor startStopExecutor;

@Override
protected void initInternal() throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    startStopExecutor = new ThreadPoolExecutor(
            getStartStopThreadsInternal(),
            getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
            startStopQueue,
            new StartStopThreadFactory(getName() + "-startStop-"));
    // 允许core线程超时未获取任务时退出
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}

private int getStartStopThreadsInternal() {
    int result = getStartStopThreads();

    if (result > 0) {
        return result;
    }
    result = Runtime.getRuntime().availableProcessors() + result;
    if (result < 1) {
        result = 1;
    }
    return result;

这个startStopExecutor线程池有什么用呢?

1、 在start的时候,如果发现有子容器,则会把子容器的start操作放在线程池中进行处理
2、 在stop的时候,也会把stop操作放在线程池中处理

在前面的文章中我们介绍了Container组件,StandardEngine作为顶层容器,它的直接子容器是StardandHost,但是对StandardEngine的代码分析,我们并没有发现它会对子容器StardandHost进行初始化操作,StandardEngine不按照套路出牌,而是把初始化过程放在start阶段。个人认为Host、Context、Wrapper这些容器和具体的webapp应用相关联了,初始化过程会更加耗时,因此在start阶段用多线程完成初始化以及start生命周期,否则,像顶层的Server、Service等组件需要等待Host、Context、Wrapper完成初始化才能结束初始化流程,整个初始化过程是具有传递性的

Connector初始化

Connector也是继承至LifecycleMBeanBase,公共的初始化逻辑都是一样的。我们先来看下Connector的默认配置,大部分属性配置都可以在Connector类中找到,tomcat默认开启了HTTP/1.1、AJP/1.3,其实AJP的用处不大,可以去掉

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

Connector定义了很多属性,比如port、redirectPort、maxCookieCount、maxPostSize等等,比较有意思的是竟然找不到connectionTimeout的定义,全文搜索后发现使用了属性名映射,估计是为了兼容以前的版本

protected static final HashMap<String,String> replacements = new HashMap<>();
static {
    replacements.put("acceptCount", "backlog");
    replacements.put("connectionLinger", "soLinger");
    replacements.put("connectionTimeout", "soTimeout");
    replacements.put("rootFile", "rootfile");
}

public Object getProperty(String name) {
    String repl = name;
    if (replacements.get(name) != null) {
        repl = replacements.get(name);
    }
    return IntrospectionUtils.getProperty(protocolHandler, repl);
}

public boolean setProperty(String name, String value) {
    String repl = name;
    if (replacements.get(name) != null) {
        repl = replacements.get(name);
    }
    return IntrospectionUtils.setProperty(protocolHandler, repl, value);

initInternal过程如下所示:
1、 实例化Coyote适配器,这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的,后续的博客会进行深入分析
2、 为ProtocolHander指定CoyoteAdapter用于处理请求
3、 初始化ProtocolHander,这一部分放在Connector后面进行分析

protected void initInternal() throws LifecycleException {

    // 注册jmx
    super.initInternal();

    // 初始化Coyote适配器,这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的
    adapter = new CoyoteAdapter(this);

    // protocolHandler需要指定Adapter用于处理请求
    protocolHandler.setAdapter(adapter);

    // Make sure parseBodyMethodsSet has a default
    if (null == parseBodyMethodsSet) {
        setParseBodyMethods(getParseBodyMethods());
    }

    // apr支持,忽略部分代码......

    // 初始化ProtocolHandler,这个init不是Lifecycle定义的init,而是ProtocolHandler接口的init
    try {
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }

ProtocolHandler初始化

接下来,我们分析下HTTP/1.1的ProtocolHandler的初始化过程。首先,我们来认识下ProtocolHandler,它是一个抽象的协议实现,它不同于JNI这样的Jk协议,它是单线程、基于流的协议。ProtocolHandler是一个Cycote连接器实现的主要接口,而Adapter适配器是由一个Coyote Servlet容器实现的主要接口,定义了处理请求的抽象接口。

public interface ProtocolHandler {

    public void setAdapter(Adapter adapter);
    public Adapter getAdapter();
    public Executor getExecutor();

    public void init() throws Exception;
    public void start() throws Exception;
    public void pause() throws Exception;
    public void resume() throws Exception;
    public void stop() throws Exception;
    public void destroy() throws Exception;

    // other code......
}

public interface Adapter {

    public void service(Request req, Response res) throws Exception;

    public boolean prepare(Request req, Response res) throws Exception;

    public boolean asyncDispatch(Request req,Response res, SocketEvent status) throws Exception;

    public void log(Request req, Response res, long time);

    public void checkRecycled(Request req, Response res);

    public String getDomain();

ProtocolHandler的子类如下所示,AbstractProtocol是基本的实现,而NIO默认使用的是Http11NioProtocol

调用ProtocolHandler的init进行初始化是调用的AbstractProtocol,首先完成jmx的注册,然后对NioEndpoint进行初始化

public abstract class AbstractProtocol<S> implements ProtocolHandler,
        MBeanRegistration {
    public void init() throws Exception {
        // 完成jmx注册
        if (oname == null) {
            oname = createObjectName();
            if (oname != null) {
                Registry.getRegistry(null, null).registerComponent(this, oname, null);
            }
        }
        if (this.domain != null) {
            rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
            Registry.getRegistry(null, null).registerComponent(
                    getHandler().getGlobal(), rgOname, null);
        }

        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length()-1));
        endpoint.setDomain(domain);

        // 初始化endpoint
        endpoint.init();
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值