03 | 你应该知道的 Servlet 规范和 Servlet 容器_servlet规范和servlet容器-程序员宅基地

技术标签: 深入拆解Tomcat  java  容器  服务器  


浏览器发给服务端一个 HTTP 格式的请求,HTTP 服务器收到请求后,需要调用服务端程序来处理,所谓的服务端程序就是你写的 Java 类,一般来说不同的请求需要由不同的 Java 类来处理。

HTTP 服务器怎么知道要调用哪个 Java 类的哪个方法呢。最直接的做法是在 HTTP 服务器代码里写一大堆 if else 逻辑判断:如果是 A 请求就调 X 类的 M1 方法,如果是 B 请求就调 Y 类的 M2 方法。但这样做明显有问题,因为 HTTP 服务器的代码跟业务逻辑耦合在一起了,如果新加一个业务方法还要改 HTTP 服务器的代码。

怎么解决这个问题呢?面向接口编程是解决耦合问题的法宝,于是有一伙人就定义了一个接口,各种业务类都必须实现这个接口,这个接口就叫 Servlet 接口,有时我们也把实现了 Servlet 接口的业务类叫作 Servlet

对于特定的请求,HTTP 服务器如何知道由哪个 Servlet 处理呢?Servlet 又是由谁来实例化呢?显然 HTTP 服务器不适合做这个工作,否则又和业务类耦合了。

于是又发明了 Servlet 容器,Servlet 容器用来加载和管理业务类。HTTP 服务器不直接跟业务类打交道,而是把请求交给 Servlet 容器处理,Servlet 容器将请求转发到具体的 Servlet,如果这个 Servlet 还没创建,就加载并实例化这个 Servlet,然后调用这个 Servlet 的接口方法。因此 Servlet 接口其实是 Servlet 容器跟具体业务类之间的接口。下面我们通过一张图来加深理解。
在这里插入图片描述
图的左边表示 HTTP 服务器直接调用具体业务类,它们是紧耦合的。图的右边,HTTP 服务器不直接调用业务类,而是把请求交给容器,容器加载并调用业务类的Servlet接口方法。Servlet 接口和 Servlet 容器的出现,达到了 HTTP 服务器与业务类解耦的目的。

Servlet 接口和 Servlet 容器这一整套规范叫作 Servlet 规范。Tomcat 和 Jetty 都按照 Servlet 规范的要求实现了 Servlet 容器,同时它们也具有 HTTP 服务器的功能。作为 Java 程序员,如果我们要实现新的业务功能,只需要实现一个 Servlet,并把它注册到 Tomcat(Servlet 容器)中,剩下的事情就由 Tomcat 帮我们处理了

Servlet 接口

public interface Servlet {
    

    void init(ServletConfig config) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

最重要是的 service 方法,具体业务类在这个方法里实现处理逻辑。这个方法有两个参数:ServletRequest 和 ServletResponse。ServletRequest 用来封装请求信息,ServletResponse 用来封装响应信息,因此本质上这两个类是对通信协议的封装。

HTTP 协议中的请求和响应就是对应了 HttpServletRequestHttpServletResponse 这两个类。你可以通过 HttpServletRequest 来获取所有请求相关的信息,包括请求路径、Cookie、HTTP 头、请求参数等。此外,我在专栏上一期提到过,我们还可以通过 HttpServletRequest 来创建和获取 Session。而 HttpServletResponse 用来封装 HTTP 响应

接口中还有两个跟生命周期有关的方法 init 和 destroy,这是一个比较贴心的设计,Servlet 容器在加载 Servlet 类的时候会调用 init 方法,在卸载的时候会调用 destroy 方法。我们可能会在 init 方法里初始化一些资源,并在 destroy 方法里释放这些资源,比如 Spring MVC 中的 DispatcherServlet,就是在 init 方法里创建自己的 Spring 容器

ServletConfig 的作用就是封装 Servlet 的初始化参数。你可以在web.xml给 Servlet 配置参数,并在程序里通过 getServletConfig 方法拿到这些参数。

有接口一般就有抽象类,抽象类用来实现接口和封装通用的逻辑,因此 Servlet 规范提供了 GenericServlet 抽象类,我们可以通过扩展它来实现 Servlet。虽然 Servlet 规范并不在乎通信协议是什么,但是大多数的 Servlet 都是在 HTTP 环境中处理的,因此 Servet 规范还提供了 HttpServlet 来继承 GenericServlet,并且加入了 HTTP 特性。我们通过继承 HttpServlet 类来实现自己的 Servlet,只需重写两个方法:doGet 和 doPost

Servlet 容器

Servlet 容器是怎么工作的呢?接下来我会介绍 Servlet 容器大体的工作流程,一起来聊聊我们非常关心的两个话题:Web 应用的目录格式是什么样的,以及我该怎样扩展和定制化 Servlet 容器的功能

工作流程

当客户请求某个资源时,HTTP 服务器会用一个 ServletRequest 对象把客户的请求信息封装起来,Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet,如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet,并调用 Servlet 的 init 方法来完成初始化,接着调用 Servlet 的 service 方法来处理请求,把 ServletResponse 对象返回给 HTTP 服务器,HTTP 服务器会把响应发送给客户端。同样我通过一张图来帮助你理解。
在这里插入图片描述

Web 应用

Servlet 容器会加载并调用 Servlet接口方法,那 Servlet 是怎么注册到 Servlet 容器中的呢?一般来说,我们是以 Web 应用程序的方式来部署 Servlet 的,而根据 Servlet 规范,Web 应用程序有一定的目录结构,在这个目录下分别放置了 Servlet 的类文件、配置文件以及静态资源,Servlet 容器通过读取配置文件,就能找到并加载 Servlet。Web 应用的目录结构大概是下面这样的:

| - MyWebApp

