栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

从server.xml到Tomcat底层源码---从浅到深,抽丝剥茧

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

从server.xml到Tomcat底层源码---从浅到深,抽丝剥茧

前面的复习
我的tomcat路径D:java_developapache-tomcat-8.5.55

文章目录
  • Sevlet生命周期
  • 部署JavaWeb项目
  • Server.xml
  • 容器
  • 源码分析
    • 对象属性图

Sevlet生命周期

servlet只会初始化一次,也就是整个过程中只存在一个servlet对象,即便是有多次访问,依然只有一个对象,这个对象是可以复用的

我想你一定会好奇这个servlet究竟是在什么时候创建的,所以就来讲一下servlet的生命周期

所谓的生命周期我们在java基础知识中一定也了解过,就是servlet类究竟在什么时候创建,调用了何种方法,最后在什么时候被销毁.

我们之前的对象都是自己手动创建,最后由JVM来销毁的,这就是普通Java对象的生命周期

而servlet的整个生命周期,都是由tomcat,也就是服务器控制的,在第一次访问路径对应的servlet的时候创建它,在服务器关闭时候销毁servlet

让servlet在服务器启动的时候就创建

servlet只有在第一次被访问的时候才会加载,这肯定会造成第一个访问的人访问时间较长,因为他需要等待servlet完成加载.那么,有没有什么方法能够使得servlet自动加载呢,就是在启动服务器的时候就将servlet加载起来呢?答案是有的,同样可以在web.xml中进行配置

部署JavaWeb项目

1个javaweb项目经过idea编译后,在对应的out目录下面生成war_exploded目录

javaweb项目部署

打开01_tomcat_war_exploded,下图就是目录结构
其中原来的class文件放在了classes目录下面,当这个项目部署到tomcat, 项目的Web-INF目录下面的文件我们没法通过浏览器直接访问。外面的文件我们可以直接访问,例如通过http://localhost:8080/index.jsp访问index.jsp

部署这个项目有3个方法,

方法1:直接在idea中启动
方法2:将这个01_tomcat_war_exploded直接放到webapps, 通过cmd在命令行中输入:startup.bat,就会运行此项目。因为tomcat默认端口8080,浏览器输入
http://localhost:8080/01_tomcat_war_exploded/index.jsp访问就能访问01_tomcat_war_exploded目录下面的index.jsp文件


观察路径 http://localhost:8080/01_tomcat_war_exploded/index.jsp
其中01_tomcat_war_exploded对应项目文件夹的名字,如果更改文件夹的名字,访问路径也要随之改。

还有一种方式,我们可以把01_tomcat_war_exploded项目放到任何地方,比如我把它从webapps中移了出来,现在我再conf下面的server.xml中加入一个Context标签

指明我们的访问项目路径(path属性)和项目位置(docbase属性),现在我访问index.jsp路径是http://localhost:8080/mytomcat/index.jsp

总结:项目文件夹放在webapps下面或者Context方式指定位置

Server.xml

为啥webapps目录下的项目直接就部署了,理解这个问题之前先看看server.xml文件
文件在D:java_developapache-tomcat-8.5.55confserver.xml

有一个Host标签
name=域名
appbase=项目集合文件夹(为啥这么说?等会解释)
unpackWARs=true 自动解压war包
autoDeply=true 自动部署


 

当我们访问http://localhost:8080/01_tomcat_war_exploded/index.jsp,tomcat最先通过域名Host找到appbase即webapps, 然后找到01_tomcat_war_exploded文件夹,再找到文件夹下面的index.jsp

如果在appbase下面找不到项目,就会寻找Context标签,这意味着我们的Context属于Host, 即Host可以管理很多Context, 再想一想,Context其实是一个项目,一个项目中有多个Servlet, 这些Servlet才是我们自己写的


            
            
 

多个Host

实际上,server.xml可以有多个Host, 比如我写了一个Host, 指定appbase=cyapp, 当tomcat启动,我们会得到一个文件夹cyapp,类似webapps, 我们可以把项目放在这里

当浏览器访问http://www.cy.com:8080/tomcatapp/index.jsp,他会先找到www.cy.com对应的服务器(如果要本地实验需要改host文件,将这个域名和本机IP对应上), 再找到服务器上面的Tomcat应用(8080端口),通过www.cy.com域名找到Host标签,找到tomcatapp对应哪个项目,找项目通过cyapp文件夹和Context标签。

2个域名可以对应1个IP地址, 可以多写几个Host标签测试

想一想,当访问
http://www.cy.com:8080/tomcatapp/index.jsp
http://www.mn.com:8080/tomcatapp/index.jsp
他们找到的是同一个index.jsp吗


            
 
 

            
 
容器

前面我们了解了Host, Context, Servlet, 最后一个标签就是Engine
他是最大的标签,包裹了多个Host,Host包裹多个Context, Context里面有多个我们自己写的Servlet,层层递进,像俄罗斯套娃



     
      
       
        
      
     
     
            
	 
    
     
          
        
    	
        
      

