Tomcat学习2:一键启动以及源码阅读
2021/11/1 12:40:14
本文主要是介绍Tomcat学习2:一键启动以及源码阅读,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一次请求在Tomcat中经过的组件
Tomcat处理一个HTTP请求,在各组件中的流转过程如下图红色箭头:
一个系统通过如此多的组件组装起来完成一次完成的服务,那如何管理组件的创建、初始化和调用关系?
Lifecycle
系统设计要找到不变的点和变化的点,这里不变的地方就是每个组件都要创建、初始化、启动、销毁等,这些状态和状态之间的转化是不变的。变化的是每个组件初始化方法不一样。
Tomcat把不变的地方抽象出一个生命周期Lifecycle接口,定义了一些不变的方法:init,start,stop,destory,各组件去实现具体的逻辑。在父组件的init方法里面,会调用子组件的Init方法,只要调用最顶层的server组件的Init和start方法,整个tomcat组件都会被启动起来。(组合模式-Lifecycle接口)
生命周期会对应到一个个状态LiftcycleState,状态可作为事件,是可以被监听的。一个组件的状态变化会触发子组件的变化,比如Host容器的启动事件会触发Web应用的扫描和加载(反射)(观察者模式),最终会在Host容器中创建出Context容器,Lifecycle接口里有两个方法:添加监听器和删除监听器。
LifecycleBase抽象类实现了Lifecycle接口,并把一些公共的逻辑放到基类中,如生命状态的转变和、生命周期事件的触发等,子类就负责自己的初始化、启动和停止等方法(模板模式), 子类的实现会加上Internal后缀,比如InitInternal,startInternal等。
如何启动Tomcat
1.Tomcat本质上是一个Java程序,因此startup.sh
脚本会启动一个JVM来运行Tomcat的启动类Bootstrap。
2.Bootstrap的主要任务是初始化Tomcat的类加载器,并且创建Catalina。Tomcat为什么需要自己的类加载器?
3.Catalina是一个启动类,它通过解析server.xml
、创建相应的组件,并调用Server的start方法和init方法。
Catalina作为管理者,还通过”钩子“处理各种异常,如tomcat关闭时,如何释放资源以及内存数据刷到磁盘等
4.Server组件的职责就是管理Service组件,它会负责调用Service的start方法。
5.Service组件的职责就是管理连接器和顶层容器Engine,因此它会调用连接器和Engine的start方法。
1:Bootstrap类
Tomcat是通过startup.sh调用了Bootstra的main方法启动
1.1:main方法:
1 public static void main(String args[]) { 2 3 synchronized (daemonLock) { 4 if (daemon == null) { 5 // Don't set daemon until init() has completed 6 Bootstrap bootstrap = new Bootstrap(); 7 try { // 1:初始化 8 bootstrap.init(); 9 } catch (Throwable t) { 13 } 14 daemon = bootstrap; 15 } 21 } 22 23 try { 24 String command = "start"; 25 if (args.length > 0) { 26 command = args[args.length - 1]; 27 } 28 // 2:对同的命令做不同的动作 29 if (command.equals("startd")) { 30 args[args.length - 1] = "start"; 31 daemon.load(args); 32 daemon.start(); 33 } else if (command.equals("stopd")) { 34 args[args.length - 1] = "stop"; 35 daemon.stop(); 36 } 63 } 64 }
主要做一些初始化init和完成一些动作指令load,start
1.2:init方法:
1 public void init() throws Exception { 2 //1:类加载器 3 initClassLoaders(); 4 5 Thread.currentThread().setContextClassLoader(catalinaLoader); 6 7 SecurityClassLoad.securityClassLoad(catalinaLoader); 8 9 // Load our startup class and call its process() method 10 if (log.isDebugEnabled()) 11 log.debug("Loading startup class"); 12 Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); 13 Object startupInstance = startupClass.getConstructor().newInstance(); 14 15 // Set the shared extensions class loader 16 if (log.isDebugEnabled()) 17 log.debug("Setting startup class properties"); 18 String methodName = "setParentClassLoader"; 19 Class<?> paramTypes[] = new Class[1]; 20 paramTypes[0] = Class.forName("java.lang.ClassLoader"); 21 Object paramValues[] = new Object[1]; 22 paramValues[0] = sharedLoader; //2:实例化catalina 23 Method method = 24 startupInstance.getClass().getMethod(methodName, paramTypes); 25 method.invoke(startupInstance, paramValues); 26 27 catalinaDaemon = startupInstance; 28 }
1:初始化类加载器,包括了common类加载器,shared类加载器,catalina类加载器(Tomcat类加载器和Jvm类加载器?),其中common类加载器作为父类加载器
2:实例化Catalina对象,并传入catalinaClassLoader作为parentClassLoader加载子组件,实现catalinaClassLoader和shareClassLoader隔离
1.3:load方法:
1 private void load(String[] arguments) throws Exception { 2 3 // Call the load() method 4 String methodName = "load"; 5 Method method = 6 catalinaDaemon.getClass().getMethod(methodName, paramTypes); 7 method.invoke(catalinaDaemon, param); 8 }
通过反射调用Catalina类的load方法
1.4:start方法:
start方法也通过反射调用了Catalina类的start方法
2:Catalina
Catalina作为启动类,它通过解析server.xml,
创建相应的组件,并调用Server的start方法和init方法完成Tomcat的启动过程
2.1:load方法:
1 public void load() { // Set configuration source 3 ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile())); 4 File file = configFile(); 5 6 // Create and execute our Digester // 1:创建各组件:server,service,ThreadPool,Listener等 7 Digester digester = createStartDigester(); 8 // 2:解析server.xml 9 try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) { 10 InputStream inputStream = resource.getInputStream(); 11 InputSource inputSource = new InputSource(resource.getURI().toURL().toString()); 12 inputSource.setByteStream(inputStream); 13 digester.push(this); 14 digester.parse(inputSource); 15 } catch (Exception e) { 16 log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e); 17 if (file.exists() && !file.canRead()) { 18 log.warn(sm.getString("catalina.incorrectPermissions")); 19 } 20 return; 21 } 22 23 getServer().setCatalina(this); 24 getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); 25 getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); 26 27 // Stream redirection 28 initStreams(); 29 30 // Start the new server 31 try { // 3:调用server的init方法初始化Tomcat各组件 32 getServer().init(); 33 } catch (LifecycleException e) { 34 35 } 36 37 }
核心调用了server的init方法完成完成了Server及以下组件的初始化
2.2:start方法:
getServer().start();
调用了server的start方法启动Tomcat的所有组件
3:Server类
server组件的init和start方法,最终调用的是Lifecycle的init和start方法。Lifecycle的实现类LifecycleBase的init方法(模板模式)
3.1:LifecycleBase的init方法:
1 public final synchronized void init() throws LifecycleException { 2 if (!state.equals(LifecycleState.NEW)) { 3 invalidTransition(Lifecycle.BEFORE_INIT_EVENT); 4 } 5 6 try { //1:状态变更事件 7 setStateInternal(LifecycleState.INITIALIZING, null, false); // 2:server的初始化方法 8 initInternal(); 9 setStateInternal(LifecycleState.INITIALIZED, null, false); 10 } catch (Throwable t) { 11 handleSubClassException(t, "lifecycleBase.initFail", toString()); 12 } 13 }
1:状态变更事件(观察者模式)
2:调用StandardServer的initInternal方法,初始化各组件
先看第二步,调用了StandardServer的initInternal方法对各组件进行初始化,后面子容器实现类都是Standard- 开头;状态变更事件稍后在看(如何注册事件,怎么通知?)
整个初始化链路如下图:
3.2:StandardServer的initInternal方法:
protected void initInternal() throws LifecycleException { super.initInternal(); // Initialize utility executor reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads)); register(utilityExecutor, "type=UtilityExecutor"); onameStringCache = register(new StringCache(), "type=StringCache"); // Register the MBeanFactory MBeanFactory factory = new MBeanFactory(); factory.setContainer(this); onameMBeanFactory = register(factory, "type=MBeanFactory"); // Register the naming resources globalNamingResources.init(); // Populate the extension validator with JARs from common and shared // class loaders if (getCatalina() != null) { .... } // Initialize our defined Services for (int i = 0; i < services.length; i++) { services[i].init(); } }
server的初始化工作,除了对自身做一些基础的初始化,主要是对service组件进行初始化(1个server可对应多个service)
3.3:StandardServer的startInternal方法:
startInternal方法主要是调用service的startInternal方法,子组件会做一些特殊的动作
4:Service类
4.1:initInternal方法:
protected void initInternal() throws LifecycleException { super.initInternal(); //1:engine初始化 if (engine != null) { engine.init(); } // 2:线程池初始化 for (Executor executor : findExecutors()) { if (executor instanceof JmxEnabled) { ((JmxEnabled) executor).setDomain(getDomain()); } executor.init(); } // 3:Initialize mapper listener mapperListener.init(); // 4:Initialize our defined Connectors synchronized (connectorsLock) { for (Connector connector : connectors) { connector.init(); } } }
service的初始化比较热闹,主要完成四件事,刚好对应到tomcat架构设计上service的功能
1)子组件的初始化
2)公共线程池
3)mapper listener请求映射
4)连接器
4.2:initInternal方法:
start方法和init方法也比较类似。
至此,顶层的公共逻辑已经完成,下面分为连接器、处理器、映射Mapper以及公共线程池独立的初始化和启动流程。
从上面的service的初始化开始进容器组件的初始化最顶层的Engine(Container作为容器组件的公共接口提供服务)。
5:Engine类
5.1:initInternal方法
protected void initInternal() throws LifecycleException { getRealm(); super.initInternal(); }
从代码层面看,init方法调用了ContainerBase的init方法,但是好像并没有做太多的事情,那子容器的初始化在哪完成的呢?,继续看
5.2:startInternal方法
protected synchronized void startInternal() throws LifecycleException { // Standard container startup super.startInternal(); }
调用了ContainerBase的startInternal方法,这个方法里面做了很多很多的事情
protected synchronized void startInternal() throws LifecycleException { // 1:Start our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } MultiThrowable multiThrowable = null; 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) { ((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); } }
去掉前面部分代码,核心在找到子组件并用线程池进行初始化(前面的init方法没有进行初始化,这里使用了线程池)。
private static class StartChild implements Callable<Void> { private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { child.start(); return null; }
这个线程的start方法调用了LifecycleBase的start方法,最终初始化了Host类的init方法(Container默认实现)和start方法
6:Host类
6.1:startInternal方法
protected synchronized void startInternal() throws LifecycleException { // Set error report valve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { try { boolean found = false; Valve[] valves = getPipeline().getValves(); for (Valve valve : valves) { if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } if(!found) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { } super.startInternal(); }
对Host的Pipeline添加了value,并调用了父类ContainerBase的startInternal方法,持续进行子组件的初始化工作。
这篇关于Tomcat学习2:一键启动以及源码阅读的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求
- 2024-10-01使用 golang 将ETH账户的资产平均分散到其他账户
- 2024-10-01JWT用户校验课程:从入门到实践
- 2024-10-01Server Component课程入门指南
- 2024-09-30Dnd-Kit学习:新手快速入门指南