  | -  WEB-INF/web.xml        -- 配置文件,用来配置Servlet等

  | -  WEB-INF/lib/          -- 存放Web应用所需各种JAR包

  | -  WEB-INF/classes/      -- 存放你的应用类,比如Servlet类

  | -  META-INF/              -- 目录存放工程的一些信息

Servlet 规范里定义了 ServletContext 接口来对应一个 Web 应用。Web 应用部署好后,Servlet 容器在启动时会加载 Web 应用,并为每个 Web 应用创建唯一的 ServletContext 对象。你可以把 ServletContext 看成是一个全局对象,一个 Web 应用可能有多个 Servlet,这些 Servlet 可以通过全局的 ServletContext 来共享数据,这些数据包括 Web 应用的初始化参数、Web 应用目录下的文件资源等。由于 ServletContext 持有所有 Servlet 实例,你还可以通过它来实现 Servlet 请求的转发。

扩展机制

引入 Servlet 规范后,你不需要关心 Socket 网络通信、不需要关心 HTTP 协议,不需要关心业务类是如何被实例化和调用的,因为这些都被 Servlet 规范标准化了,你只要关心怎么实现你的业务逻辑。这对于程序员来说是件好事,但也有不方便的一面。所谓规范就是说大家都要遵守,就会千篇一律,但是如果这个规范不能满足你的业务的个性化需求,就有问题了,因此设计一个规范或者一个中间件,要充分考虑到可扩展性。Servlet 规范提供了两种扩展机制:Filter 和 Listener

Filter 是过滤器,这个接口允许你对请求和响应做一些统一的定制化处理,比如你可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。过滤器的工作原理是这样的:Web 应用部署完成后,Servlet 容器需要实例化 Filter 并把 Filter 链接成一个 FilterChain。当请求进来时,获取第一个 Filter 并调用 doFilter 方法,doFilter 方法负责调用这个 FilterChain 中的下一个 Filter

Listener 是监听器,这是另一种扩展机制。当 Web 应用在 Servlet 容器中运行时,Servlet 容器内部会不断的发生各种事件,如 Web 应用的启动和停止、用户请求到达等。 Servlet 容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet 容器会负责调用监听器的方法。当然,你可以定义自己的监听器去监听你感兴趣的事件,将监听器配置在web.xml中。如 Spring 就实现了自己的监听器ContextLoaderListener,来监听 ServletContext 的启动事件,目的是当 Servlet 容器启动时,创建并初始化全局的 Spring 容器。缺省情况时在WEB-INF/applicationContext.xml文件找Spring的配置。通过定义一个元素名字为”contextConfigLocation”来改变Spring配置文件的位置(重命名applicationContext)。

最后我给你总结一下 Filter 和 Listener 的本质区别:Filter 是干预过程的,它是过程的一部分,是基于过程行为的。Listener 是基于状态的,任何行为改变同一个状态,触发的事件是一致的。

到这里相信你对 Servlet 容器的工作原理有了深入的了解,只有理解了这些原理,我们才能更好的理解 Tomcat 和 Jetty,因为它们都是 Servlet 容器的具体实现。后面我还会详细谈到 Tomcat 和 Jetty 是如何设计和实现 Servlet 容器的,虽然它们的实现方法各有特点,但是都遵守了 Servlet 规范,因此你的 Web 应用可以在这两个 Servlet 容器中方便的切换

解答

文章中提到 1.SpringMVC 容器实在DispatcherServlet中init方法里创建的。 2.Spring 容器是通过Listener创建的 a、就是说SpringMVC容器和Spring容器还不一样,那么他们是什么关系? b、他们和Servlet容器又是啥关系?

Tomcat&Jetty启动时给每个Web应用创建一个全局的上下文环境,这个上下文就是ServletContext,为后面的Spring容器提供宿主环境。
Tomcat&Jetty在启动过程中触发容器初始化事件,Spring的ContextLoaderListener会监听到这个事件,它的contextInitialized方法会被调用,在这个方法中,Spring会初始化全局的Spring根容器,这个就是Spring的IoC容器,IoC容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取。
Tomcat&Jetty在启动过程中还会扫描Servlet,一个Web应用中的Servlet可以有多个,以SpringMVC中的DispatcherServlet为例,这个Servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个Servlet请求。
Servlet一般会延迟加载,当第一个请求达到时,Tomcat&Jetty发现DispatcherServlet还没有被实例化,就调用DispatcherServlet的init方法,DispatcherServlet在初始化的时候会建立自己的容器,叫做SpringMVC容器,用来持有Spring MVC相关的Bean。同时,SpringMVC还会通过ServletContext拿到Spring根容器,并将Spring根容器设为SpringMVC容器的父容器,请注意,SpringMVC容器可以访问父容器中的Bean,但是父容器不能访问子容器的Bean,也就是说Spring根容器不能访问SpringMVC容器里的Bean。说的通俗点就是,在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象
https://blog.csdn.net/qq_35571554/article/details/82453684
https://www.runoob.com/servlet/servlet-tutorial.html

请问service方法为什么把request和response都当作输入参数,而不是输入参数只有request,response放到返回值里呢?

方便责任链模式下层层传递

老题,问下。springmvc如何实现url到方法的映射

通过扫描注解,将所有带有@Controller和@RequestMapping注解的类收集起来,统一保存到map里,请求来了通过查找这个map找到相应的类。

老师您好!请问您的置顶回复中“IoC容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取”,对于这句话我不是很理解,想请老师解答一下IoC容器是如何被存储到ServletContext中的,以及为什么要这么做,相应的源码在哪里看。非常感谢老师~

将全局的Spring容器做为ServletContext的一个属性,代码在ContextLoader类中的initWebApplicationContext方法:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.context);ServletContext就是用来共享数据的,比如SpringMVC需要从ServletContext拿到全局的Spring容器,把它设置成自己的父容器。