这些嵌套关系我们用java可以简单的表示,Engine ,Host, Context可以看作是一个容器

Class Engine 
	List hosts
Class Host 
	List contexts
Class Context 
	List servlets

实际上,Tomcat远不止这么简单,因为在Host标签里面还有一个Valve标签,并有一个属性className=“org.apache.catalina.valves.AccessLogValve”,先记着AccessLogValve,后面即将遇到它

看来Valve是一种类型,它的中文名是阀门,写在Host标签下面的Valve自然是属于Host的。Host标签下面只写了一个Valve,难道Host就只有1个Valve吗?
自然不是,而且不只是Host容器有Valve, Engine容器 , Context容器也有自己的Valve

提到阀门,我们可以联想到水管,毕竟他们俩是配套的,一个水管上面应该可以装多个阀门,水流流过水管的时候,会依次经过各个阀门。
我们的请求像水流就会经过这些水管上面的阀门。

除此之外,我们的Context不是最后一层容器,还有一层容器叫做Wrapper, 它是对Servlet的包装

为啥还要包装,按道理一个Servlet类型只会生成一个单例对象,其实我们可以设置,让Servlet生成多个对象。参考:Servlet单例多例问题
这些对象会存在List servlets中

接下来才是完整的容器描述版本,下面只是一个简单描述,后面会通过源码方式得到具体对象描述

Class Engine 
	List hosts
	List pipeline //水管
Class Host 
	List contexts
	List pipeline
Class Context 
	List wrappers
	List pipeline
Class Wrapper
	String servletName
	Class  servletClass
	List servlets
	List pipeline

请认真观看下图,请求先经过Engine容器的阀门,再经过Host容器的各个阀门
看到类型AccessLogValve 和之前server.xml中的描述
className=“org.apache.catalina.valves.AccessLogValve”对上了

每一个容器至少都有1个阀门,叫做Standard***Valve

源码分析

我们在自己的Servlet中添加一个断点,开始调试

下面是函数调用栈
请认真观看下图,看到类型AbstractAccessLogValve 和之前server.xml中的描述
className=“org.apache.catalina.valves.AccessLogValve”, 你发现他俩好像不一样,实际上AbstractAccessLogValve是一个抽象类,因为AccessLogValve没有写invoke方法,所以执行AbstractAccessLogValve的invoke

doGet:21, MyServlet (com)
service:634, HttpServlet (javax.servlet.http)
service:741, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:199, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:543, Authenticatorbase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
//这里
invoke:690, AbstractAccessLogValve (org.apache.catalina.valves)
//Engine下面的一个Valve
invoke:87, StandardEnginevalve (org.apache.catalina.core) 
service:343, CoyoteAdapter (org.apache.catalina.connector)
//下面不分析了
service:615, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:818, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1627, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorbase (org.apache.tomcat.util.net)
runWorker:1128, ThreadPoolExecutor (java.util.concurrent)
run:628, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:830, Thread (java.lang)

下面是我截取的源码,有删减

final class StandardEnginevalve extends Valvebase {
    
    public final void invoke(Request request, Response response) throws IOException, ServletException {
    //先拿到Host容器,再从水管中得到阀门
        Host host = request.getHost(); 
        host.getPipeline().getFirst().invoke(request, response);
    }
}

public abstract class AbstractAccessLogValve extends Valvebase implements AccessLog {

    public void invoke(Request request, Response response) throws IOException, ServletException {

        this.getNext().invoke(request, response);
    }
}

final class StandardHostValve extends Valvebase {

    public final void invoke(Request request, Response response) throws IOException, ServletException {
        Context context = request.getContext();
        //这里
        context.getPipeline().getFirst().invoke(request, response);      
    }
}

final class StandardContextValve extends Valvebase {
    public final void invoke(Request request, Response response) throws IOException, ServletException {
        Wrapper wrapper = request.getWrapper();          
        //这里
        wrapper.getPipeline().getFirst().invoke(request, response);
    }
}

final class StandardWrapperValve extends Valvebase {
    public final void invoke(Request request, Response response) throws IOException, ServletException {
        StandardWrapper wrapper = (StandardWrapper)this.getContainer();
        Servlet servlet = null;
        //通过wrapper容器得到Servlet
        servlet = wrapper.allocate();
        //wrapper的父亲的Context
        Context context = (Context)wrapper.getParent();
        ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
        filterChain.doFilter(request.getRequest(), response.getResponse());
}
对象属性图

host的属性
用HashMap children存储下一层context容器
pipeline不是简单的把Valve放在一个List中
通过first找到第一个阀门,这里是AccessLogValve
再通过阀门的next属性找到下一个阀门

AccessLogValve的下一个阀门是ErrorReportValve

分析到这里,我想你已经对Tomcat容器有一个大概了了解了,其实Tomcat的设计非常复杂,比如底层的BIO, NIO模型,还有Socket,以及HTTP长连接,如果觉得我的博客不错,可以点个赞,大家的鼓励就是我创造最大的动力!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/665993.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号