欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Tomcat源码解析:初始化

程序员文章站 2022-07-14 10:53:24
...

Bootstrap

启动Tomcat只需要执行Bootstrap中的#main方法即可。

public static void main(String[] args) {
     Bootstrap bootstrap = new Bootstrap();
     bootstrap.init();//实例化Catalina,并设置classloader
    //...
    if (command.equals("start")) {
        daemon.setAwait(true);
        daemon.load(args);//解析xml,实例化组件,并执行Lifecycle的init阶段
        daemon.start();//start阶段
    }
}

init

初始化classloader ,实例化Catalina类,并且反射调用setParentClassLoader将sharedLoader设置进去。

public void init() throws Exception {
    //初始化commonLoader catalinaLoader sharedLoader
    initClassLoaders();

    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");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // Set the shared extensions class loader
    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);
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;
}

load

load方法会反射调用Catalina#load方法进行解析xml,实例化组件,并执行Lifecycle的init阶段。

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);
    }
    method.invoke(catalinaDaemon, param);
}

Catalina

load

public void load() {

    //...

    initDirs();

    // Before digester - it may be needed
    initNaming();

    // Set configuration source //获取server.xml的存放位置
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
    File file = configFile();

    // Create and execute our Digester 
    //将需要的class,属性及需要执行的方法封装存入Digester 
    // 定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类
    Digester digester = createStartDigester();
    //解析server.xml
    try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
        InputStream inputStream = resource.getInputStream();
        InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
        inputSource.setByteStream(inputStream);
        digester.push(this);// 把Catalina作为一个*实例
        digester.parse(inputSource);//解析xml,会实例化各个组件,比如Server、Container、Connector等
    } catch (Exception e) {
        //...
    }
    // 给Server设置catalina信息
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();//初始化System.out和err

    // Start the new server 
    // 调用Lifecycle的init阶段
    //...
    getServer().init();
    
}
  • 首先找到server.xml
  • 使用Digester来解析xml,并按执行提前配置好的规则
  • 调用Lifecycle的init阶段

因为Server、Container、Connector等的实例化是在解析xml时完成的,所以需要先了解一下Digester是什么。

Digester

Digester是一款用于将XML转换成Java对象的事件驱动型工具,是对SAX的高层次封装。它提供一套对象栈机制用于构建Java对象,这是因为XML是分层结构,所以创建的Java对象也应该是分层级的树状结构,而且还要根据XML内容组织各层级Java对象的内部结构以及设置相关属性。

这里主要对Digester在Tomcat中使用的处理规则进行说明。

规则类 描述
ObjectCreateRule 当begin()方法调用时,会将指定的java class实例化,并将其放入对象栈。具体的java类可由该规则的构造方法传入,也可以通过当前处理XML节点的某个属性指定,属性名称通过构造方法传入。当end()方法调用时,创建的对象将从栈中取出
SetPropertiesRule 当begin()方法调用时,Digester使用setter方法将XML节点属性值设置到栈顶的对象中。
SetNextRule 当end()方法调用时,Digester会栈顶对象实例作为属性设置到父class(即栈顶对象后一个对象,index为1)中

这里拿ObjectCreateRule来做个示范

ObjectCreateRule

public void begin(String namespace, String name, Attributes attributes)
    throws Exception {

    String realClassName = getRealClassName(attributes);
    //...
    // Instantiate the new object and push it on the context stack
    Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
    Object instance = clazz.getConstructor().newInstance();
    digester.push(instance);
}

public void end(String namespace, String name) throws Exception {
    Object top = digester.pop();
    //...
}

而层级结构在createStartDigester()配置,这里同时会初始化监听器,包括定义在server.xml里的和createStartDigester()里的。

Catalina#createStartDigester

protected Digester createStartDigester() {
    Digester digester = new Digester();
    //...
    //创建server实例,默认实现为StandardServer
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");
    //...
}
  • 首先实例化StandardServer。
  • 然后将xml里配置的Server的属性,如port和shutdown等调用set方法设置到server
<Server port="8095" shutdown="SHUTDOWN">
    ...
</Server>
  • 最后调用setServer将当前StandardServer设置到Catalina中。

需要注意的是:EngineConfig,HostConfig,ContextConfig都是在这里作为生命周期监听器实例化并设置到对应的节点中去的。

这里放一张xml解析完成后的图,方便理解