老师你好,请问在SpringBoot项目中,为什么没有web.xml了?

SpringBoot是以嵌入式的方式来启动Tomcat。对于SpringBoot来说,Tomcat只是个JAR包。
SpringBoot通过Servlet3.0规范中@WebServlet注解或者API直接向Servlet容器添加Servlet,无需web.xml。像纯手工打造Servlet那篇演示的那样。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/name_sakura/article/details/121744049

智能推荐

【信贷风控30分钟精通39】风控策略体系搭建2-程序员宅基地

文章浏览阅读938次,点赞23次,收藏21次。反欺诈策略是为防范恶意客户采取欺诈行为谋取利益而制订的策略,目的是通过对欺诈行为的识别,遏制欺诈风险,为金融机构止损。根据欺诈的不同维度,欺诈的分类目前,应对欺诈风险的有效措施包括反欺诈规则和反欺诈模型。

yum安装及配置_安装yum-程序员宅基地

文章浏览阅读10w+次,点赞40次,收藏332次。yum是用来管理rpm的,就跟maven管理jar包相似。yum源(库)分为本地库、网络库。首先要配置yum源,可支持多个源。先查看一下挂载情况:df -h这里我们要更换光盘,并挂载:mount /dev/cdrom /mnt(如果不能成功挂载,点击一下连接即可)之后再次使用 df -h命令,就能查看到光盘的内容。下面我们cd到 /mnt下查看一下:首先关注一下Pa..._安装yum

关于STM32 CAN的过滤器/滤波器_stm32can mailbox filter-程序员宅基地

文章浏览阅读3.8k次,点赞5次,收藏12次。1.在设置CanTxMsg.StdId时注意需要将其右移一位,比如如下滤波器配置:CAN_FilterInitStructure.CAN_FilterNumber=0;CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;CAN_FilterInitStructure.CAN_FilterScale=CAN_Filter..._stm32can mailbox filter

