tomcat怎么知道访问servlet(详解从源码分析tomcat如何调用Servlet的初始化)
tomcat怎么知道访问servlet
详解从源码分析tomcat如何调用Servlet的初始化目录
- 引言
- 一、代码启动tomcat
- 二、tomcat框架
- 三、创建容器(addWebapp())
- 3.1 方法 调用流程图
- 3.2 源码分析
- 四、启动容器(tomcat.start())
- 4.1、方法调用流程图
- 4.2、源码分析
- 五、总结
上一篇博客我们将tomcat源码在本地成功运行了,所以在本篇博客中我们从源码层面分析,tomcat在启动的过程中,是如何初始化servlet容器的。我们平常都是将我们的服务部署到 tomcat中,然后修改一下配置文件,启动就可以对外提供 服务了,但是我们对于其中的一些流程并不是非常的了解,例如如何加载的web.xml等。这是我们分析servlet 和 sringMVC必不可少的过程。
注释源码地址:https://github.com/good-jack/tomcat_source/tree/master
一、代码启动tomcat平常我们不论是Windows还是linux,我们都是通过脚本来启动tomcat,这对于我们分析源码不是很友好,所以我们 需要通过代码启动,启动代码如下:
Tomcat tomcat = new Tomcat(); tomcat.setPort(8080); //new 出各层容器,并且维护各层容器的关系 tomcat.addWebapp("/","/"); tomcat.start(); //阻塞监听端口 tomcat.getServer().await();
启动代码还是非常非常简单,从代码中我们就可以看出,我们本篇博客主要分析的就是 addWebapp()方法和start()方法,通过这两个方法我们就可以找到servlet容器是在什么时候被初始化的。
二、tomcat框架在我们进行分析上面两个方法之前,我们先总结一下tomcat的基础框架,其实从我们非常熟悉的 server.xml配置文件中就可以知道,tomcat就是一系列父子容器组成:
Server ---> Service --> Connector Engine addChild---> context(servlet容器) ,这就是我们从配置文件中分析出来的几个容器,tomcat启动时候就是逐层启动容器。
三、创建容器(addWebapp())3.1 方法 调用流程图
上面的流程图就是,从源码中逐步分析出来的几个重要的方法,这对于我们分析源码非常有帮助。
3.2 源码分析
1)通过反射获得configContext监听器
方法路径:package org.apache.catalina.startup.Tomcat.addWebapp(Host host, String contextPath, String docBase);
public Context addWebapp(Host host, String contextPath, String docBase) { //通过反射获得一个监听器 ContextConfig, //通过反射得到的一定是LifecycleListener的一个实现类,进入getConfigClass得到实现类(org.apache.catalina.startup.ContextConfig) LifecycleListener listener = null; try { Class<?> clazz = Class.forName(getHost().getConfigClass()); listener = (LifecycleListener) clazz.getConstructor().newInstance(); } catch (ReflectiveOperationException e) { // Wrap in IAE since we can't easily change the method signature to // to throw the specific checked exceptions throw new IllegalArgumentException(e); } return addWebapp(host, contextPath, docBase, listener); }
2) 获得一个context容器(StandardContext)
在下面代码中,createContext()方法通过反射加载StandardContext容器,并且将设置监听ContextConfig, ctx.addLifecycleListener(config);
public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) { silence(host, contextPath); //获得一个context容器(StandardContext) Context ctx = createContext(host, contextPath); ctx.setPath(contextPath); ctx.setDocBase(docBase); if (addDefaultWebXmlToWebapp) { ctx.addLifecycleListener(getDefaultWebXmlListener()); } ctx.setConfigFile(getWebappConfigFile(docBase, contextPath)); //把监听器添加到context中去 ctx.addLifecycleListener(config); if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) { // prevent it from looking ( if it finds one - it'll have dup error ) ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath()); } if (host == null) { //getHost会逐层创建容器,并维护容器父子关系 getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; }
3)维护各层容器
getHost()方法中得到各层容器,并且维护父亲容器关系,其中包括,server容器、Engine容器。并且将StandardContext容器通过getHost().addChild(ctx); 调用containerBase中的addChild()方法维护在 children 这个map中。
public Host getHost() { //将每一层的容器都new 出来 Engine engine = getEngine(); if (engine.findChildren().length > 0) { return (Host) engine.findChildren()[0]; } Host host = new StandardHost(); host.setName(hostname); //维护tomcat中的父子容器 getEngine().addChild(host); return host; }
getEngine().addChild(host); 方法选择调用父类containerBase中的addChild方法
@Override public void addChild(Container child) { if (Globals.IS_SECURITY_ENABLED) { PrivilegedAction<Void> dp = new PrivilegedAddChild(child); AccessController.doPrivileged(dp); } else { //这里的child 参数是 context 容器 addChildInternal(child); } }
addChildInternal()方法的 核心代码
private void addChildInternal(Container child) { if( log.isDebugEnabled() ) log.debug("Add child " + child + " " + this); synchronized(children) { if (children.get(child.getName()) != null) throw new IllegalArgumentException("addChild: Child name '" + child.getName() + "' is not unique"); child.setParent(this); // May throw IAE children.put(child.getName(), child); }
4.1、方法调用流程图
4.2、源码分析
说明:StandardServer 、StandardService、StandardEngine等容器都是继承LifecycleBase
所以这里是模板模式的经典应用
1)逐层启动容器
此时的server对应的是我们前面创建的StandardServer
public void start() throws LifecycleException { //防止server容器没有创建 getServer(); //获得connector容器,并且将得到的connector容器设置到service容器中 getConnector(); //这里的start的实现是在 LifecycleBase类中实现 //LifecycleBase方法是一个模板方法,在tomcat启动流程中非常关键 server.start(); }
2) 进入start方法
进入LifecycelBase中的start方法,其中核心方法是startInternal。
从上面我们知道现在我们调用的是StandardServer容器的startInternal()方法,所以我们这里选择的是StandardServer
方法路径:org.apache.catalina.core.StandardServer.startInternal()
protected void startInternal() throws LifecycleException { fireLifecycleEvent(CONFIGURE_START_EVENT, null); setState(LifecycleState.STARTING); globalNamingResources.start(); // Start our defined Services synchronized (servicesLock) { //启动 service容器,一个tomcat中可以配置多个service容器,每个service容器都对应这我们的一个服务应用 for (Service service : services) { //对应 StandardService.startInternal() service.start(); } } }
从上面代码中我们可以看出,启动server容器的时候需要启动子容器 service容器,从这里开始就是容器 逐层向向内引爆,所以接下来就是开始依次调用各层容器的star方法。在这里就不在赘述。
2)ContainerBase中的startInternal()方法 核心代码,从这开始启动StandardContext容器
// Start our child containers, if any //在addWwbapp的流程中 addChild方法中加入的,所以这里需要找出来 //这里找出来的就是 context 容器 Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { //通过线程池 异步的方式启动线程池 开始启动 context容器,进入new StartChild results.add(startStopExecutor.submit(new StartChild(child))); }
new StartChild(child)) 方法开始启动StandardContext容器
private static class StartChild implements Callable<Void> { private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { //开始启动context,实际调用 StandardContext.startInternal() child.start(); return null; } }
StandardContext.startInternal() 方法中的核心代码:
protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); //lifecycleListeners 在addwebapp方法的第一步中,设置的监听的 contextConfig对象 for (LifecycleListener listener : lifecycleListeners) { //这里调用的是 contextConfig的lifecycleEvent()方法 listener.lifecycleEvent(event); } }
进入到 contextConfig中的lifecycleEvent()方法
public void lifecycleEvent(LifecycleEvent event) { // Identify the context we are associated with try { context = (Context) event.getLifecycle(); } catch (ClassCastException e) { log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { //完成web.xml的内容解析 configureStart(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { // Restore docBase for management tools if (originalDocBase != null) { context.setDocBase(originalDocBase); } } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) { configureStop(); } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) { init(); } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); } }
在上面方法中,完成对web.xml的加载和解析,同时加载xml中配置的servlet并且封装成wrapper对象。
3)、启动servlet容器,StandardContext.startInternal() 中的 loadOnStartup(findChildren())方法
public boolean loadOnStartup(Container children[]) { // Collect "load on startup" servlets that need to be initialized TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>(); for (Container child : children) { //这里的 Wrapper就是 我们前面封装的 servlet Wrapper wrapper = (Wrapper) child; int loadOnStartup = wrapper.getLoadOnStartup(); if (loadOnStartup < 0) { continue; } Integer key = Integer.valueOf(loadOnStartup); ArrayList<Wrapper> list = map.get(key); if (list == null) { list = new ArrayList<>(); map.put(key, list); } list.add(wrapper); } // Load the collected "load on startup" servlets for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { //通过 load 方法 最终会调用 servlet的init方法 wrapper.load(); } catch (ServletException e) { getLogger().error(sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()), StandardWrapper.getRootCause(e)); // NOTE: load errors (including a servlet that throws // UnavailableException from the init() method) are NOT // fatal to application startup // unless failCtxIfServletStartFails="true" is specified if(getComputedFailCtxIfServletStartFails()) { return false; } } } } return true; }
通过 load 方法 最终会调用 servlet的init方法。
五、总结上面内容就是整个tomcat是如何调用servlet初始化方法的流程,整个流程小编的理解,如果有错误,欢迎指正,小编已经在源码中重要部分进行了注释,所以如果有需要的各位读者,可以下载我的注释 源码,注释源码地址:
https://github.com/good-jack/tomcat_source/tree/master
到此这篇关于详解从源码分析tomcat如何调用Servlet的初始化的文章就介绍到这了,更多相关tomcat调用Servlet初始化内容请搜索开心学习网以前的文章或继续浏览下面的相关文章希望大家以后多多支持开心学习网!
- 如何让tomcat启动更快(快速解决Tomcat启动慢的问题,超简单)
- idea里面怎么配置tomcat(intellij idea 使用Tomcat部署的项目位置在哪)
- tomcat配置jmx监控(Tomcat配置JNDI数据源的三种方式)
- apache服务部署tomcat(Apache与Tomcat服务器整合的基本配置方法及概要说明)
- tomcat怎么知道访问servlet(详解从源码分析tomcat如何调用Servlet的初始化)
- idea怎么在tomcat部署项目(IDEA 配置Tomcat服务器和发布web项目的图文教程)
- docker和tomcat建立连接(如何基于Dockerfile构建tomcat镜像)
- idea配合tomcat进行web开发(IDEA2021 tomcat10 servlet 较新版本踩坑问题)
- tomcat集群如何实现线程安全(如何通过LambdaProbe实现监控Tomcat)
- tomcatcpu配置(Tomcat进程占用CPU过高的解决方法)
- dockertomcat多开实例(Docker tomcat的设置内存大小配置方式)
- idea项目中tomcat的配置(tomcat部署项目以及与IDEA集成的实现)
- tomcat启动闪退拒绝访问(详解Tomcat双击startup.bat闪退的解决方法)
- tomcat处理高并发请求(Tomcat打破双亲委派机制实现隔离Web应用的方法)
- tomcat是异步非阻塞吗(浅谈Tomcat如何打破双亲委托机制)
- docker容器如何更改tomcat端口(Docker方式启动tomcat访问首页出现404错误)
- 靳东新剧《精英律师》定档,众星云集,这剧可追(靳东新剧精英律师定档)
- 精英律师 廖佳敏封印恋情曝光,顾婕马失前蹄 你个老不死的(廖佳敏封印恋情曝光)
- 以家人之名广受好评,剧情生动引起观众共鸣,演员张新成圈粉无数(以家人之名广受好评)
- 三兄妹感情再遇波折,人设接连崩塌 《以家人之名》剧情猜不透(三兄妹感情再遇波折)
- 《小敏家》金波想要复婚 这只是他圈套的第1步,更可恶的在后面(小敏家金波想要复婚)
- 小敏家 剧情离谱一锅乱炖,但他们俩绝对是这部剧的一大 亮点(剧情离谱一锅乱炖)
热门推荐
- nginx反向代理多个server(Nginx反向代理多个服务器的实现方法)
- 织梦v5.7首页二维码怎么修改(织梦dedecms登录管理后台总是验证码错误的解决方案)
- js运算符使用教程(js中不常见的运算符与操作符总结)
- 织梦源码安装数据库怎么填(Mysql修改端口号 织梦DedeCMS设置教程)
- php新建文件夹代码(php新建文件的方法实例)
- python对mysql数据分析(python使用adbapi实现MySQL数据库的异步存储)
- python如何抓取公众号文章(python爬取微信公众号文章的方法)
- php框架laravel使用(laravel5环境隐藏index.php后缀apache的方法)
- pjs计算方式(JS代码编译器Monaco使用方法)
- docker清理占用空间(docker清理大杀器/docker的overlay文件占用磁盘太大的解决)
排行榜
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9