手把手教你撸Servlet


前言

原文https://www.zhihu.com/question/21416727

Servlet在api文档里标注的清清楚楚,是一个Interface(接口)。

接口就是一堆(未实现)方法的集合。所以接口也叫规范或协议,并且Servlet这个规范是当初Sun公司提出来了,实现了这些方法,就写好了一个Servlet实现类。

Servlet本身在Tomcat中是属于"非常被动“的一个角色,处理的事情也很简单。网络请求和响应,不是它的主要职责,它其实更偏向于业务代码,所谓的Request和Response对象是Tomcat传给它,用来处理请求和响应的工具,但它本身不处理这些。

一、Servlet前世今生

所谓的Tomcat其实就是Web服务器和Servlet容器的结合体。

Tomcat才是与客户端直接打交道的家伙,他监听了端口,请求过来后,根据URL等信息,确定要将请求交给哪个servlet去处理,然后调用那个Servlet的service方法,service方法返回一个Response对象,Tomcat再把这个Response返回给客户端。

1. 什么是Web服务器

比如我当前在杭州,你能否用自己的电脑访问我桌面的一张图片?恐怕不行,我们习惯于通过URL访问一个网站。一个资源,如果没有URL映射,那么外界就很难访问。而Web服务器说白了就是:将某个主机上的资源映射为一个URL供外界访问。

2. 什么是Servlet容器

Servlet容器顾名思义,里面存放着Servlet对象, 我们为什么可以通过Web服务器映射的URL访问资源?肯定需要写程序处理需求,主要3个过程

  • 接收请求
  • 处理请求
  • 响应请求

任何一个程序,必然包括这三个步骤。 其中接受请求和响应请求是共性请求,且没有差异性。访问淘宝,接收taobao.com/brandNo=1,响应给浏览器的都是JSON数据。于是,大家就把接收和响应两个步骤抽取出Web服务器:
在这里插入图片描述
处理请求的逻辑是不同的。没关系,抽象出来做成Servlet,交给程序员自己编写。
随着后期互联网的发展,出现了三层架构,所以一些逻辑就从Servlet抽取出来,分担到service和dao。
在这里插入图片描述
但是Servlet并不擅长向浏览器输出HTML页面,所以就出现了JSP。
JSP的故事进入

等Spring家族出现之后,Servlet开始退居幕后,取而代之的是方便的SpringMVC。SpringMVC的核心组件DispatchServlet其实本质上就是一个Servlet。但它已经自立门户,在原来HttpServlet的基础上,又封装了一条逻辑。

二、JavaWeb三大组件

不知道从什么时候开始,我们不用再关心、甚至根本不知道到底是谁调用了我写的这个程序,反正我就写了一个类,甚至从来没有new过,它就跑起来了…

我们把模糊的记忆往前推一推,没错,就是在学了Tomcat之后!从Tomcat开始,我们就没有写main方法。以前,一个main方法启动,程序间调用井然有序,我们知道程序的所有流转过程。

但是到了JavaWeb后,Servlet/Filter/Listener一路下来我们越学越沮丧。没有main方法,也没有new,写一个类然后在web.xml中配置标签,它就自动运行了。

其实,这一切的一切,简单的来说就是”注入“和”回调“。

Tomcat里面有个main方法,假设是这样的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其实,编程学习越往后越是如此,我们能做的其实是有限。大部分工作,框架都帮我们做了,只要我们去实现xxx接口,他就会帮我们去创建实例, 然后搬运到它合适的地方,然后一套既定的流程下来。

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

三、如何编写Servlet

首先,我们心里必须有一个信念:我们都是菜鸡,框架肯定不会让我们写很难的代码。所以Servlet的实现肯定是很简单的!

查看接口方法:
在这里插入图片描述
五个方法,最难的地方在于形参,然而Tomcat会事先把形参对象封装好传给我…除此之外,既不需要我写TCP连接数据库,也不需要我解析HTTP请求,更不需要我把结果转成HTTP,Request和Response对象已经帮我们搞定了。

看吧,Tomcat是不是把我们当成智障啊。

Tomcat之所以放心的交给我们实现,是因为Servlet里主要写的代码都是业务逻辑代码。和原始的、底层的解析、连接等没有丝毫关系。最难的几个操作,人家已经给你封装成形参传进来了。