HDU 5119 Happy Matt Friends(动态规划)【状压基础类模板】_matt has n friends. they are playing a game togeth-程序员宅基地

文章浏览阅读373次。att has N friends. They are playing a game together. Each of Matt’s friends has a magic number. In the game, Matt selects some (could be zero) of his friends. If the xor (exclusive-or) sum of the selected friends’magic numbers is no less than M , Matt wi_matt has n friends. they are playing a game together.

vue3+vite+ts项目配置开发环境和生产环境 打包命令配置_vite打包配置-程序员宅基地

文章浏览阅读8.4k次,点赞6次,收藏29次。开发环境和生产环境的配置和打包方式有所不同,下面是基于vue3+vite+ts项目的开发环境和生产环境配置及打包方式的详细说明。打包完成后会在项目根目录下生成dist目录,里面包含了打包后的静态文件和index.html文件,可以直接部署到服务器上。这里配置了三个命令,分别是开发环境启动命令、开发环境打包命令和生产环境打包命令。1.3 配置.env.development。2.2 配置.env.production。1.2 配置vite.config.ts。2.1 配置vite.config.ts。_vite打包配置

(最新最详细)安装ubuntu18.04-程序员宅基地

文章浏览阅读2w次,点赞4次,收藏91次。目录1. window10中下载ubuntu镜像2. 制作U盘启动盘3. Ubuntu 分配硬盘空间1. window10中下载ubuntu镜像下载地址2. 制作U盘启动盘安装制作工具:UltraISO(点我下载),下载完成后安装插入用来做启动盘的U盘(最好是usb3.0接口,16GB或以上),并清空里面的文件打开安装好的UltraISO,点击继续试用按钮工作界面进入工作界面后,点击菜单栏文件(F),在弹出的选项卡里点击打开在弹出的文件选择对话框中找到下载好的 Ubuntu18.04._ubuntu18.04

随便推点

Linux 文件系统 EXT4 的前世今生-程序员宅基地

文章浏览阅读161次。在先前关于Linux文件系统的文章中,我写了一份说明书去介绍Linux文件系统,里面有一些高级的概念,比如说,一切都是文件。我很想去深入地讨论更多EXT文件系统的特性的信息。所以,首先让我们来回答这个问题:什么是文件系统?一个文件系统应该遵循以下特点: 1.数据存储:文件系统主要的功能是结构化存储和取回数据。 2.命名空间:提供一套命名和组织的方法,就是命名和结构化数据的规则。 3.安全模型:一种访问控制的策略。 4.API:系统操控文件系统对象的函数,就像操作文件夹...

别人家的公司:微软为员工发1500美元疫情奖金-程序员宅基地

文章浏览阅读308次。西雅图IT圈:seattleit【今日作者】Dexter读书巨慢理事会会长别人家的公司什么样?坐拥巨额现金流的微软,一言不合就发钱。01昨天微软首席人事官凯瑟琳霍根宣布——将向微软全球员工..._微软 西雅图 年底奖金

Ik分词器配置远程扩展字典_ik analyzer 扩展词典配置远程词典 可实时编辑-程序员宅基地

文章浏览阅读2k次。通过配置远程扩展词典,可以读取远程词典,当改变远程词典时,不必重启服务器,elasticsearch会自动加载并进行分词。步骤:配置文件服务器,把远程扩展词典放到服务器下。修改elasticsearch目录下plugins\ik\config\IKAnalyzer.cfg.xml文件并保存,如下: <properties> <comment>IK A..._ik analyzer 扩展词典配置远程词典 可实时编辑

分布式系列教程(11) -分布式协调工具Zookeeper(分布式锁实现)_分布式锁 的具体实现工具-程序员宅基地

文章浏览阅读553次,点赞2次,收藏2次。代码已提交至Github,有兴趣的同学可以下载来看看(git版本号:bea4d6f7ec9f7309033bcfa43316a660171ae5b6):https://github.com/ylw-github/Zookeeper-Demo本文目录结构:l____1. 知识点回顾l________1.1 多线程l________1.2 Java共享内存模型l____2. 分布式锁的解决方..._分布式锁 的具体实现工具

Nginx网站服务详解(Nginx服务的主配置文件 ——nginx.conf)-程序员宅基地

文章浏览阅读9.3k次,点赞9次,收藏51次。Nginx网站服务详解,Nginx服务的主配置文件,修改,监听,配置,密码认证,以及IP和端口虚拟主机配置方法,含图文步骤拆解讲解_nginx.conf

推荐文章

热门文章

相关标签