本文共 3016 字,大约阅读时间需要 10 分钟。
Servlet的作用
servlet本质上是一个接口,它压根就不管也管不着哪些网络协议、http。
那servlet到底是干什么的呢?很简单,接口的作用是什么?规范呗!
servlet接口定义的是一套处理网络请求的规范,所有实现servlet的类,都需要实现它的那五个方法,其中最主要的是两个声明周期方法init()和destory(),还有一个处理请求的service()。也就是说,所有实现servlet接口的类,或者说,所有想要处理网络请求的类,都需要回答这三个问题:
- 你初始化时要做什么?
- 你销毁时要做什么?
- 你接收到请求时要做什么?
这是Java给的一种规范!就像阿西莫夫的机器人三大定律、行尸走肉里Rick的那三个问题一样,规范!
servlet是一个规范,那实现了servlet的类,就能处理请求了吗?
-
不能。你从来不会在servlet中写什么监听8080端口的代码,servlet不会直接和客户端打交道!
-
那请求是怎么来到servlet的呢?答案是servlet容器,比如我们最常用的Tomcat。servlet都是部署在一个容器中的,不然你的servlet根本不起作用。
-
Tomcat才是与客户端直接打交道的家伙,它监听了端口,请求过来后,根据URL等信息,确定要将请求交给哪个servlet去处理,然后调用那个servlet的service方法,service方法返回一个response对象,Tomcat再把这个respond返回给客户端。
Servlet本身在Tomcat中是“非常被动”的一个角色,处理的事情也很简单。网络请求与响应,不是它的主要职责,它其实更偏向于业务代码。所谓的request和respond是Tomcat传给它,用来处理请求和响应的工具,但它本身不处理这些。
Servlet的前世今生
所谓Tomcat其实是Web服务器和Servlet容器的结合体。
(1) Web服务器的所做的工作本质上是:
- 将某个主机上的资源映射为一个URL供外界访问
(2) 那什么是servlet容器呢?
- servlet容器,顾名思义里面存放的是servlet对象。
- 我们为什么能通过web服务器映射的URL访问到资源呢?肯定需要写程序处理请求,主要三个过程
- 接收请求
- 处理请求
- 响应请求
- 任何一个应用程序,必然包括这三个步骤。其中接收请求和响应请求是共性功能,而且没有差异性。于是,就可以把接收和响应两个步骤抽取成web服务器。
- 但处理请求的逻辑是不同的。没关系,抽取出来做成servlet,交给程序员自己编写
当然,随着互联网的发展,出现了三次架构,所以一些逻辑就从servlet抽取出来,分担到service和dao
但是servlet并不擅长往浏览器输出HTML页面,所以就出现了JSP。等Spring家族出现后,Servlet开始退居幕后,取而代之的是SpringMVC。SpringMVC的核心组件DispacterServlet其实本质是一个Servlet,但是它已经自立门户,在原来HttpServlet的基础上,有封装了一层逻辑。
如何编写一个Servlet
查看接口方法:
五个方法,最难的地方在于形参,然而Tomcat会事先把形参对象封装好传给我们。除此之外,既不需要我们写TCP连接,也不需要我们解析HTTP请求,更不需要我们把结果转换成HTTP响应,request对象和response对象帮我们搞定了。在Servlet里面主要写的代码都是业务逻辑,和原始的、底层的解析、连接等没有丝毫关系。最难的几个操作,人家已经给你封装成形参传进来了。
也就是说servlet虽然是个接口,但是实现类只是个空壳,我们写点业务逻辑就好了
总的来说,Tomcat已经帮我们完成了底层的操作,并且传入了三个对象:ServletConfig、ServletRequest、ServletResponse。ServletConfig
ServletConfig即“servlet配置”,就是我们在web.xml中配置的servlet。它封装了servlet的一些参数信息。如果需要,我们可以从它获取。
Request/Response
这是接收请求和发送请求的类,Tomcat已经处理并封装好了,不需要servlet操心。HTTP请求到达Tomcat之后,Tomcat通过字符串解析,把各个请求头(Header)、请求地址(URL)、请求参数等都封装进Request对象中。通过调用
等方法,就可以得到浏览器当初发送的请求信息。至于Response,Tomcat传给Servlet时,它还是空的对象。Servlet逻辑处理后得到结果,最终通过response.write()方法,将结果写入response内部的缓冲区。Tomcat会在servlet处理结束后,拿到response,遍历里面的信息,组装成HTTP响应发给客户端。
Servlet接口5个方法,其中init、service、destory是声明周期方法。init和destory各自只执行一次,即servlet创建和销毁时。而service会在每次有新请求到来时被调用。也就是说,我们主要的业务代码需要写在service中。但是,浏览器发送请求基本的有两种:GET/POST。于是我们必须这样写
那能不能简化呢?
于是我们发现了一个GenericServlet,是个抽象类。
作用- 提升了init方法中原本是形参的servletConfig对象的作用域,方便其他方法使用
- init方法中还调用了一个init空参方法,如果我们希望在servlet创建时做一些什么初始化操作,可以继承GenericServlet后,覆盖init空参方法
- 由于需要其他方法也可以使用servletConfig,于是写了一个getServletConext。
但是service()方法没有实现。
于是又引入了一个HttpServlet
它继承了GenericServlet。GenericServlet本身是一个抽象类,有一个抽象方法service。实现如下:
也就是说,HttpServlet的service方法已经替我们完成了复杂的请求方法判断。问题是HttpServlet为什么要声明成抽象类呢?它的文档中注释了:
一个类声明成抽象方法,一般有两个原因:- 有抽象方法
- 没有抽象方法,但是不希望被实例化
HTTPServlet做成抽象类,仅仅是为了不让new
它为什么不希望被实例化,而且要求子类重写doGet、doPost等方法呢?
我们来看一下源码:
如果我们没有重写会怎么样?
浏览器页面会显示405(http.method_get_not_supported)也就是说,HttpServlet虽然在service中帮我们写了请求方式的判断。但是针对每一种请求,业务逻辑代码都是不同的,HttpServlet无法知晓子类想干嘛,所以就抽象出了7个方法,并且提供了默认实现:报405、400错误,提示请求不支持。
但是这种实现本身非常。。。简单来说等于没有。所以不能让它被实例化,不然调用doXXX方法是无用功。
这就是模板方法模式:父类把能写的逻辑都写完,把不确定的业务代码抽象成一个方法,调用它。当子类重写该方法,整个业务代码就活了
转载地址:https://blog.csdn.net/zhizhengguan/article/details/123125285 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!