Java安全学习——Tomcat架构浅析

Tomcat简介

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

简单来说,Tomcat可以看成是Web服务器+Servlet容器,如下图

Tomcat能够通过Connector组件接收并解析HTTP请求,然后将一个ServletRequest对象发送给Container处理。容器处理完之后会将响应封装成ServletRespone返回给Connector,然后Connector再将ServletRespone解析为HTTP响应文本格式发送给客户端,至此Tomcat就完成了一次网络通信。

Tomcat架构

Tomcat架构图

可以看到Tomcat Server大致可以分为三个组件,Service、Connector、Container

Service

其中一个Tomcat Server可以包含多个Service,比如Tomcat默认的Service服务Catalina。每一个Service都是独立的,他们共享一个JVM以及系统类库,并且一个Service负责维护多个Connector和一个Container。

Connector

Connector用于连接Service和Container,解析客户端的请求并转发到Container,以及转发来自Container的响应。每一种不同的Connector都可以处理不同的请求协议,包括HTTP/1.1、HTTP/2、AJP等等。

Container

Tomcat的Container包含四种子容器:EngineHostContextWrapper,在Tomcat源码中我们可以清晰地看到各容器之间的继承关系

其中,一个Container对应一个Engine,一个Engine可以包含多个Host,一个Host可以包含多个Context,Context又包含多个Wrapper,各子容器的功能如下

Engine

可以看成是容器对外提供功能的入口,每个Engine是Host的集合,用于管理各个Host。

Host

可以看成一个虚拟主机,一个Tomcat可以支持多个虚拟主机。

Context

又叫做上下文容器,我们可以将其看成一个Web应用,每个Host里面可以运行多个Web应用。同一个Host里面不同的Context,其contextPath必须不同,默认Context的contextPath为空格(“”)或斜杠(/)。

Wrapper

是对Servlet的抽象和包装,每个Context可以有多个Wrapper,用于支持不同的Servlet每个Wrapper实例表示一个具体的Servlet定义,Wrapper主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行以及资源回收。

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

以上的映射信息通过通过Mapper组件来关联。Mapper组件保存了Web应用的配置信息,容器组件与访问路径的映射关系等。

JavaWeb三大组件

Servlet

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

Servlet的生命周期

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

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

只要访问Servlet,service()就会被调用。init()只有第一次访问Servlet的时候才会被调用。 destroy()只有在Tomcat关闭的时候才会被调用。因此我们主要的业务逻辑代码是写在service()函数中的。

Servlet 生命周期

Servlet使用示例

想要编写一个自己的Servlet,就必须要继承Servlet接口并实现如下五个方法

但每次需要编写Servlet的时候都要重写五个方法,这样未免太繁琐。因此Tomcat已经帮我们封装好了两个类,分别是GenericServlet类和HttpServlet类。

Servlet 关系图

GenericServlet抽象类实现了 Servlet 接口,并对 Servlet 接口中除service()方法外的其它四个方法进行了简单实现。如果我们通过继承GenericServlet类创建来Servlet,只需要重写service()方法即可。但正如其名,GenericServlet抽象类是一个通用的Servlet类,并不是针对某种应用场景而设计的,因此我们在处理HTTP请求的时候需要手动实现对HTTP请求的解析和封装。

HttpServlet是GenericServlet的子类,它在GenericServlet的基础上专门针对HTTP协议进行了处理。其针对每一种HTTP请求都设置了一种处理方法。当我们在使用HttpServlet类的时候,只需要根据HTTP请求类型重写相应的处理方法即可

下面我就以HttpServlet为例,编写一个自己的Servlet

package Servlet;

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("/hello")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String parameter = req.getParameter("name");
        PrintWriter writer = resp.getWriter();
        writer.write("Hello "+parameter+"!");
        writer.close();
    }
}

这里我们使用注解来注册Servlet

...
@WebServlet("/hello")
public class MyServlet extends HttpServlet {
...
}

当然我们也可以通过手动配置web.xml文件来注册Servlet,这种方式虽然繁琐,但是便于管理各Servlet。

  <servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>Servlet.MyServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>

