如何理解Tomcat容器技术

前面的复习
我的tomcat路径D:\java_develop\apache-tomcat-8.5.55

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_develop\apache-tomcat-8.5.55\conf\server.xml

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

<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
 </Host>

当我们访问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 name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
            <Context path="/tomcatapp1" 
            docBase = "D:\01_tomcat_war_exploded"/>
            <Context path="/tomcatapp2" 
            docBase = "D:\02_tomcat_war_exploded"/>
 </Host>

多个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 name="www.cy.com"  appBase="cyapp"
            unpackWARs="true" autoDeploy="true">
            <Context path="/tomcatapp" 
            docBase = "D:\01_tomcat_war_exploded"/>
 </Host>
 
<Host name="www.mn.com"  appBase="cyapp"
            unpackWARs="true" autoDeploy="true">
            <Context path="/tomcatapp" 
            docBase = "D:\02_tomcat_war_exploded"/>
 </Host>

容器

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

<Engine name="Catalina" defaultHost="localhost">

     
      <Realm className="org.apache.catalina.realm.LockOutRealm">
       
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
     <!-- 自己加的Host-->
     <Host name="www.cy.com"  appBase="cyapp"
            unpackWARs="true" autoDeploy="true">
            <Context path="/tomcatapp" 
            docBase = "D:\01_tomcat_war_exploded"/>
	 </Host>
    
     <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
          <!-- Valve是啥,英文翻译阀门 -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" 
               directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
    	<!-- 自己加的Context  -->
        <Context path="/mytomcat" 
        docBase = "D:\01_tomcat_war_exploded"/>
      </Host>
</Engine>

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

Class Engine 
	List<Host> hosts
Class Host 
	List<Context> contexts
Class Context 
	List<Servlet> 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<Servlet> servlets中

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

Class Engine 
	List<Host> hosts
	List<Valve> pipeline //水管
Class Host 
	List<Context> contexts
	List<Valve> pipeline
Class Context 
	List<Wrapper> wrappers
	List<Valve> pipeline
Class Wrapper
	String servletName
	Class  servletClass
	List<Servlet> servlets
	List<Valve> 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长连接,如果觉得我的博客不错,可以点个赞,大家的鼓励就是我创造最大的动力!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

forgetable tree

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值