Tomcat架构学习

Tomcat架构学习

Tomcat简介

Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,实现了对Servlet和JavaServer Page(JSP)的支持。由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器。

Tomcat架构

img

112a8d88bf72526f701c09b4b97fd5f7.png

​ Tomcat架构采用类似于俄罗斯套娃的设计方式。换句话说就是一个容器包含一个容器,而这个被包含的容器反过来再包含别的实体。Tomcat将Engine,Host,Context,Wrapper统一抽象成容器(Container)。一个抽象的容器模块可以包含各种服务。

容器组件

  • Server组件:Server是最顶级的组件,一个Tomcat对应一个Server,Server代表tomcat的运行实例,其中包含Listener组件用以监听生命周期中的各种事件;包含Global Naming Resources组件用以集成JNDI;包含Service组件用以提供服务。
  • Service组件:Service是服务的抽象,代表请求从接受到处理的所有组件的集合;Server组件可以包含多个Service组件;包含Connector组件用以接收客户端的信息;包含Engine组件用以处理请求;包含Executor用以提供线程池执行任务。一个Service包含多个connector(接受请求的协议),和一个container(容器),多个connector共享一个container容器。
  • Connector组件:负责接受客户端连接并接受信息报文,解析不同协议及io方式。包含Mapper组件对请求地址进行路由;包含CoyoteAdaptor组件用以将Connector组件和Engine等容器组件适配;
  • Executor组件:线程池
  • Engine组件:Servlet引擎,container容器中顶层的容器对象,用来管理多克虚拟站点,包含Listener组件用以在生命周期中对Engine相关的事件进行监听;包含AccessLog组件以记录访问日志;包含Cluster组件以提供集群功能,将需要共享的数据同步到集群中的其他Tomcat实例中;包含Pipeline组件用以处理请求;包含Realm组件用以提供安全权限功能。一个Service最多只有一个Engine,但一个engine可以包含多个host主机。
  • Host组件:代表一个虚拟主机,或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可包含多个Context。Host包含Listener组件用以在生命周期中对Host相关的事件进行监听;包含AccessLog组件以记录访问日志;包含Cluster组件以提供集群功能,将需要共享的数据同步到集群中的其他Tomcat实例中;包含Pipeline组件用以处理请求;包含Realm组件用以提供安全权限功能。一个host对应一个网络域名,一个host包含多个context。
  • Context组件:Web应用抽象,Web应用部署tomcat后会转换为Context对象;包含了各种静态资源、若干Servlet(Wrapper容器)以及各种其他动态资源。contest表示一个Web应用。
  • Mapper组件用以作为路由映射Servlet。
  • Wrapper组件:对应的是Servlet;包含Web应用开发常用的Servlet组件;包含ServletPool组件用以存放Servlet对象。web应用中的servlet会被包装成一个wrapper。

可以用一张图来表示请求在Container中的解析过程:

img

Tomcat将请求路由到具体的Serclet的过程是交给Mapper组件来完成的。Mapper组件里面会保存着Tomcat应用的各种配置信息,例如Host域名、Context路径等。

1.根据请求协议各端口号路由到相应的Service,找到Service对应唯一的Engine容器。
2.更与域名地址找到相应的Host容器。
3.根据URL路径匹配某一个Context组件。
4.最后根据URL匹配到某一个Wrapper,也就是Servlet。

JavaWeb三大组件

Servlet组件

Servlet是用来处理客户端请求的动态资源,当Tomcat接收到来自客户端的请求时,会将其解析成RequestServlet对象并发送到对应的Servlet上处理。

Servlet组件有javax.servlet.Servlet类实现,由initgetServletConfigservicegetServletInfodestory方法构成。

image-20221123230434457

Servlet的生命周期

Servlet的生命周期分如下五个阶段

  • 加载:当Tomcat第一次访问Servlet时,Tomcat会负责创建Servlet的实例
  • 初始化:Tomcat创建Servlet后,调用init()方法初始化对象
  • 服务:当浏览器访问Servlet时,Servlet会调用service()处理请求
  • 销毁:当Tomcat关闭时或检测到Servlet从Tomcat删除时会调用destory()方法。
  • 卸载:当Servlet调用完destroy()方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()方法进行初始化操作

为了简化操作,Tomcat帮我们封装了javax.servlet.http.HttpServlet类,其中包括doGetdoPost等方法。对于简单的HTTP请求,我们只需要重载响应的方法即可,例如get方法重载doGet()