配置Tomcat,添加好相应的war包

访问http://localhost:8081/Tomcat_Servlet_war/hello?name=Feng,结果如下

ServletConfig和ServletContext

ServletConfig

当Servlet容器初始化一个Servlet时,会为这个Servlet创建一个ServletConfig对象,并将 ServletConfig 对象作为参数传递给Servlet。ServletConfig对象封装了Servlet的一些独有参数信息,因此一个Servlet只能对应一个ServletConfig。

下面是ServletConfig的使用示例

package Servlet;

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("/config")
public class Config_Servlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置响应编码
        resp.setContentType("text/html; charset=UTF-8");
        //获取ServletConfig
        ServletConfig servletConfig = getServletConfig();
        //获取Servlet名称
        String name = servletConfig.getServletName();
        PrintWriter writer = resp.getWriter();
        writer.write("Servlet名称为:"+name);
        writer.close();
    }
}
ServletContext

Servlet 容器启动时,会为每个 Web 应用(webapps 下的每个目录都是一个 Web 应用)创建一个唯一的 ServletContext 对象,该对象一般被称为“Servlet 上下文”。

由于一个Web应用可以包含多个Servlet,因此ServletContext可以看作是一个Web应用中各Servlet的共享资源。不同 Servlet 之间可以通过ServletContext对象实现数据通讯,因此ServletContext对象也被称为Context域对象。

ServletContext 对象的生命周期从 Servlet 容器启动时开始,到容器关闭或应用被卸载时结束。

通过ServletContext可以获取Web应用中一些共享的资源。下面我们使用ServletContext来获取上下文初始化参数

首先在web.xml中配置一个上下文初始化参数

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  //配置上下文初始化参数name
  <context-param>
    <param-name>name</param-name>
    <param-value>Feng</param-value>
  </context-param>
  <context-param>
    <param-name>age</param-name>
    <param-value>18</param-value>
  </context-param>
</web-app>
package Servlet;

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;
import java.util.Enumeration;

@WebServlet("/context")
public class Context_Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=UTF-8");
        PrintWriter writer = resp.getWriter();

        ServletContext servletContext = getServletContext();

        Enumeration<String> initParamerNames = servletContext.getInitParameterNames();
        while(initParamerNames.hasMoreElements()){
            String ParamerName = initParamerNames.nextElement();
            String Paramer = servletContext.getInitParameter(ParamerName);
            writer.write(ParamerName+"的值为:"+Paramer+"<br/>");
        }

        writer.close();
    }
}

Filter

Filter用于拦截用户请求以及服务端的响应,能够在拦截之后对请求和响应做出相应的修改。Filter不是Servlet,不能直接访问,它能够对于Web应用中的资源(Servlet、JSP、静态页面等)做出拦截,从而实现一些相应的功能。下面是Filter在Server中的调用流程图

Filter 流程图

这种调用流程类似于设计模式中的“责任链模式”,对于不符合要求的资源进行拦截,而符合要求的资源使用FilterChain.doFilter()放行。下面是一个简单的Filter Servlet示例,这里我们同样使用注解方式进行配置

Hello_Servlet.java

package Filter;

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("/hello")
public class Hello_Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.write("Hello World!");
        writer.close();
    }
}

Hello_Filter1.java

package Filter;

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

@WebFilter("/hello")
public class Hello_Filter1 implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write("调用Filter1!</br>");
        chain.doFilter(request,response);
    }
}

Hello_Filter2.java

package Filter;

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

@WebFilter("/hello")
public class Hello_Filter2 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        PrintWriter writer = response.getWriter();
        writer.write("调用Filter2!</br>");
        chain.doFilter(request,response);
    }
}

运行结果如下

Filter的生命周期

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

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

FilterChain

我们知道,一个Servlet可以注册多个Filter,Web容器会将注册的多个Filter组合成一个“Filter链”,并按照一定的顺序依次执行各Filter的doFilter()方法。

FilterChain就是这样一个接口,其doFIiter()方法用于将本Filter处理完的Servlet资源交给下一个Filter处理。

Filter执行顺序

Filter的注册方式不同,Filter的执行顺序也有所不同

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

