Tomcat顶层组件的启动流程

一、启动流程

在我们初学Tomcat的时候,会去官网下载,解压之后运行bin目录下的startup.sh/startup.bat。

1、Tomcat是用Java编写的一个程序,所以是需要在JVM上运行,通过启动脚本来运行Tomcat的启动类Bootstrap。

2、Bootstrap是一个启动类,主要任务就是初始化Tomcat的类加载器,并且创建Catalina。

3、Catalina是一个启动类,它回去解析server.xml,创建对应的组件,并调用Server的start方法。

4、Server组件负责管理Service组件,调用Service的start方法。

5、Service组件的职责就是管理连接器和顶层容器Engine,所以它会调用Engine和连接器的start方法。

由此可见,Tomcat的顶层们各有各自负责的事情,它们并不处理具体的请求,它们做的事"管理"。

二、Catalina的start方法

Catalina负责创建Server,解析server.xml中的<server></server>创建Server,并将Server里面的组件都创建出来,紧接着调用Server组件的init方法和start方案,这样整个Tomcat就启动起来了。Tomcat还会监听停止操作,例如使用者主动停止,异常停止等,当发生这些情况是,需要优雅停止并且清理资源,此时JVM钩子就可以派上用场了,注册一个JVM关闭钩子,可以在JVM停止的时候进行一些处理,例如资源的回收。像我们在使用线程池的时候也可以注册一个关闭钩子,优雅关闭线程池回收资源。

 

java

复制代码

