- 初始化协议处理器
// AbstractProtocol.java (Http11NioProtocol.java) @Override public void init() throws Exception { // 初始化终点(端点) String endpointName = getName(); endpoint.setName(endpointName.substring(1, endpointName.length()-1)); endpoint.setDomain(domain); endpoint.init(); } // 终(端)点初始化 --这一步,连接器就要让终点去绑定端口了 // AbstractEndpoint.java public final void init() throws Exception { if (bindOnInit) { bindWithCleanup(); bindState = BindState.BOUND_ON_INIT; } } // NioEndpoint.java protected void initServerSocket() throws Exception { if (!getUseInheritedChannel()) { serverSock = ServerSocketChannel.open(); socketProperties.setProperties(serverSock.socket()); InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset()); // 最终这一步去绑定了8080端口 serverSock.socket().bind(addr,getAcceptCount()); } else { } serverSock.configureBlocking(true); //mimic APR behavior }
5. 回归Bootstrap
接着单步执行会发现它一层一层往外走,最终回到了Bootstrap中,进入下一步启动-start()。
启动-start
启动过程通过反射调用了Catalina的start方法。
// Bootstrap.java public void start() throws Exception { Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null); method.invoke(catalinaDaemon, (Object [])null); } // Catalina.java public void start() { // 获取不到Server则调用之前的加载,到这里他已经加载过了,不会进这个分支 if (getServer() == null) { load(); } // 正式调用Server的启动生命周期 try { getServer().start(); } catch (LifecycleException e) { } }
启动步骤按顺序分以下几个:
1. Server的启动
可以发现它也是交由超类的final start进行实现,可想而知,所有容器的启动,也都会进这个start方法。
并且与load()方法相似startInternal都交由子类具体实现其内部逻辑
// LifecycleBase.java @Override public final synchronized void start() throws LifecycleException { // ... try { setStateInternal(LifecycleState.STARTING_PREP, null, false); // startInternal交由子类具体实现内部逻辑 startInternal(); } catch (Throwable t) { } }
具体进入startInternal看实现,可以看到实现在StandardServer中
// org.apache.catalina.core.StandardServer#startInternal @Override protected void startInternal() throws LifecycleException { // 触发生命周期的事件 -- START fireLifecycleEvent(CONFIGURE_START_EVENT, null); setState(LifecycleState.STARTING); globalNamingResources.start(); // 对定义的Service进行start synchronized (servicesLock) { for (Service service : services) { service.start(); } } }
2. Service启动
同理,service.start();进入了超类的start()方法,且最终调用自身StandardService的startInternal方法进行实现(下同之处自动省略)。
// org.apache.catalina.core.StandardService#startInternal @Override protected void startInternal() throws LifecycleException { // 第一步启动引擎 if (engine != null) { synchronized (engine) { engine.start(); } } // 第二步启动执行器 synchronized (executors) { for (Executor executor: executors) { executor.start(); } } // 第三步启动监听器 mapperListener.start(); // 第四步启动已经成功加载的连接器 // 那么为什么会有失败的呢,最直接的就是端口被占用,无法绑定bind() synchronized (connectorsLock) { for (Connector connector: connectors) { // If it has already failed, don't try and start it if (connector.getState() != LifecycleState.FAILED) { connector.start(); } } } }
3. 启动引擎、执行器、监听器、连接器
- 启动引擎
// org.apache.catalina.core.StandardEngine#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // Standard container startup // 调用了超类[ContainerBase]中的startInternal super.startInternal(); } // org.apache.catalina.core.ContainerBase#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // debug 发现进来为null Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { logger.info(String.format("cluster[%s] 开始执行生命周期之start()", cluster.getClusterName())); ((Lifecycle) cluster).start(); } // 会获取到Load方法加载进来的`LockOutRealm` Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { logger.info(String.format("realm-属于container[%s] 开始执行生命周期之start()", realm.getContainer().getName())); // 最终执行的为org.apache.catalina.realm.CombinedRealm#startInternal ((Lifecycle) realm).start(); } // Start our child containers, if any // 最终找到了Engine下定义的Host [StandardHost] Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { // 使用了load()时初始化的线程池 logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化"); results.add(startStopExecutor.submit(new StartChild(child))); } MultiThrowable multiThrowable = null; // 使用Future#get进行阻塞,获取Host的初始化结果 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) { logger.debug(String.format("开始管道pipeline[%s]的生命周期之start()", pipeline.getContainer().getName())); ((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); } }
- 启动Cluster
获得对象为null,无需启动 - 启动Realm
有对象,跳过 - 启动子容器Host
根据上述代码可知,它会被提交到线程池,进行多线程启动,将会调用到线程的call方法
// org.apache.catalina.core.ContainerBase.StartChild // 超类中的静态内部类 private static class StartChild implements Callable<Void> { @Override public Void call() throws LifecycleException { // 调用子容器的start方法,在这步中,子容器为`StandardHost` child.start(); //DEBUG: child: "StandardEngine[Catalina].StandardHost[localhost]" return null; } }
- 接着通过StandardHost的start生命周期又进入startInternal方法
// org.apache.catalina.core.StandardHost#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // 检查管道中有没有报错 // 真正的启动交由超类实现 super.startInternal(); } // 接着又交由到ContainerBase这个熟悉的超类进行实现,在启动-start章节的3.1中已经贴出过,这里不再详细展示,主要就是多线程去启动器子容器 -- 套娃模式的好处 // org.apache.catalina.core.ContainerBase#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // Start our child containers, if any Container children[] = findChildren(); // 由于在server.xml中没有在<Host>节点下再定义<Context>容器,所以这次children数组是空数组 List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化"); results.add(startStopExecutor.submit(new StartChild(child))); } }
- 启动管道pipeline
跳过
- 启动执行器
在执行完多线程对Host的启动后,一路点击Step Out跳回到StandardService中的startInternal方法。
若已经分不清层次可以在StandardService类中搜索下面这段代码并打上断点。
// org.apache.catalina.core.StandardService#startInternal synchronized (executors) { for (Executor executor: executors) { executor.start(); } }
- 不过executors数组长度为0,在这儿就跳过了。
- 启动监听器
// org.apache.catalina.mapper.MapperListener#startInternal @Override public void startInternal() throws LifecycleException { // 获取引擎 Engine engine = service.getContainer(); // 添加监听器进引擎 addListeners(engine); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { // Registering the host will register the context and wrappers registerHost(host); } } } // org.apache.catalina.mapper.MapperListener#addListeners private void addListeners(Container container) { // 把监听器[回调接口]注册进容器、生命周期、并递归对子容器进行注册 container.addContainerListener(this); container.addLifecycleListener(this); for (Container child : container.findChildren()) { // 通过一共5层递归,观察到层次如下 // StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default] addListeners(child); } }
- 可以观察到套娃一样对子容器进行递归添加监听器层次为:StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default]。
- 启动连接器
可以在org.apache.catalina.core.StandardService#startInternal中搜索for (Connector connector: connectors)打上断点,快速从上一步的递归中出来,再进入connector.start();。
ps: 这时候在windows平台上遇到了端口被占用,如何杀死占用了8080端口的进程呢?
<!-- 先找出占用8080端口的进程 --> netstat -ano| findstr "8080" <!-- 杀死占用8080端口的进程[此处刚好为7400] --> taskkill /f /pid 7400
// org.apache.catalina.connector.Connector#startInternal @Override protected void startInternal() throws LifecycleException { try { // 启动协议处理器 protocolHandler.start(); } catch (Exception e) { } } // org.apache.coyote.AbstractProtocol#start @Override public void start() throws Exception { // 启动端点 endpoint.start(); } // org.apache.tomcat.util.net.AbstractEndpoint#start public final void start() throws Exception { startInternal(); } // org.apache.tomcat.util.net.NioEndpoint#startInternal @Override public void startInternal() throws Exception { if (!running) { // 读取配置 // Create worker collection if (getExecutor() == null) { // 初始化线程池,任务队列 createExecutor(); } // 创建限制锁 initializeConnectionLatch(); // 创建NIO的poller线程 poller = new Poller(); Thread pollerThread = new Thread(poller, getName() + "-ClientPoller"); pollerThread.setPriority(threadPriority); pollerThread.setDaemon(true); pollerThread.start(); // 真正开始接收NIO端口的请求 startAcceptorThread(); } } // org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThread protected void startAcceptorThread() { acceptor = new Acceptor<>(this); String threadName = getName() + "-Acceptor"; // http-nio-8080-Acceptor acceptor.setThreadName(threadName); Thread t = new Thread(acceptor, threadName); t.setPriority(getAcceptorThreadPriority()); t.setDaemon(getDaemon()); t.start(); // Thread[http-nio-8080-Acceptor,5,main] }
- 回归BootStrap
一直Step Out回到BootStrap中,完成启动。
总结
- 加载(load)过程主要完成 配置读取-实例化容器等组件、创建线程池、占用监控端口。
- 启动(start)过程主要完成 顺序/多线程启动各层容器、开始接收端口的请求数据。
收获
- 组件模块化,在超类中定义基本操作,在子类中定义具体的一部分的实现。架构脉络清晰,层次分明,节省大量代码量。
- 加载与启动整体解耦分离,就不必在启动过程中在意所需组件是否加载。有点像Spring的懒加载,读取了Bean的定义,但不必去初始化,用到的时候才去启动(描述的不贴切)。