大家好,欢迎来到IT知识分享网。
Tomcat组件梳理–Catalina
1.定义和功能
Catalina
是Tomcat的核心组件,是Servlet容器,Catalina包含了所有的容器组件,其他模块均为Catalina提供支撑。通过Coyote模块提供连接通信,Jasper模块提供JSP引擎,Naming提供JNDI服务,Juli提供日志服务。结构如下:
主要的功能包括接收请求,处理请求,返回结果。但是这些具体的实现是在catalina里面的子容器里面,我们在对应的文章里面讲解,此处聚焦在Catalina的源代码提供的功能上。
处理上面这些,Catalina还提供启动入口,关闭入口等。
2.属性
//org.apache.catalina.startup.Catalina / * 用于await的flag */ protected boolean await = false; / * Server配置的文件路 */ protected String configFile = "conf/server.xml"; / * The shared extensions class loader for this server. * 此server的shared 类加载器 */ protected ClassLoader parentClassLoader = Catalina.class.getClassLoader(); / * Server组件 */ protected Server server = null; / * 使用shutdown钩子的flag */ protected boolean useShutdownHook = true; / * Shutdown钩子实例 */ protected Thread shutdownHook = null; / * 默认需要开启Naming */ protected boolean useNaming = true; / * 预防重复加载的标记字段 */ protected boolean loaded = false;
有几个主要的属性。
- Catalina的子组件Server,通过digster工具解析server.xml文件构造该对象。
- 用户shutdown时的钩子,是否使用以及调用的线程
- 是否需要启动JNDI的标识
- Server.xml配置文件的地址
- stop用的await标记
- 父类加载器。
3.操作
Catalina
的操作有比较明显的区分,因为主要是处理来自shell的不同命令,所以,根据shell的传入的命令行,我们可以看到Catalina主要处理来自shell的start和stop命令。下面来解析start命令和stop命令的背后,以及Tomcat中提供的一个对xml解析很有用的库Digester。
再看一下Bootstrap类中的main方法中对shell命令的处理,可以看到start时主要调用load(args)和start()方法,stop时主要调用stopServer(args)方法。
//3.判断shell传入的值,执行对应的动作 if (command.equals("startd")) { //执行start方法的内容,主要为执行Catalina的load()和start()方法 args[args.length - 1] = "start"; daemon.load(args); daemon.start(); } else if (command.equals("stopd")) { args[args.length - 1] = "stop"; daemon.stop(); } else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); if (null == daemon.getServer()) { System.exit(1); } } else if (command.equals("stop")) { daemon.stopServer(args); } else if (command.equals("configtest")) { daemon.load(args); if (null == daemon.getServer()) { System.exit(1); } System.exit(0); } else { log.warn("Bootstrap: command \"" + command + "\" does not exist."); }
3.1.执行shell的start命令
3.1.1.调用load(args)方法
现在我们知道Shell的start命令交给Catalina处理时,实际上调用了load(args)和start()方法。我们先看load(args)方法
//org.apache.catalina.startup.Catalina#load(java.lang.String[]) /* * 使用参数进行load */ public void load(String args[]) { try { //1.根据传入的参数设置Catalina的一些属性的值 if (arguments(args)) { //2.调用无参的load()方法 load(); } } catch (Exception e) { e.printStackTrace(System.out); } }
可以看到这里有两个业务逻辑:
- 1.根据传入的参数设置Catalina的一些属性,这些属性主要就是Naming等一些值。
- 2.调用午餐的load()方法
再看load()方法,去掉里面的异常和环境检查,可以看到主要逻辑如下:
//org.apache.catalina.startup.Catalina#load() / * 准备好环境,解析好server.xml文件生成好对象。server对象也准备好,然后调用server的init方法 */ public void load() { //1.检查java.io.tmpdir是有有效 initDirs(); //2.设置catalina.useNaming的系统参数 initNaming(); //3.用digester解析server.xml文件,把配置文件中的配置解析成java对象 //3.1.准备好用来解析server.xml文件需要用的digester。 Digester digester = createStartDigester(); //3.2.server.xml文件作为一个输入流传入 File file = configFile(); InputStream inputStream = new FileInputStream(file); //3.3.使用inputStream构造一个sax的inputSource InputSource inputSource = new InputSource(file.toURI().toURL().toString()); inputSource.setByteStream(inputStream); //3.4.把当前类压入到digester的栈顶,用来作为digester解析出来的对象的一种引用 digester.push(this); //3.5.调用digester的parse()方法进行解析。 digester.parse(inputSource); //4.为子组件Server设置一些值 getServer().setCatalina(this); getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); //执行server的init方法,start方法的准备方法 getServer().init(); }
分析一下主要逻辑
- 1.检查java.io.tmpdir是有有效,这个没啥好说的。
- 2.设置catalina.useNaming的系统参数,这个也没啥好多的,等主要流程梳理完,咱们做一个JDNI的源码解析。
- 3.用digester解析server.xml文件,把配置文件中的配置解析成java对象。该过程需要经过5个步骤才可以。
- 3.1.准备好用来解析server.xml文件需要用的digester。
- 3.2.读取server.xml文件作为一个输入流。
- 3.3.使用inputStream构造一个sax的inputSource,因为digester底层用的是sax去解析的。
- 3.4.把当前类压入到digester的栈顶,用来作为digester解析出来的对象的一种引用,digester自带一个栈的结构。
- 3.5.调用digester的parse()方法进行解析。前面几步都是在准备环境,这里才是正真的去解析了。
- 4.为子组件Server设置一些值,并调用server的init方法,start方法的准备方法。
这里面最值得说的应该就是第3步,通过Digester去解析xml文件,每遇到一个匹配的节点,都可以添加一个对应的事件。对Digester的操作都是一些普通的API操作,这里就不解释了,可以查看官方文档,或者查看博客,API挺简单的。
3.1.2.调用start()方法
start()
方法主要作用就是调用Server的start()方法,并将一个shutdown的钩子加到JVM中。
主要的逻辑如下:
- 1.执行Server的start()方法,如果执行失败,就调用Server的destroy()方法
- 2.注册一个shutdown的钩子
- 3.等待处理如何停止的问题
比较有意思的是3,如何停止一个服务,这种方法比较有意思,不过我们放在Server组件中去讲,因为实现是放在Server中的。
//org.apache.catalina.startup.Catalina#start public void start() { //1.执行Server的start()方法,如果执行失败,就调用Server的destroy()方法 try { //执行server的start方法 getServer().start(); } catch (LifecycleException e) { try { //如果start失败,就调用server的destroy方法 getServer().destroy(); } catch (LifecycleException e1) { log.debug("destroy() failed for failed Server ", e1); } return; } //2.注册一个shutdown的钩子 if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); } //3.等待处理如何停止的问题 if (await) { await(); stop(); } }
在第2步的逻辑中,new了一个类去注册,该类的主要处理逻辑如下:
protected class CatalinaShutdownHook extends Thread { @Override public void run() { try { if (getServer() != null) { //1.调用Catalina的stop()方法 Catalina.this.stop(); } } catch (Throwable ex) { ExceptionUtils.handleThrowable(ex); log.error(sm.getString("catalina.shutdownHookFail"), ex); } finally { // If JULI is used, shut JULI down *after* the server shuts down // so log messages aren't lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).shutdown(); } } } }
可以看到,只是去调用Catalina的stop()方法。不过这总调用方法也是比较奇特的,通过Catalina.this.stop()方法,不知道这种是不是可以跨线程调用一个类的实例,如果能,那将是一个很棒。
[补充]:Catalina.this方法是内部类调用外部类的方法。可以用来解决线程之间传递示例的问题。
3.2.执行shell的stop命令
shell的stop命令落在Catalina上只是去调用stopServer(args)
方法,具体方法实现如下:
public void stopServer(String[] arguments) { //1.参数设置 if (arguments != null) { arguments(arguments); } Server s = getServer(); //2.如果Server存在,就调用Server的stop和destroy方法进行关闭, if (s == null) { // Create and execute our Digester Digester digester = createStopDigester(); File file = configFile(); try (FileInputStream fis = new FileInputStream(file)) { InputSource is = new InputSource(file.toURI().toURL().toString()); is.setByteStream(fis); digester.push(this); digester.parse(is); } catch (Exception e) { log.error("Catalina.stop: ", e); System.exit(1); } } else { // Server object already present. Must be running as a service try { s.stop(); s.destroy(); } catch (LifecycleException e) { log.error("Catalina.stop: ", e); } return; } // 3.如果Server不存在,就重新解析server.xml文件构造server,然后通过socket发送shutdown命令关闭 s = getServer(); if (s.getPort()>0) { try (Socket socket = new Socket(s.getAddress(), s.getPort()); OutputStream stream = socket.getOutputStream()) { String shutdown = s.getShutdown(); for (int i = 0; i < shutdown.length(); i++) { stream.write(shutdown.charAt(i)); } stream.flush(); } catch (Exception ce) { ce.printStackTrace(); } } else { log.error(sm.getString("catalina.stopServer")); System.exit(1); } }
主要的业务逻辑有三个:
- 1.参数设置
- 2.如果Server存在,就调用Server的stop和destroy方法进行关闭
- 3.如果Server不存在,就重新解析server.xml文件构造server,然后通过socket发送shutdown命令关闭
关闭Server的具体实现,我们放在Server组件中。
4.总结
我们根据用户的调用,梳理了Catalina对start和stop命令行的相应方法。其中Digester对xml文件的解析时值得注意的,停止Server的方式也比较有意思,但是我们放在Server中解析。
转载于:https://www.cnblogs.com/cenyu/p/11072543.html
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/126607.html