Tomcat源码分析(3)—Server组件
在上一篇文章中笔者通过Bootstrap类分析了tomcat的启动流程,包括Server的初始化、启动等,而Server类中的主要方法是根据解析server.xml配置文件初始化tomcat运行所需要的组件,例如Server实例、Service、Engine容器、Connector连接器等等,接着调用组件的init()及start()方法完成tomcat服务器的启动。
什么是Server
每一个Server实例表示一个tomcat容器,而一个tomcat容器也只能有一个server实例。在类继承关系中,Server是一个接口,它的实现类是StandardServer,同时StandarServer继承了LifecycleMBeanBase抽象类,间接地实现了Lifecycle接口定义的生命周期方法,包括init()、start()、stop()等,这相当于把StandardServer的生命周期保持与其他组件启动和关闭的一致性。
在Catalina实例执行load()方法时,通过解析器Digester解析server.xml并构建生成server对象实例。
1/**
2* Start a new server instance.
3*/
4public void load() {
5 //省略部分代码
6 Digester digester = createStartDigester();
7 digester.parse(inputSource);
8 //省略部分代码
9 initStreams();
10 getServer().init();
11}
如果是内嵌的方式可以直接通过new StandardServer()的方式直接实例化,可参考下面代码:
1public Server getServer() {
2 Server server = new StandardServer();
3 initBaseDir();
4 server.setPort( -1 );
5
6 Service service = new StandardService();
7 service.setName("Tomcat");
8 server.addService(service);
9 return server;
10}
初始化
在解析完server.xml配置文件后,调用server实例的init()方法启动自身及嵌套的组件,同时更新其生命周期状态为初始化中,当初始化完成后更新为初始化完成状态。
StandardServer类通过模板设计方式继承LifecycleMBeanBase抽象类并实现了Server接口,在初始化时调用LifecycleBase的方法更改生命周期状态、触发生命周期事件等。
在tomcat组件中,每个组件会通过JMX方式注册到MBeanServer中,因此在讲解StandardServer初始化逻辑之前,需先知道Java JMX是什么。
JMX的全称为Java Management Extensions. 顾名思义,是管理Java的一种扩展。这种机制可以方便的管理正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等,可参考:https://www.cnblogs.com/trust-freedom/p/6842332.html。
在JMX涉及到几个概念:
- MBean:MBean可理解为是一种资源对象。
- MBeanServer:MBean Server是一个集中的管理MBean对象实例的地方。通过MBeanServer,你可以查询、管理、操作Agent应用发布的特定的MBean。
- ObjectName:MBean Server通过object name来对mbean进行区分,同一个MBean类可能有多个实例对象,但是每个实例对象都有一个唯一的objectname。
在StandardServer初始化时,StandardServer实例也不例外,它会作为MBean对象注册到MBeanServer中,在StandardServer实例的initInternal()方法体中,主要执行以下逻辑:
- 初始化MBeanServer,并将standardServer实例注册到MBeanServer管理。
- 注册字符串缓存实例到MBeanServer管理。
- 实例化MBean工厂并注册到MBeanServer管理。
- 初始化命名、JNDI上下文定义的命名资源。
- 校验给定的jar包文件是否包含MANIFEST,若包含则添加到系统资源中。
- 初始化service组件。
1@Override
2protected void initInternal() throws LifecycleException {
3
4 //初始化MBeanServer,并将当前组件注册到MBean Server
5 super.initInternal();
6
7 //注册实现字节、字符缓冲的字符串缓存实例到MBean Server
8 onameStringCache = register(new StringCache(), "type=StringCache");
9
10 // 实例化MBean工厂,并注册到MBean Server
11 MBeanFactory factory = new MBeanFactory();
12 factory.setContainer(this);
13 onameMBeanFactory = register(factory, "type=MBeanFactory");
14
15 // 初始化命名、JNDI上下文定义的命名资源
16 globalNamingResources.init();
17
18 //校验给定的jar包文件是否包含MANIFEST,若包含则添加到系统资源中
19 if (getCatalina() != null) {
20 ClassLoader cl = getCatalina().getParentClassLoader();
21 while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
22 if (cl instanceof URLClassLoader) {
23 URL[] urls = ((URLClassLoader) cl).getURLs();
24 for (URL url : urls) {
25 if (url.getProtocol().equals("file")) {
26 try {
27 File f = new File (url.toURI());
28 if (f.isFile() &&
29 f.getName().endsWith(".jar")) {
30 ExtensionValidator.addSystemResource(f);
31 }
32 } catch (URISyntaxException e) {
33 // Ignore
34 } catch (IOException e) {
35 // Ignore
36 }
37 }
38 }
39 }
40 cl = cl.getParent();
41 }
42 }
43 //初始化service组件
44 for (Service service : services) {
45 service.init();
46 }
47}
全局命名资源即globalNamingResources也继承了LifecycleMBeanBase抽象类,因此在初始化命名、JNDI上下文资源时,也会将自身作为MBean注册到MBeanServer管理,同时将web应用的资源、应用环境类目、web应用的资源链接ResourceLink统一注册到MBeanServer管理。
除了StandardServer之外,在其他组件初始化时也会调用LifecycleMBeanBase实例的initInternal()方法构建MBean并将自身注册到MBeanServer管理,最后可通过MBeanServer查询、管理、操作发布的MBean。
启动
当StandardServer初始化操作准备完成时,下一步就是启动server及其嵌套的组件,同样在StandardServer启动前变更生命周期状态为启动中,完成后更新为启动成功或失败,下面是StandardServer启动的时序流程图:
从流程图中看出,Catalina调用start()方法实际上是委托给LifecycleBase抽象类,而LifecycleBase再委托给StandardServer自身,通过模板方法设计模式把具体的实现交由给子类,在子类中完成生命周期状态更新、命名资源组件的启动等。
1 @Override
2 protected void startInternal() throws LifecycleException {
3
4 fireLifecycleEvent(CONFIGURE_START_EVENT, null);
5 setState(LifecycleState.STARTING);
6
7 globalNamingResources.start();
8
9 // Start our defined Services
10 synchronized (servicesLock) {
11 for (Service service : services) {
12 service.start();
13 }
14 }
15 }
观察上面的代码,得知在调用NamingResource组件的start()方法时,完成了命名、JNDI上下文状态的变更、监听器触发等逻辑,接着遍历、启动其关联的Service组件(Service组件将在下一篇文章中阐述)。
最后总结
本文讲解了Server组件的含义及它在tomcat中扮演的角色、作用,每一个Server实例即是一个tomcat容器,而一个tomcat容器也只能有一个server实例,读者通过阅读本文能掌握tomcat在启动时server实例初始化、启动的流程与细节,更好学习tomcat的其他组件。