image-20221123230835655

Servlet样例
// src/main/java/example/demo/Login.java
package example.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/password")
public class Login extends HttpServlet {
    private String message;

    public void init() {
        message = "欢迎登陆";
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // String getMethod():获取请求方式:GET
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("<pre><h3>");
        String method = req.getMethod();
        printWriter.write("method:" + method);
        printWriter.write("\n");
        // String getContextPath():获取虚拟目录(项目访问路径):/webapp
        String contextPath = req.getContextPath();
        printWriter.write("contextPath:" + contextPath);
        printWriter.write("\n");
        // StringBuffer
        // getRequestURL():获取URL(统一资源定位符):http://localhost:8080/webapp/Login
        StringBuffer url = req.getRequestURL();
        printWriter.write("url:" + url.toString());
        printWriter.write("\n");
        // String getRequestURI():获取URI(统一资源标识符): /webapp/Login
        String uri = req.getRequestURI();
        printWriter.write("uri:" + uri);
        printWriter.write("\n");
        // String getQueryString():获取请求参数(GET方式): username=admin
        String queryString = req.getQueryString();
        printWriter.write("queryString:" + queryString);
        printWriter.write("\n");

        printWriter.write(message);
        printWriter.write("</h3></pre>");
    }

    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("Post方法" + message);
    }
}

上述代码在/password路径中创建了名为Login的Servlet,项目名称为webapp,访问http://localhost:8080/webapp/password,成功调用doGet方法。

image-20221123231446703

值得注意的是,在代码中我们使用了@WebServlet("/password")注解进行Servlet注册,对Servlet进行路由绑定,任何访问password的请求都将传递给Login类进行处理。当然@WebServlet是servlet3.0以后支持的方式,如果servlet低于3.0,我们仍然需要使用web.html进行配置,为了与上面区分,下面的web.html/Login的路径绑定至Login类上。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <servlet>
        <!--这里是给我们的servlet取个别名,一般类名就行-->
        <servlet-name>Login</servlet-name>
        <!--这里是全类名,也就是让系统知道你上面那个名字是取给谁的-->
        <servlet-class>example.demo.Login</servlet-class>
    </servlet>
    <!--给上面的东西添加映射,可以理解为,上面创建了一个门,这里写门通向哪里-->
    <servlet-mapping>
        <!--和上面的大门名对应-->
        <servlet-name>Login</servlet-name>
        <!--大门名的接口, 后面运行后的地址加上/ser01,就可以访问到我们的servlet类了-->
        <url-pattern>/Login</url-pattern>
    </servlet-mapping>

</web-app>

tomcaturl访问http://localhost:8080/webapp/password过程如下图所示:

JavaWeb.drawio

ServletConfig

ServletConfig就是Servlet的配置参数对象,每个Servlet都有一个专属的ServletConfig,负责在初始化时将当前Servlet的配置传递给Servlet,简单来说,ServletConfig就是当前Servlet在web.xml中的配置信息,可以使用getServletConfig()获得当前Servlet的ServletConfig对象。

主要方法

getServletName() 获取Servlet的别名servlet-name的值
getInitParameter("") 获取当前Servlet的初始化值init-param
getServletContext() 获取ServletContext对象

测试样例

// src/main/java/example/demo/Login.java
package example.demo;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/password")
public class Login extends HttpServlet {
    private String message;

    public void init() {
        message = "欢迎登陆";
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("<pre><h3>");

        //获取ServletConfig
        ServletConfig servletConfig = getServletConfig();
        String name = servletConfig.getServletName();
        printWriter.write("Servlet的名称是"+name);
        printWriter.write("\n");
        String arg1 = servletConfig.getInitParameter("arg1");
        printWriter.write("arg1:的值为"+arg1);
        printWriter.write("</h3></pre>");
    }

    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("Post方法" + message);
    }
}
<!--/src/main/webapp/WEB-INF/web.xml-->
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Login</servlet-name>
    <servlet-class>example.demo.Login</servlet-class>
    <!--  配置当前Servlet上下文参数-->
    <init-param>
      <param-name>arg1</param-name>
      <param-value>Servlet_login</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>Login</servlet-name>
    <url-pattern>/Login</url-pattern>
  </servlet-mapping>
  <!--  配置ServletContext上下文参数-->
  <context-param>
    <param-name>arg2</param-name>
    <param-value>Servlet-Global</param-value>
  </context-param>

</web-app>

image-20221124103649762