FilterConfig

和Servlet类似,由于Filter也有可能访问Servlet,所以Servlet 规范将代表 ServletContext 对象和 Filter 的配置参数信息都封装到一个称为 FilterConfig 的对象中。

FilterConfig接口则用于定义FilterConfig对象应该对外提供的方法,以便在 Filter的doFilter()方法中可以调用这些方法来获取 ServletContext 对象,以及获取在 web.xml 文件中的一些初始化参数。

Listener

Listener是一个实现了特定接口的Java程序,用于监听一个方法或者属性,当被监听的方法被调用或者属性改变时,就会自动执行某个方法。

相关概念

下面有几个与Listener相关的概念

  • 事件:某个方法被调用,或者属性的改变
  • 事件源:被监听的对象(如ServletContext、requset、方法等)
  • 监听器:用于监听事件源,当发生事件时会触发监听器

监听器的分类

监听器一共有如下8种

事件源监听器描述
ServletContextServletContextListener用于监听 ServletContext 对象的创建与销毁过程
HttpSessionHttpSessionListener用于监听 HttpSession 对象的创建和销毁过程
ServletRequestServletRequestListener用于监听 ServletRequest 对象的创建和销毁过程
ServletContextServletContextAttributeListener用于监听 ServletContext 对象的属性新增、移除和替换
HttpSessionHttpSessionAttributeListener用于监听 HttpSession 对象的属性新增、移除和替换
ServletRequestServletRequestAttributeListener用于监听 HttpServletRequest 对象的属性新增、移除和替换
HttpSessionHttpSessionBindingListener用于监听 JavaBean 对象绑定到 HttpSession 对象和从 HttpSession 对象解绑的事件
HttpSessionHttpSessionActivationListener用于监听 HttpSession 中对象活化和钝化的过程

按照监听的对象不同可以划分为三类

  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener

ServletContextListener使用示例

这里我们以ServletContextListener为例,创建一个用于监听ServletContext对象的Listener,这里我仍使用注解配置

package Listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class Hello_Listener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext对象创建了!");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext对象销毁了!");

    }

}

在启动Tomcat服务器的时候,ServletContext对象被创建,同时触发我们设置的ServletContextListener.contextInitialized()方法

当停止Tomcat服务器时,ServletContext对象被销毁,会触发ServletContextListener.contextDestroyed()方法

三者的加载顺序

三者的加载顺序为Listener->Filter->Servlet

org.apache.catalina.core.StandardContext类的startInternal()方法中,首先调用了listenerStart(),接着是filterStart(),最后是loadOnStartup()。这三处调用触发了Listener、Filter、Servlet的构造加载。

...
if (ok) {
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }

            // Check constraints for uncovered HTTP methods
            // Needs to be after SCIs and listeners as they may programmatically
            // change constraints
            if (ok) {
                checkConstraintsForUncoveredMethods(findConstraints());
            }

            try {
                // Start manager
                Manager manager = getManager();
                if (manager instanceof Lifecycle) {
                    ((Lifecycle) manager).start();
                }
            } catch(Exception e) {
                log.error(sm.getString("standardContext.managerFail"), e);
                ok = false;
            }

            // Configure and call application filters
            if (ok) {
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            }

            // Load and initialize all "load on startup" servlets
            if (ok) {
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }

            // Start ContainerBackgroundProcessor thread
            super.threadStart();
        }if (ok) {
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }

            // Check constraints for uncovered HTTP methods
            // Needs to be after SCIs and listeners as they may programmatically
            // change constraints
            if (ok) {
                checkConstraintsForUncoveredMethods(findConstraints());
            }

            try {
                // Start manager
                Manager manager = getManager();
                if (manager instanceof Lifecycle) {
                    ((Lifecycle) manager).start();
                }
            } catch(Exception e) {
                log.error(sm.getString("standardContext.managerFail"), e);
                ok = false;
            }

            // Configure and call application filters
            if (ok) {
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            }

            // Load and initialize all "load on startup" servlets
            if (ok) {
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }

            // Start ContainerBackgroundProcessor thread
            super.threadStart();
        }
...
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