servlet的本质是什么,它是如何工作的?

论坛 期权论坛 期权     
匿名用户1024   2021-5-28 07:44   6176   5
servlet的本质是什么,它是如何工作的?基于计算机和网络通讯(主要是http协议)构建网络应用的,具体的实现细节是怎样的?
分享到 :
0 人收藏

5 个回复

倒序浏览
2#
有关回应  16级独孤 | 2021-5-28 07:44:17
很多人可能和我当初一样,把Servlet和太多东西联系起来。其实Servlet本身在Tomcat中是“非常被动”的一个角色,处理的事情也很简单。网络请求与响应,不是他的主要职责,它其实更偏向于业务代码。所谓的Request和Response是Tomcat传给它,用来处理请求和响应的工具,但它本身不处理这些。
文章会比较长,但是看完会拔高你看待Servlet的视角。
主要内容:
  • Servlet的前世今生
  • 我所理解的JavaWeb三大组件
  • 如何编写一个Servlet




[h1]Servlet的前世今生[/h1]类似于Servlet是Server Applet(运行在服务端的小程序)等其他博文已经提过的内容,这里就不重复了。它就是用来处理请求的业务逻辑的。
之前在Tomcat外传中我们聊过,所谓Tomcat其实是Web服务器和Servlet容器的结合体。
什么是Web服务器?
比如,我当前在杭州,你能否用自己的电脑访问我桌面上的一张图片?恐怕不行。我们太习惯通过URL访问一个网站、下载一部电影了。一个资源,如果没有URL映射,那么外界几乎很难访问。而Web服务器的作用说穿了就是:将某个主机上的资源映射为一个URL供外界访问。
什么是Servlet容器?
Servlet容器,顾名思义里面存放着Servlet对象。我们为什么能通过Web服务器映射的URL访问资源?肯定需要写程序处理请求,主要3个过程:
  • 接收请求
  • 处理请求
  • 响应请求
任何一个应用程序,必然包括这三个步骤。其中接收请求和响应请求是共性功能,且没有差异性。访问淘宝和访问京东,都是接收http://www.taobao.com/brandNo=1,响应给浏览器的都是JSON数据。于是,大家就把接收和响应两个步骤抽取成Web服务器:

所有题主问题描述的网络请求啥的,其实是Tomcat的工作但处理请求的逻辑是不同的。没关系,抽取出来做成Servlet,交给程序员自己编写。
当然,随着后期互联网发展,出现了三层架构,所以一些逻辑就从Servlet抽取出来,分担到Service和Dao。

JavaWeb开发经典三层架构:代码分层,逻辑清晰,还解耦但是Servlet并不擅长往浏览器输出HTML页面,所以出现了JSP。JSP的故事,我已经在怎样学习JSP?讲过了。
等Spring家族出现后,Servlet开始退居幕后,取而代之的是方便的SpringMVC。SpringMVC的核心组件DispatcherServlet其实本质就是一个Servlet。但它已经自立门户,在原来HttpServlet的基础上,又封装了一条逻辑。
总之,很多新手程序员框架用久了,甚至觉得SpringMVC就是SpringMVC,和Servlet没半毛钱关系。


[h1]我所理解的JavaWeb三大组件[/h1]不知道从什么时候开始,我们已经不再关心、甚至根本不知道到底谁调用了我写的这个程序,反正我写了一个类,甚至从来没new过,它就跑起来了...
我们把模糊的记忆往前推一推,没错,就是在学了Tomcat后!从Tomcat开始,我们再也没写过main方法。以前,一个main方法启动,程序间的调用井然有序,我们知道程序所有流转过程。
但是到了Javaweb后,Servlet/Filter/Listener一路下来我们越学越沮丧。没有main,也没有new,写一个类然后在web.xml中配个标签,它们就这么兀自运行了。
其实,这一切的一切,简单来说就是“注入”和“回调”。想象一下吧朋友们,Tomcat里有个main方法,假设是这样的:



其实,编程学习越往后越是如此,我们能做的其实很有限。大部分工作,框架都已经帮我们做了。只要我们实现xxx接口,它会帮我们创建实例,然后搬运(接口注入)到它合适的位置,然后一套既定的流程下来,肯定会执行到。我只能用中国一个古老的成语形容这种开发模式:闭门造车,出门合辙(敲黑板,成语本身是夸技术牛逼,而不是说某人瞎几把搞)。
很多时候,框架就像一个傀儡师,我们写的程序是傀儡,顶多就是给傀儡化化妆、打扮打扮,实际的运作全是傀儡师搞的。