ServletContext

ServletContext是一个全局的存储信息的空间,可以理解为全局变量,一个Web工程内只有一个ServletContext对象实例,是一个域对象。任何ServletConfig都可以通过servletConfig.getServletContext()获得ServletContext对象。同样的ServletContext对象也可以在web.xml设置上下文初始化参数。

主要方法

getContextPath() 获得当前工程路径,/工程路径
getRealPath("/") 获得工程部署后在服务器硬盘上的绝对路径
getInitParameter("") 获得web.xml配置的全局上下文参数context-param

测试样例

// src/main/java/example/demo/Login.java
package example.demo;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/password")
public class Login extends HttpServlet {
    private String message;

    public void init() {
        message = "欢迎登陆";
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("<pre><h3>");
 
        //获取ServletConfig
        ServletConfig servletConfig = getServletConfig();
        String name = servletConfig.getServletName();
        printWriter.write("Servlet的名称是"+name);
        printWriter.write("\n");
        String arg1 = servletConfig.getInitParameter("arg1");
        printWriter.write("arg1:的值为"+arg1);
        printWriter.write("\n");

        // 获得ServletContext
        ServletContext servletContext = servletConfig.getServletContext();
        String arg2 = servletContext.getInitParameter("arg2");
        printWriter.write("arg2:的值为"+arg2);

        printWriter.write("</h3></pre>");
    }

    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("Post方法" + message);
    }
}

<!--/src/main/webapp/WEB-INF/web.xml-->
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Login</servlet-name>
    <servlet-class>example.demo.Login</servlet-class>
    <!--  配置当前Servlet上下文参数-->
    <init-param>
      <param-name>arg1</param-name>
      <param-value>Servlet_login</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>Login</servlet-name>
    <url-pattern>/Login</url-pattern>
  </servlet-mapping>
  <!--  配置ServletContext上下文参数-->
  <context-param>
    <param-name>arg2</param-name>
    <param-value>ServletContext</param-value>
  </context-param>

</web-app>

image-20221124104918885

Filter组件

Filter组件是运行在服务端的组件,主要功能是对客户端访问资源的过滤,不符合条件的拦截,符合要求的资源使用FilterChain.doFilter()放行,并且可以对访问的目标资源访问前后进行逻辑处理。

​ 每个过滤器在客户端向服务器发送请求时进行一次过滤,在服务器向客户端响应时进行一次过滤。一个Servlet可以设置多个Filter,Filter之间按照一定顺序构成调用链,优先级高的先进行调用,每个Filter使用FilterChain接口将处理后的资源传递,其doFIiter()方法用于将本Filter处理完的Servlet资源交给下一个Filter处理,最终交付给Servlet进行响应,Filter调用过程如下图所示:

image-20221117191830346

img

Filter生命周期

Filter生命周期与Servlet一样,Filter的创建和销毁也是由WEB服务器负责。

  • 初始化阶段:init(FilterConfig),只会在web应用启动时调用
  • 拦截和过滤阶段:doFilter(ServletRequest, ServletResponse, FilterChain),完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。
  • 销毁阶段:destory(),销毁Filter,只会在当web应用移除或服务器停止时才调用一次来卸载Filter对象
Filter样例

Filter_memshell.java

// src/main/java/Filter_memshell.java
package example.demo;

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class Filter_memshell implements Filter {
    private String message;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        message = "调用 Filter_mem";
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter printWriter = response.getWriter();

        printWriter.write("<pre><h3>");
        printWriter.write(message);
        printWriter.write("\n");
        // 放行请求
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
    }
}

Filter_2.java

// src/main/java/Filter_2.java
package example.demo;

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class Filter_2 implements Filter {
    private String message;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        message = "调用 Filter_2";
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter printWriter = response.getWriter();

        // printWriter.write("<pre><h3>");
        printWriter.write(message);
        // 放行请求
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
    }
}

web.xml