也就是说,Servlet虽然是个接口,但实现类只是个空壳,我们写点逻辑就好了。

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

  1. ServletConfig

    翻译过来就是Servlet配置。我们在哪里配置Servlet来着?web.xml嘛,请问你会用dom4j解析xml得到对象吗?
    可能…会吧,就是不熟练,嘿嘿嘿
    所以Tomcat 还真没错怪我们,已经帮"菜鸟"搞定了
    在这里插入图片描述
    也就是说,ServletConfig对象封装了Servlet的一些参数信息。如果需要,我们可以从它那里获取。

  2. Request/Response

    两位老朋友,不用多介绍了。也是题主最在意的网络请求相关的内容,其实是Tomcat处理的并封装好了的,不需要Servlet操心。很多人看待HTTP和Request/Response的眼光过于分裂。它们的关系就像菜园里的大白菜和餐桌上的酸辣白菜一样。HTTP请求到了Tomcat后,Tomcat通过字符串解析,把各个请求头(Header),请求地址(URL),请求参数(QueryString)都封装进了Request对象中。通过调用:

    	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等方法呢?

    我们来看一下源码:

    在这里插入图片描述
    浏览器页面会显示:405(http.method_get_not_supported)

    也就是说,HttpServlet虽然在service中帮我们写了请求方式的判断。但是针对每一种请求,业务逻辑代码是不同的,HttpServlet无法知晓子类想干嘛,所以就抽出七个方法,并且提供了默认实现:报405、400错误,提示请求不支持。

    但这种实现本身非常鸡肋,简单来说就是等于没有。所以,不能让它被实例化,不然调用doXxx方法是无用功。

    Filter用到了责任链模式,Listener用到了观察者模式,Servlet也不会放过使用设计模式的机会:模板方法模式。上面的就是。
    在这里插入图片描述

小结:

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

在这里插入图片描述

四、Tomcat启动原理

什么是容器?容器就是程序运行时需要的环境。Tomcat是servlet的运行环境,所以Tomcat是servlet容器。

那么Tomcat是怎样启动的呢?说到这里需要先介绍一下Servlet容器和Web容器的区别。Sevrlet容器是用来管理servlet的生命周期,而web容器,即web服务器是用来管理和部署Web应用的。Tomcat就是一个开源的Servlet容器,也是一个web容器—用于处理静态html,css等。接着介绍一下Tomcat的架构,如下图。
在这里插入图片描述

  1. server层代表整个servlet容器,用于启动与监听服务端事件

  2. service是由一个engine和一个或多个connector组成,这些connector共享一个Engine来处理请求

  3. connector将在某个指定端口监听客户的请求,把从socket传送来的数据封装成request传递给engine,并从engine处获得响应返回给客户。

    Tomcat通常会用到两种Connector:

    a) Http Connector 在端口8080处侦听来自客户browser的http请求。

    b) AJP Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。

  4. engine负责处理来自相关联的service的所有请求,处理后返回给service,connector作为两者中间媒介出现

    engine下可以配置多个虚拟主机,当engine获得一个请求时将这个请求匹配给对应的虚拟主机上处理

  5. Host虚拟主机与某个网络域名(domain name)相匹配。一个主机下可以部署一个或者多个web应用,每个应用对应一个context,有一个context path。当host获得一个请求时将这个请求匹配到某个context上,将请求交给context处理,这种方法叫最长匹配。path==“”时即匹配所有无法匹配到context的请求。

  6. context对应一个应用,由一个或多个servlet组成。context创建时将根据web.xml载入servlet类。

了解完tomcat的架构,然后介绍tomcat是怎样启动的。对于engine, host, context来说,它们都属于容器,当接收到客户端请求的时候,请求会被传递到容器中,在一个容器中处理完毕之后,会被传递给下一个容器处理。因此,我们可以这样理解tomcat,总的来说,tomcat就是一种自上而下,一个容器里面又嵌套包含了另一个子容器的结构。所以,在tomcat启动的时候,我们也可以想象,它必定要先启动父容器,然后再启动子容器,在启动每一层容器的时候,还会启动容器中的一些相关组件,当所有的容器与组件都安装启动完毕,那么tomcat就启动完毕了。

因此,很容易理解,tomcat 启动的第一步就是进行容器的装配,就是把父容器和子容器拼装起来,并且安装上相关的组件,这很像一个车间装配的过程。

当一切装配齐全,机器已经在各个工人的手中完全组装好了,那么接下来的一步,我们只需要按下开关,机器就可以工作啦。多么方便哪!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值