public void start() { // 如果Server实列为空,那么就解析server.xml创建出来 if (this.getServer() == null) { this.load(); } // 如果创建失败,报错退出 if (this.getServer() == null) { log.fatal(sm.getString("catalina.noServer")); } else { long t1 = System.nanoTime(); try { // 启动Server this.getServer().start(); } catch (LifecycleException var6) { log.fatal(sm.getString("catalina.serverStartFail"), var6); try { this.getServer().destroy(); } catch (LifecycleException var5) { log.debug("destroy() failed for failed Server ", var5); } return; } if (log.isInfoEnabled()) { log.info(sm.getString("catalina.startup", new Object[]{Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))})); } // 创建注册钩子 if (this.useShutdownHook) { if (this.shutdownHook == null) { this.shutdownHook = new Catalina.CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(this.shutdownHook); LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager)logManager).setUseShutdownHook(false); } } // 监听停止请求 if (this.await) { this.await(); this.stop(); } } }

三、Server组件

Server组件的实现类是StandardServer。Server继承了LifeCycleBase,因此生命周期被统一管理,并且它的子组件是Service,因此需要管理Service的生命周期,也就是在Server组件需要调用Service的启动和停止的方法。因此在Server内部维护者若干个Service组件。

 

java

复制代码

public void addService(Service service) { service.setServer(this); synchronized(this.servicesLock) { // 创建一个新的长度+1的数组 Service[] results = new Service[this.services.length + 1]; // 把旧数组复制到新的数组上 System.arraycopy(this.services, 0, results, 0, this.services.length); results[this.services.length] = service; this.services = results; // 启动service if (this.getState().isAvailable()) { try { service.start(); } catch (LifecycleException var6) { } } // 出发监听事件 this.support.firePropertyChange("service", (Object)null, service); } }

可见当添加一个新的Service,会创建一个新的数组长度是原数组的长度+1,并把原数组的内容复制过去,然后再把新的Service添加进新数组,这样做的目的是为了节省内容空间。

Server组件还有一个重要的任务就是启动一个Socket来监听停止端口,所以我们可以通过Tomcat提供的停止脚本来停止Tomcat。在上面的Catalina的start方法最后调用了await方法,Catalina的await方法只是再调用Server的await方法:

 

java

复制代码

// Catalina.java public void await() { this.getServer().await(); }

在 await 方法里会创建一个 Socket 监听 8005 端口(server.xml),并在一个死循环里接收 Socket 上的连接请求,如果有新的连接到来就建立连接,然后从 Socket 中读取数据;如果读到的数据是停止命令“SHUTDOWN”,就退出循环,进入 stop 流程。

四、Service组件

Tomcat中Service是个接口,具体的实现类是StandardService。

 

java

复制代码

public class StandardService extends LifecycleMBeanBase implements Service { private static final Log log = LogFactory.getLog(StandardService.class); // Service的name private String name = null; private static final StringManager sm = StringManager.getManager("org.apache.catalina.core"); // Server实例 private Server server = null; protected final PropertyChangeSupport support = new PropertyChangeSupport(this); // 连接器数组 protected Connector[] connectors = new Connector[0]; private final Object connectorsLock = new Object(); protected final ArrayList<Executor> executors = new ArrayList(); // 对应的Engine容器 private Engine engine = null; private ClassLoader parentClassLoader = null; // 映射器以及其监听器 protected final Mapper mapper = new Mapper(); protected final MapperListener mapperListener = new MapperListener(this); //.... }

StandardService 继承了 LifecycleBase 抽象类,此外 StandardService 中还有一些我们熟悉的组件,比如 Server、Connector、Engine 和 Mapper。

因为Tomcat支持热部署,当Web应用的部署发生变化时,Mapper中的映射信息也要跟着变化,MapperListener就是一个监听器,监听容器的变化,并把信息更新到Mapper中,这是典型的观察者模式。

又到我们常说的Tomcat的组件时上层控制下层的启动,那么来看一下Service的启动方法:

 

java

复制代码

protected void startInternal() throws LifecycleException { //1. 触发启动监听器 setState(LifecycleState.STARTING); //2. 先启动 Engine,Engine 会启动它子容器 if (engine != null) { synchronized (engine) { engine.start(); } } //3. 再启动 Mapper 监听器 mapperListener.start(); //4. 最后启动连接器,连接器会启动它子组件,比如 Endpoint synchronized (connectorsLock) { for (Connector connector: connectors) { if (connector.getState() != LifecycleState.FAILED) { connector.start(); } } } }

Service 先启动了 Engine 组件,再启动 Mapper 监听器,最后才是启动连接器。这很好理解,因为内层组件启动好了才能对外提供服务,才能启动外层的连接器组件。而 Mapper 也依赖容器组件,容器组件启动好了才能监听它们的变化,因此 Mapper 和 MapperListener 在容器组件之后启动。组件停止的顺序跟启动顺序正好相反的,也是基于它们的依赖关系。

五、Engine组件

Engine 本质是一个容器,在Tomcat中Engine是个接口实现类是StandardEngine,因此它继承了 ContainerBase 基类,并且实现了 Engine 接口。

Engine 的子容器是 Host,所以它持有了一个 Host 容器的数组,这些功能都被抽象到了 ContainerBase 中,在ContainerBase中有个一个Map来存储

 

java

复制代码

protected final HashMap<String, Container> children = new HashMap<>();

ContainerBase 用 HashMap 保存了它的子容器,并且 ContainerBase 还实现了子容器的“增删改查”,甚至连子组件的启动和停止都提供了默认实现,

 

java

复制代码

// StandardEngine.java protected synchronized void startInternal() throws LifecycleException { if (log.isInfoEnabled()) { log.info(sm.getString("standardEngine.start", new Object[]{ServerInfo.getServerInfo()})); } // 调用父类(ContainerBase)的启动方法 super.startInternal(); }

在ContainerBase中会用专门的线程池来启动子容器。如下:

 

java

复制代码

for(int var7 = 0; var7 < var6; ++var7) { Container child = var5[var7]; results.add(this.startStopExecutor.submit(new ContainerBase.StartChild(child))); }

Engine 容器对请求的“处理”,其实就是把请求转发给某一个 Host 子容器来处理,具体是通过 Valve 来实现的。每一个容器组件都有一个 Pipeline,而 Pipeline 中有一个基础阀(Basic Valve),而 Engine 容器的基础阀定义如下:

 

java

复制代码

final class StandardEngineValve extends ValveBase { public StandardEngineValve() { super(true); } public final void invoke(Request request, Response response) throws IOException, ServletException { Host host = request.getHost(); if (host != null) { if (request.isAsyncSupported()) { request.setAsyncSupported(host.getPipeline().isAsyncSupported()); } host.getPipeline().getFirst().invoke(request, response); } } }

这个基础阀实现非常简单,就是把请求转发到 Host 容器。从代码中可以看到,处理请求的 Host 容器对象是从请求中拿到的,请求对象中怎么会有 Host 容器呢?这是因为请求到达 Engine 容器中之前,Mapper 组件已经对请求进行了路由处理,Mapper 组件通过请求的 URL 定位了相应的容器,并且把容器对象保存到了请求对象中。

六、总结

Tomcat的启动过程是启动类和顶层组件来完成,他们承担着管理的角色,负责将子组件创建出来,并拼装在一起,同时掌控着子组件的“生老病死”。

在我们设计组件的时候应该思考用那些合适的数据接口来存储,像用数组来存储Service组件,每次都新建数组,复制,添加新Service组件。数组的结构简单,占用的内存小。使用HashMap来保存子容器,虽然Map占用的内存多一点,但是一次性哈希就能快速找到子容器,这就是空间换时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值