引言

上一篇博客我们将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 exceptionsthrow 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方法

  @Overridepublic 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 IAEchildren.put(child.getName(), child);}

四、启动容器(tomcat.start())

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 Servicessynchronized (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 StartChildresults.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;}@Overridepublic 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 withtry {context = (Context) event.getLifecycle();} catch (ClassCastException e) {log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);return;}// Process the event that has occurredif (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 toolsif (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 initializedTreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();for (Container child : children) {//这里的 Wrapper就是 我们前面封装的 servletWrapper 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" servletsfor (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 specifiedif(getComputedFailCtxIfServletStartFails()) {return false;}}}}return true;}

通过 load 方法  最终会调用 servlet的init方法。

五、总结

上面内容就是整个tomcat是如何调用servlet初始化方法的流程,整个流程小编的理解,如果有错误,欢迎指正,小编已经在源码中重要部分进行了注释,所以如果有需要的各位读者,可以下载我的注释 源码,注释源码地址:

https://github.com/good-jack/tomcat_source/tree/master

从源码分析tomcat如何调用Servlet的初始化相关推荐

  1. java tomcat源码_详解Tomcat系列(一)-从源码分析Tomcat的启动

    在整个Tomcat系列文章讲解之前, 我想说的是虽然整个Tomcat体系比较复杂, 但是Tomcat中的代码并不难读, 只要认真花点功夫, 一定能啃下来. 由于篇幅的原因, 很难把Tomcat所有的知 ...

  2. Tomcat - 源码分析Tomcat是如何处理一个Servlet请求的

    文章目录 Tomcat中的NIO模型 Servlet 请求处理分析 Servlet请求处理流程示意图 Servlet请求处理源码剖析 Mapper 组件体系结构 Tomcat中的NIO模型 Tomca ...

  3. mybatis源码分析(方法调用过程)

    十一月月底,宿舍楼失火啦,搞得20多天没有网,目测直到放假也不会来了... 正题 嗯~,其实阅读源码不是为了应付面试,更重要的让你知道,大师是怎样去写代码的,同样是用Java,为啥Clinton Be ...

  4. MyBatis源码分析(三):MyBatis初始化(配置文件读取和解析)

    一. 介绍MyBatis初始化过程 项目是简单的Mybatis应用,编写SQL Mapper,还有编写的SqlSessionFactoryUtil里面用了Mybatis的IO包里面的Resources ...

  5. android源码分析之JNI调用与回调

    通过JNI,Java程序可以在加载本地库之后,调用Java类中声明的在本地库中实现的本地方法.此外,本地库中的函数也可以通过回调的方式调用Java类中的成员变量或者成员函数.        1.在Ja ...

  6. jQuery1.11源码分析(8)-----jQuery调用Sizzle引擎的相关API

    之所以把这部分放在这里,是因为这里用到了一些基本API,前一篇介绍过后才能使用. //jQuery通过find方法调用Sizzle引擎 //jQuery通过find方法调用Sizzle引擎 jQuer ...

  7. Openstack Nova 源码分析 — RPC 远程调用过程

    目录 目录 Nova Project Services Project 的程序入口 setuppy Nova中RPC远程过程调用 nova-compute RPC API的实现 novacompute ...

  8. ijkplayer源码分析之surface与opengl关联初始化

    转自:https://www.jianshu.com/p/84151c863c72 上层java代码 IjkMediaPlayer.java 构造方法 step 1: IjkMediaPlayer() ...

  9. Spring源码分析4 — spring bean创建和初始化

    1 介绍 创建并初始化spring容器中,refresh()方法中解析xml配置文件,注册容器后处理器,bean后处理器,初始化MessageSource,ApplicationEventMultic ...

最新文章

  1. 中国安全态势越来越好,专访山石网科CSO蒋东毅 | 拟合
  2. 如何卸载Windows 7中的IE10并还原到IE9
  3. 【转】Linux查看文件编码格式及文件编码转换
  4. 「Codeforces」598E (区间dp)
  5. 数据库面试题【十三、超大分页怎么处理】
  6. PMP - 2011年6月考前辅导班
  7. WildFly 8与GlassFish 4 –选择哪个应用服务器
  8. ZDB5304烧写方法
  9. 基于java员工管理系统设计(含源文件)
  10. mysql数据库无法被其他ip访问的解决方法
  11. 在实际工作中,WPS对比office,谁更强?
  12. Element-UI 要怎么学?官方文档!
  13. 简单的web接口自动化测试
  14. Oracle 备份与恢复学习笔记(14)
  15. 有关linux的GPG签名验证错误的解决方法。
  16. postgresql9.5.9相关的日志文件介绍
  17. 关于10月16日数据迁移致网友的致歉信
  18. android水印的添加,Android添加水印的正确方法 只要三步!
  19. A Game of Thrones(58)
  20. OpenGL GLM 环境配置

热门文章

  1. GEE:应用遥感影像时空插值技术的实践(插值填补去云空洞)
  2. 让声音回归本质,畅听天籁之声,KZ ZEX Pro动铁发烧耳机上手实测
  3. Framework入门のPiex 6P源码(下载/编译/刷机)
  4. 如何制作倒放视频,8秒教会你
  5. catia 斑马线分析_CATIA V5R21实用技能快速学习指南
  6. 跨时钟域信号传输(二)——数据信号篇
  7. 大家行,才是真的行——人人都是产品经理编辑手记
  8. EAIDK-610(RK3399)使用体验
  9. 百融榕树使用TreeMap ,百融榕树实现Comparable接口
  10. Java方法的参数传递解析