了解到这个层面后,JavaWeb三大组件任何生命周期相关的方法、以及调用时Tomcat传入的形参,这里就不再强调。肯定是程序的某处,在创建实例后紧接着就传入参数调用了呗。没啥神秘的。




[h1]如何编写一个Servlet[/h1]首先,我们心里必须有一个信念:我们都是菜鸡,框架肯定不会让我们写很难的代码。
所以Servlet既然交给我们实现,肯定是很简单的!
(没有网络请求和响应需要我们处理,都封装好了!)
你别不服。进入Tomcat阶段后,我们开始全面面向接口编程。但是“面向接口编程”这个概念,最早其实出现在JDBC阶段。我就问你,JDBC接口是你自己实现的吗?别闹了,你导入MySQL的驱动包,它给你搞定了一切。
真正的连接过程太难写了,朋友们。底层就是TCP连接数据库啊,你会吗?写Socket,然后进行数据库校验,最后返回Connection?这显然超出我们的能力范围了。我们这么菜,JDBC不可能让我们自己动手的。所以各大数据库厂商体贴地推出了驱动包,里面有个Driver类,调用
  1. driver.connect(url, username, password);
复制代码
即可得到Connection。
BUT,这一次难得Tomcat竟然这么瞧得起我黄某 ,仅仅提供了javax.servlet接口,这是打算让我自己去实现?
不,不可能的,肯定是因为太简单了。
查看接口方法:

五个方法,最难的地方在于形参,然而Tomcat会事先把形参对象封装好传给我...除此以外,既不需要我写TCP连接数据库,也不需要我解析HTTP请求,更不需要我把结果转成HTTP响应,request对象和response对象帮我搞定了。
看吧,Tomcat是不是把我们当成智障啊。
Tomcat之所以放心地交给我们实现,是因为Servlet里主要写的代码都是业务逻辑代码。和原始的、底层的解析、连接等没有丝毫关系。最难的几个操作,人家已经给你封装成形参传进来了。
也就是说,Servlet虽然是个接口,但实现类只是个空壳,我们写点业务逻辑就好了。

总的来说,Tomcat已经替我们完成了所有“菜鸡程序员搞不定的骚操作”,并且传入三个对象:ServletConfig、ServletRequest、ServletResponse。接下来,我们看看这三个传进来都是啥。


ServletConfig
翻译过来就是“Servlet配置”。我们在哪配置Servlet来着?web.xml嘛。请问你会用dom4j解析xml得到对象吗?
可能...会吧,就是不熟练,嘿嘿嘿。
所以,Tomcat还真没错怪我们,已经帮“菜鸡们”搞掂啦:

也就是说,servletConfig对象封装了servlet的一些参数信息。如果需要,我们可以从它获取。


Request/Response
两位老朋友,不用多介绍了。也是题主最在意的网络请求相关的内容,其实是Tomcat处理的并封装好了的,不需要Servlet操心。很多人看待HTTP和Request/Response的眼光过于分裂。它们的关系就像菜园里的大白菜和餐桌上的酸辣白菜一样。HTTP请求到了Tomcat后,Tomcat通过字符串解析,把各个请求头(Header),请求地址(URL),请求参数(QueryString)都封装进了Request对象中。通过调用
  1. request.getHeader();request.getUrl();request.getQueryString();...
复制代码
等等方法,都可以得到浏览器当初发送的请求信息。
至于Response,Tomcat传给Servlet时,它还是空的对象。Servlet逻辑处理后得到结果,最终通过response.write()方法,将结果写入response内部的缓冲区。Tomcat会在servlet处理结束后,拿到response,遍历里面的信息,组装成HTTP响应发给客户端。

Servlet接口5个方法,其中init、service、destroy是生命周期方法。init和destroy各自只执行一次,即servlet创建和销毁时。而service会在每次有新请求到来时被调用。也就是说,我们主要的业务代码需要写在service中。
但是,浏览器发送请求最基本的有两种:Get/Post,于是我们必须这样写:

很烦啊。有没有办法简化这个操作啊?我不想直接实现javax.servlet接口啊。


于是,菜鸡程序员找了下,发现了GenericServlet,是个抽象类

我们发现GenericServlet做了以下改良:
  • 提升了init方法中原本是形参的servletConfig对象的作用域(成员变量),方便其他方法使用
  • init方法中还调用了一个init空参方法,如果我们希望在servlet创建时做一些什么初始化操作,可以继承GenericServlet后,覆盖init空参方法
  • 由于其他方法内也可以使用servletConfig,于是写了一个getServletContext方法
  • service竟然没实现...要它何用
放弃GenericServlet。
于是我们继续寻找,又发现了HttpServlet:

它继承了GenericServlet。
GenericServlet本身是一个抽象类,有一个抽象方法service。查看源码发现,HttpServlet已经实现了service方法:

好了,也就是说HttpServlet的service方法已经替我们完成了复杂的请求方法判断。
但是,我翻遍整个HttpServlet源码,都没有找出一个抽象方法。所以为什么HttpServlet还要声明成抽象类呢?
看一下HttpServlet的文档注释:

一个类声明成抽象方法,一般有两个原因:
  • 有抽象方法
  • 没有抽象方法,但是不希望被实例化
HttpServlet做成抽象类,仅仅是为了不让new。

所以构造方法不做任何事它为什么不希望被实例化,且要求子类重写doGet、doPost等方法呢?
我们来看一下源码:

protected修饰,希望子类能重写如果我们没重写会怎样?

浏览器页面会显示:405(http.method_get_not_supported)
也就是说,HttpServlet虽然在service中帮我们写了请求方式的判断。但是针对每一种请求,业务逻辑代码是不同的,HttpServlet无法知晓子类想干嘛,所以就抽出七个方法,并且提供了默认实现:报405、400错误,提示请求不支持。
但这种实现本身非常鸡肋,简单来说就是等于没有。所以,不能让它被实例化,不然调用doXxx方法是无用功。
Filter用到了责任链模式,Listener用到了观察者模式,Servlet也不会放过使用设计模式的机会:模板方法模式。上面的就是。这个模式我在JDBC的博文里讲过了:JDBC(中)

小结:
  • 如何写一个Servet?
  • 不用实现javax.servlet接口
  • 不用继承GenericServlet抽象类
  • 只需继承HttpServlet并重写doGet()/doPost()
  • 父类把能写的逻辑都写完,把不确定的业务代码抽成一个方法,调用它。当子类重写该方法,整个业务代码就活了。这就是模板方法模式

想看看Servlet与SpringMVC关系的朋友请戳:
bravo1988:Servlet(下)最近新写的小册,图文并茂,通俗易懂:
bravo1988:中级Java程序员如何进阶


3#
有关回应  16级独孤 | 2021-5-28 07:44:18
这个提问的最大一个bug,就是以为servlet是很复杂的东西,事实上,servlet就是一个Java接口,interface! 打开idea,ctrl + shift + n,搜索servlet,就可以看到是一个只有5个方法的interface!

所以,提问中说的网络协议、http什么的,servlet根本不管!也管不着!
那servlet是干嘛的?很简单,接口的作用是什么?规范呗!
servlet接口定义的是一套处理网络请求的规范,所有实现servlet的类,都需要实现它那五个方法,其中最主要的是两个生命周期方法 init()和destroy(),还有一个处理请求的service(),也就是说,所有实现servlet接口的类,或者说,所有想要处理网络请求的类,都需要回答这三个问题:
  • 你初始化时要做什么
  • 你销毁时要做什么
  • 你接受到请求时要做什么
这是Java给的一种规范!就像阿西莫夫的机器人三大定律、行尸走肉里Rick的那三个问题一样,规范!
servlet是一个规范,那实现了servlet的类,就能处理请求了吗?
答案是,不能。
你可以随便谷歌一个servlet的hello world教程,里面都会让你写一个servlet,相信我,你从来不会在servlet中写什么监听8080端口的代码,servlet不会直接和客户端打交道!
那请求怎么来到servlet呢?答案是servlet容器,比如我们最常用的tomcat,同样,你可以随便谷歌一个servlet的hello world教程,里面肯定会让你把servlet部署到一个容器中,不然你的servlet压根不会起作用。
tomcat才是与客户端直接打交道的家伙,他监听了端口,请求过来后,根据url等信息,确定要将请求交给哪个servlet去处理,然后调用那个servlet的service方法,service方法返回一个response对象,tomcat再把这个response返回给客户端。
以上。
参考文献(看完你就知道Tomcat/Servlet/Spring MVC之间的矫情关系了):
An Introduction to Tomcat Servlet InteractionsHow Spring MVC Really Works公众号:柳树的絮叨叨  ,欢迎关注!
有些话只能在那里跟你说 (〃'▽'〃)