Tomcat源码解析:初始化

init

解析完成后,会进行初始化,这里会调用LifecycleBase#init方法,从server开始调用init方法,进行初始化。

getServer().init();

LifecycleBase#init

这里先由LifecycleBase改变当前状态值为INITIALIZING,并发出事件通知,即执行当前事件的监听器,然后执行各个节点的初始化方法,最后在将状态设置为初始化完成INITIALIZED,并发出事件通知。这里的server,即StandardServer只会改变自己的状态值,而不会改变子容器service的状态值。而且考虑到并发修改状态值的问题,init方法使用了synchronized锁,并用volatile修饰state值。

public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        //将状态设置为初始化中,并执行监听器
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        //真正的初始化方法
        initInternal();
         //将状态设置为初始化完成,并执行监听器
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}

然后会执行server的初始化方法。

Server初始化

StadardServer#initInternal

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Initialize utility executor
    reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
    register(utilityExecutor, "type=UtilityExecutor");

    // 往jmx中注册全局的String cache,尽管这个cache是全局听,但是如果在同一个jvm中存在多个Server,
    // 那么则会注册多个不同名字的StringCache,这种情况在内嵌的tomcat中可能会出现
    onameStringCache = register(new StringCache(), "type=StringCache");

    // Register the MBeanFactory // 注册MBeanFactory,用来管理Server
    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    onameMBeanFactory = register(factory, "type=MBeanFactory");

    // Register the naming resources // 初始化NamingResources
    globalNamingResources.init();

    // Populate the extension validator with JARs from common and shared
    // lass loaders
    if (getCatalina() != null) {
        //...
    }
    // Initialize our defined Services  // 初始化内部的Service
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }
}
  1. 先是将自己注册到jmx
  2. 然后注册StringCache和MBeanFactory
  3. 初始化/NamingResources。
  4. 调用service的init

Service初始化

service的初始化也调用了lifecycleBase的init的方法,与server一样,这里主要关注本身的service方法。

StandardService#initInternal

protected void initInternal() throws LifecycleException {

    super.initInternal();
    // 初始化Engine
    if (engine != null) {
        engine.init();
    }

    // Initialize any Executors // 存在Executor线程池,则进行初始化,默认是没有的
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors // 初始化Connector
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}
  1. 注册jms
  2. 初始化Engine。
  3. 初始化线程池
  4. 初始化Connector

Engine初始化

StandardEgine#initInternal

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

public Realm getRealm() {
    Realm configured = super.getRealm();
    // If no set realm has been called - default to NullRealm
    // This can be overridden at engine, context and host level
    if (configured == null) {
        configured = new NullRealm();
        this.setRealm(configured);
    }
    return configured;
}
  1. 如果当前Engine没有显示配置Relam,则会获取默认的Relam实现,即NullRealm。这里在这个版本的server.xml里配置了LockOutRealm,用于防止通过蛮力攻击猜测用户密码的尝试。
<Realm className="org.apache.catalina.realm.LockOutRealm">
    <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
           resourceName="UserDatabase"/>
</Realm>
  1. 调用父类ContainerBaseinitInternal

ContainerBase#initInternal做了两个操作,首先创建start、stop线程池,而这个线程池默认情况下,线程数是1,然后注册jms。代码如下:

private int startStopThreads = 1;
protected ExecutorService startStopExecutor;

protected void initInternal() throws LifecycleException {
    reconfigureStartStopExecutor(getStartStopThreads());
    super.initInternal();
}


private void reconfigureStartStopExecutor(int threads) {
    if (threads == 1) {
        // Use a fake executor
        if (!(startStopExecutor instanceof InlineExecutorService)) {
            startStopExecutor = new InlineExecutorService();
        }
    } else {
        // Delegate utility execution to the Service
        Server server = Container.getService(this).getServer();
        server.setUtilityThreads(threads);
        startStopExecutor = server.getUtilityExecutor();
    }
}

public int getStartStopThreads() {
    return startStopThreads;
}

那么这个startStopExecutor的作用是什么呢?这部分代码在当前类的startInternalstopInternal方法中。

  1. 在start时,如果有子容器,将子容器的start方法放到线程池来操作
  2. 在stop时,同上。

到这里Engine的初始化就结束了,而他的子容器Host则是在start阶段才进行初始化。

Connector初始化

Connector的初始化和上面的步骤也一样,这里默认会对HTTP/1.1和AJP/1.3做初始化。这个配置来源于server.xml中。

<Connector port="8091" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

Connector#initInternal

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Initialize adapter
    // 初始化Coyote适配器
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    if (service != null) {
        protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
    }
    //...
    // 初始化ProtocolHandler,这个init不是Lifecycle定义的init,而是ProtocolHandler接口的init
    try {
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
            sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}
  1. 实例化CoyoteAdapter,并将其作为protocolHandler的适配器。

  2. 执行protocolHandler的初始化,这里的初始化并不是生命周期的init阶段,而是protocolHandler的init方法,而protocolHandler的配置是再Connector的构造方法里。

private int maxCookieCount = 200;

protected int maxParameterCount = 10000;

protected int maxPostSize = 2 * 1024 * 1024;

protected int maxSavePostSize = 4 * 1024;
public Connector(String protocol) {
    boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
        AprLifecycleListener.getUseAprConnector();

    if ("HTTP/1.1".equals(protocol) || protocol == null) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
        }
    } else if ("AJP/1.3".equals(protocol)) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
        }
    } else {
        protocolHandlerClassName = protocol;
    }

    // Instantiate protocol handler
    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;
    }

    // Default for Connector depends on this system property
    setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}

这里主要对Http11NioProtocol做分析,可以看到在实例化Connector时会初始化一些连接参数,但却没有connectTimeout,那么connectTimeout是在哪里被设置进去呢?,从Connector的构造函数中,看到实例化了一个默认的 Http11NioProtocol,而实例化Http11NioProtocol时,在其父类的构造方法里发现,会设置一个connectTimeout的默认值,默认值是60000,如果在server.xml里配置了connectTimeout,会在解析xml时覆盖这个默认值。

public Http11NioProtocol() {
    super(new NioEndpoint());
}

public AbstractHttp11JsseProtocol(AbstractJsseEndpoint<S,?> endpoint) {
    super(endpoint);
}

public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
    super(endpoint);
    setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
    setHandler(cHandler);
    getEndpoint().setHandler(cHandler);
}

ProtocolHandler初始化

Http11NioProtocol的init方法的主要实现是在AbstractProtocol里,首先完成jmx的注册,然后对NioEndpoint进行初始化。

AbstractProtocol#init

public void init() throws Exception {
   if (getLog().isInfoEnabled()) {
       getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
       logPortOffset();
   }

   if (oname == null) {
       // Component not pre-registered so register it
       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();
}

Endpoint初始化

Endpoint的初始化主要是完成对端口的绑定和监听。

AbstractEndpoint.java

public final void init() throws Exception {
    if (bindOnInit) {
        bindWithCleanup();//绑定端口,连接socket
        bindState = BindState.BOUND_ON_INIT;
    }
    if (this.domain != null) {
        // Register endpoint (as ThreadPool - historical name)
        //...
    }
}

private void bindWithCleanup() throws Exception {
    try {
        bind();//这里是重点
    } catch (Throwable t) {
        // ...
    }
}

而端口的绑定是在NioEndpoint里。

NioEndpoint#bind

public void bind() throws Exception {
    initServerSocket();

    // Initialize thread count defaults for acceptor, poller // 初始化acceptor、poller线程的数量
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed // 如果有必要的话初始化ssl
    initialiseSsl();
    // 初始化selector
    selectorPool.open(getName());
}

protected void initServerSocket() throws Exception {
    if (!getUseInheritedChannel()) {// 实例化ServerSocketChannel,并且绑定端口和地址
        serverSock = ServerSocketChannel.open();//创建socket
        socketProperties.setProperties(serverSock.socket());//将超时时间等设置到socket中
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        serverSock.socket().bind(addr,getAcceptCount());//设置最大连接数和地址
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    serverSock.configureBlocking(true); //mimic APR behavior
}
  1. 首先创建socket,然后将连接属性设置到socket中。
  2. 初始化acceptor、poller线程的数量。
  3. 初始化selector,并启动BlockPoller线程,这个poller暂时不知道是干啥的。

至此,整个初始化过程便告一段落。整个初始化过程,由parent组件控制child组件的初始化,一层层往下传递,直到最后全部初始化。

参考:https://blog.csdn.net/Dwade_mia/article/details/79051444

​ 《Tomcat架构解析》刘光瑞