我们在配置url-pattern配置时,有三种写法:

  • 精确匹配:/Login
  • 目录匹配:/aaa/bbb/*
  • 扩展名匹配:*.abc,*.jsp

值得注意的是,url-pattern也可以使用servlet-name代替,此时Filter会与Servlet绑定。

<!--/src/main/webapp/WEB-INF/web.xml-->
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <servlet>
    <!--这里是给我们的servlet取个别名,一般类名就行-->a
    <servlet-name>Login</servlet-name>
    <!--这里是全类名,也就是让系统知道你上面那个名字是取给谁的-->
    <servlet-class>example.demo.Login</servlet-class>
    <init-param>
      <param-name>arg1</param-name>
      <param-value>Servlet_login</param-value>
    </init-param>
  </servlet>
    <!--给上面的东西添加映射,可以理解为,上面创建了一个门,这里写门通向哪里-->
  <servlet-mapping>
    <!--和上面的大门名对应-->
    <servlet-name>Login</servlet-name>
    <!--大门名的接口, 后面运行后的地址加上/ser01,就可以访问到我们的servlet类了-->
    <url-pattern>/Login</url-pattern>
  </servlet-mapping>
  <!--  配置ServletContext上下文参数-->
  <context-param>
    <param-name>arg2</param-name>
    <param-value>ServletContext</param-value>
  </context-param>
  <filter>
    <filter-name>Filter1</filter-name>
    <filter-class>example.demo.Filter_memshell</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Filter1</filter-name>
    <!--对该web应用下的所有资源进行过滤-->
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>Filter2</filter-name>
    <filter-class>example.demo.Filter_2</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Filter2</filter-name>
    <url-pattern>/*</url-pattern>
    <!--<servlet-name>Login</servlet-name>-->
  </filter-mapping>

</web-app>

当然,对于注册Filter,我们也可以使用@WebFilter()注解实现,上述web.xml可以简写为:

@WebFilter(filterName = "Filter_memshell",
    urlPatterns = "/Login"
)

image-20221124155447634

Filter执行顺序

filter执行顺序是由filter注册时优先级决定,与Servlert类似,Filter也存在两种注册方式。

  • 基于注解配置:按照类名的字符串比较规则比较,值小的先执行
  • 基于web.xml配置:根据对应的Mapping的顺序组织,定义在上面先执行。
FilterConfig

与Servlet类似,Filte同样存在FilterConfig存储基本配置信息,不同的是Filer只能在init方法参数中获取。

主要方法

getFilterName()  获得Filter的名字filter-name的内容
getServletContext() 获得ServletContext对象
getInitParameter("") 获取在Filter中配置的init-param初始化参数
getInitParameterNames() 获取在Filter中配置的初始化参数的名称

Listener组件

Listener是Listener监听器,用于监听一个方法或属性,当被监听的方法被调用或者属性改变时,通过回调函数,反馈给客户(程序)。

监听器的分类
事件源 监听器 描述
ServletContext ServletContextListener 用于监听 ServletContext 对象的创建与销毁过程
HttpSession HttpSessionListener 用于监听 HttpSession 对象的创建和销毁过程
ServletRequest ServletRequestListener 用于监听 ServletRequest 对象的创建和销毁过程
ServletContext ServletContextAttributeListener 用于监听 ServletContext 对象的属性新增、移除和替换
HttpSession HttpSessionAttributeListener 用于监听 HttpSession 对象的属性新增、移除和替换
ServletRequest ServletRequestAttributeListener 用于监听 HttpServletRequest 对象的属性新增、移除和替换
HttpSession HttpSessionBindingListener 用于监听 JavaBean 对象绑定到 HttpSession 对象和从 HttpSession 对象解绑的事件
HttpSession HttpSessionActivationListener 用于监听 HttpSession 中对象活化和钝化的过程

根据监听对象的不同可以大致划分为三类:

  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener
ServletContextListener使用示例
// src/main/java/Listener_memshell.java
package example.demo;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

@WebListener
public class Listener_memshell implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        /* This method is called when the servlet context is initialized(when the Web application is deployed). */
        System.out.println("ServletContext对象创建了!");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        /* This method is called when the servlet Context is undeployed or Application Server shuts down. */
        System.out.println("ServletContext对象销毁了!");
    }
}

当启动Tomcat时,Servlet被创建;Tomcat结束时,Servlet被销毁。

image-20221124163335188

image-20221124163345152

三个组件启动顺序

tomcat启动时,三大组件的启动顺序为Listener->Filter->Servlet,在org.apache.catalina.core.StandardContext类的startInternal()方法中,依次调用了listenerStart()filterStart()loadOnStartUp()分别对应Listener,Filter,Servlet。

image-20221124164252406

参考连接

tomcat整体架构

Tomcat 主要组件(让你熟练运用)

javaweb三大组件

JavaEE–JavaWeb三大组件Servlet、Filter、Listener

猜你喜欢

转载自blog.csdn.net/weixin_44411509/article/details/128021985
今日推荐