柳树的絮叨叨
4#
有关回应  16级独孤 | 2021-5-28 07:44:19
Servlet是J2EE 规范中的一种,主要是为了扩展java作为web服务的功能.J2EE 从92年到的J2EE 1.2到现在J2EE8 从12个规范到现在20多个规范,越来越完善
他的作用就是为java程序提供一个统一的web应用的规范,方便程序员统一的使用这种规范来编写程序,应用容器可以使用提供的规范来实现自己的特性。比如tomcat的代码和jetty的代码就不一样是吧,但作为程序员你只需要了解servlet规范就可以从request中取值,你可以操作session等等。不用在意应用服务器底层的实现的差别而影响你的开发
当然你也可以自己写一个http 服务器,自己定义一套API,比如你在底层接受到一个http请求后,你把这个http请求的header、cookie和param等封装成一个MyRequest.class 。然后你要得到,你在你的MyServlet中从MyRequest对象中拿到param请求参数,校验成功后需要返还给浏览器一个HTTP response。其中必须要有一个session,所以你往cookie中写了一个字段,LAOZIDESESSIONID=878361839QWQWEQEQE,同时把这个sessionid放在了自己的内存中。下一次浏览器再访问你就会带上这个LAOZIDESESSIONID这个cookie,你就知道他原来已经访问过了,而且上一次访问的数据你都有(在第一次保存在内存中)
但是有没有想过,如果每个程序员都写一个自己的HTTP服务器,该程序员离职了咋办。而且你用你的,我用我的,遇到问题都不能一起解决,你一会儿只支持http/1.0 ,别人都支持http/2.0了(虽然这个是在底层的实现了,和servlet没半毛钱关系,大家注意了,打个比方而已)。别人都支持注解了,你还在写配置呢!肯定不能啊,所有J2EE要出一个规范,要管住你们这群人,大家都要同步走。大家都用我这套规范,所有的请求都放在Request中,返回都放在response中。sessionID的名称也都可以自己设置,
比如tomcat你可以


讲了这么多废话,总结来说Servlet就是一群人来制定java应用中使用web时的各种规范,统一接口,其他内部实现由厂商自己实现,tomcat jetty jboss等等应运而生。面向接口编程!!很熟悉吧
关于他如何工作的:一个http请求到来,容器将请求封装成servlet中的request对象,在request中你可以得到所有的http信息,然后你可以取出来操作,最后你再把数据封装成servlet的response对象,应用容器将respose对象解析之后封装成一个http response。完了
5#
有关回应  16级独孤 | 2021-5-28 07:44:20
Servlet(Server Applet),全称Java Servlet,未有中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。
Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
最早支持Servlet标准的是JavaSoft的Java Web Server,此后,一些其它的基于Java的Web服务器开始支持标准的Servlet。

工作模式:
1、客户端请求该 Servlet;
2、加载 Servlet 类到内存;
3、实例化并调用init()方法初始化该 Servlet;
4、service()(根据请求方法不同调用doGet() 或者 doPost(),此外还有doHead()、doPut()、doTrace()、doDelete()、doOptions());
5、destroy();
6、加载和实例化 Servlet。这项操作一般是动态执行的。然而,Server 通常会提供一个管理的选项,用于在 Server 启动时强制装载和初始化特定的 Servlet;
7、Server 创建一个 Servlet的实例;
8、第一个客户端的请求到达 Server;
9、Server 调用 Servlet 的 init() 方法(可配置为 Server 创建 Servlet 实例时调用,在 web.xml 中  标签下配置  标签,配置的值为整型,值越小 Servlet 的启动优先级越高);
10、一个客户端的请求到达 Server;
11、Server 创建一个请求对象,处理客户端请求;
12、Server 创建一个响应对象,响应客户端请求;
13、Server 激活 Servlet 的 service() 方法,传递请求和响应对象作为参数;
14、service() 方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息;
15、service() 方法使用响应对象的方法,将响应传回Server,最终到达客户端。service()方法可能激活其它方法以处理请求,如 doGet() 或 doPost() 或程序员自己开发的新的方法;
16、对于更多的客户端请求,Server 创建新的请求和响应对象,仍然激活此 Servlet 的 service() 方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用 init() 方法。一般 Servlet 只初始化一次(只有一个对象),当 Server 不再需要 Servlet 时(一般当 Server 关闭时),Server 调用 Servlet 的 destroy() 方法。

6#
有关回应  16级独孤 | 2021-5-28 07:44:21
web服务器习惯处理静态页面,所以需要一个程序来帮忙处理动态请求(如当前时间)。Web服务器程序会将动态请求转发给帮助程序,帮助程序处理后,返回处理后的静态结果给web服务器程序。这样就避免了web服务器程序处理动态页面。Servlet的本质是一个帮助程序。如下图


Servlet工作流程分为三个阶段。init(初始化),service(运行),destroy(销毁)
Servlet没有main方法,所有行为由Container控制。Container就是一个java程序。
在加载Servlet的.class后,Servlet会由构造函数生成一个实例,然后Container调用init()方法完成参数的初始化,接着调用service()方法,service会根据网页的请求,调用doGet或者doPost方法,最后调用销毁方法。整个流程如下图:

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:136515
帖子:27303
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP