JavaWeb—Servlet基础(细节版,相当细节)

982 阅读10分钟

by:lubingyang

什么是Servlet?

Servlet就是一个普通的类,只不过这个类能够接受和处理请求,并且做出响应。提到Servlet就绕不开Servlet容器,那么什么又是Servlet容器呢?通俗的讲就是实现Servlet标准管理辅助Servlet类工作的工具。Servlet和Servlet容器在我看来就是子弹和枪的关系,通过对标准化接口的实现互相配合,彼此依存又独立发展。在大部分的情况下我们又称Servlet容器为服务器,常用的有Tomcat等。

一个HTTP来了又走经历了什么?

HTTP(超文本传输协议):是互联网通信的基础,属于 TCP/IP 模型中的应用层协议,当浏览器与服务器进行互相通信时,需要先建立TCP 连接,之后服务器才会接收浏览器的请求信息(request),当接收到信息之后,服务器返回相应的信息(response)。最后浏览器接受对服务器的信息应答后,对这些数据进行解释执行(解析HTML文件和各种资源进行展示)。

HTTP是一个无状态的连接协议。

所谓无状态:

根据早期的HTTP协议,每次request-reponse时,都要重新建立TCP连接。TCP连接每次都重新建立,所以服务器无法知道上次请求和本次请求是否来自于同一个客户端。因此,HTTP通信是无状态(stateless)的。服务器认为每次请求都是一个全新的请求,无论该请求是否来自同一地址。现在,虽然HTTP协议允许TCP连接复用,以节省建立连接所耗费的时间,但无状态的特性依旧被保留。

准备阶段

为了迎接HTTP的到来,首先我们需要有一个Servlet类,并且告诉Servlet容器自己的存在,这两个准备步骤就是创建Servlet类和写入配置文件

正如上文所讲,类和Servlet容器之间的配合是通过接口实现的,一个类只需要实现特定的接口,就可以称为一个Servlet类,并且能够被Servlet所接受,嗯,想来这就是接口的解耦和。

准备一个Servlet类

拥有一个Servlet类的三种方案

  1. 直接实现Servlet接口(interface)
  2. 继承GenericServlet类(abstract)
  3. 继承HttpServlet类(abstract)

在直接实现或者间接实现Servlet接口之后我们需要重写其中的service方法,到此Servlet就准备好了。

写入配置文件

配置文件是一个固定的写法,主要就是为了告诉Servlet容器自己在哪

<servlet>
	<servlet-name>自定义Servlet的别名</servlet-name>
  	<servlet-class>Servlet类的全类名</servlet-class>
</servlet>
<servlet-mapping>
	<servelt-name>自定义Servlet的别名</servelt-name>
  	<url-pattern>自定义路径</url-pattern>
</servlet-mapping>

接受请求

Servlet容器开启服务之后就可以迎接request的到来了,当这个HTTP请求到达Servlet容器(以Tomcat为例)的时候,Tomcat看到有HTTP来,就把它带到要去的那个地方(项目名),到了地点之后,Tomcat会拿出花名册(web.xml)让request挑一个(0.0)。

结果,不用挑有指定的,那就好办了。

Tomcat在部署文件中找 servlet-mapping 中与之匹配的 url-pattern,根据这个 url-pattern 的 servlet-name 映射到真正的 servlet-class ,然后调用相应的 Servlet 类。

扩展内容:xml

xml全称是Extensible Markup Language可扩展标记语言,看到这个难免会想起来HTML,他们有什么关系呢?为什么有了HTML语言还要xml语言呢?

认真的讲他们最大的关系就是都以ml结尾了,哈哈,开个玩笑。

HTML我们是很熟悉的,在使用的时候不难发现其中的标签都是定义好的,全世界的HTML文档用的是同一套标记的语法。

而xml更具有个性化,其中的标签不仅可以用别人的定义好的,也可以自己定义。书写一个xml有两个相关的规则,一个是标签规则,另一个就是校检规则,校检规则是用来告诉程序标签之间的规则,这个东西被称之为文档类型定义  Document Type Definition , 简称 DTD 这两个规则都是可以自定义的(所谓扩展),所以我们在书写不同的xml文件的时候,会发现标签规则是不一样的。

从这些层面上来说HTML语言也可以说是xml中的一种,HTML5就是最新的校检规则(不知道这么理解有没有问题???)

<!-- 我们不难发现很多xml文档的文档声明中都有声明其文档符合的校检规则 -->
<!-- web.xml -->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
XML Schema是dtd的改进版
<!-- mybatis-config.xml -->
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >

生命周期

通过以上的步骤Tomcat就找到了HTTP想要见到的那个Servlet了,但是这个类也许准备好了,也许没有,我们假定这个request是第一次来。这时候就开始了Servlet的生命周期了。

  1. 因为是第一次请求,Tomcat会调用Servlet类的无参构造方法,创建这个Servlet的对象。

  2. 之后初始化,会调用init方法,这个方法会对Servlet类做一些初始化的工作,需要注意的这个方法在Servlet的一生中只会执行这么一次。像初始化这么重要的事儿只进行一次是有现实意义的,毕竟如果可以多次的话,我早就一米八了。

  3. 初始化之后一个Servlet就正式的进入服务状态可以接客了,这时候就会调用service方法,接受HTTP的request,并对这个请求做一些服务项目,剪个头发之类啊,最后再把面目全非的请求送走,不,这时候应该叫响应response。听说每次剪头发都像整容,可惜好久没有剪过头发了。

    经过第一个请求之后,再有HTTP过来的时候,Servlet会直接调用service方法为其服务,毕竟谁一辈子也不能接一个客户初始化一次吧。

  4. 最后当服务关闭的时候,会销毁这个对象,在销毁前会调用destroy方法。

其他细节

会话跟踪技术

写到会话跟踪要先从HTTP开始说起,在之前我们说过HTTP是无状态的。因为其无状态的特性,服务器不能以状态来区分和管理请求和响应,而且一次请求响应之后就会断开连接,所以服务器也不需要保存状态信息,虽然这样简单不占资源,适用性广,但是不利之处在于我们没有办法根据HTTP本身对请求做一些区分。

所以为了在保留无状态协议这个特征的同时又解决类似记录状态的矛盾问题,出现了Cookie。

Cookie

cookie1.png

cookie2.png

从上图我们知道,有几个关键性的步骤是需要我们来做的:

  1. 创建Cookie

    //参数是cookie的标记和值,必须是英文
    Cookie cookie = new Cookie(flag,value);
    
  2. 响应信息中加上Cookie

    response.addCookie(cookie);
    
  3. 再次请求到来的时候检查Cookie

    //获取request中所有的cookie信息
    Cookies[] cookies = request.getCookies();
    //遍历检查cookie
    if(cookies != null){
      for(Cookie c : cookies){
      	String name = c.getName();
        String value = c.getValue();
    	}
    }
    

  1. cookie是有有效期的,一般会在浏览器关闭的时候自动清空

    设置cookie有效期,调用方法setMaxAge(60)

  2. cookie中的数据是不安全的,毕竟保存到本地,可以显式查看

Session

session是服务器为每一个浏览器建立的私人存储空间,其中(session作用域)可以存储浏览器的属性和一些配置信息,当浏览器拿到session之后(没有更换持有的session),在不同的Servlet之间跳转的时候,可以随时取出放在作用域中的数据。

session.png

上图是session的基本实现原理,我们可以看到session是通过cookie来实现的,具体的步骤是这样的:

  1. 浏览器把登录信息放入HTTP请求报文的实体部分,通常是以POST 方法把请求发送给服务器

  2. 服务器创建session,并将用户信息和session进行绑定记录在服务器,然后把处理好的session放入cookie中随着响应发给浏览器。

  3. 浏览器收到服务器响应的带有session信息的cookie时,会将cookie存在本地,下次请求的时候自动携带,服务器会通过接受其中的session对用户进行验证。

GET和POST的区别

从本质上来讲,区别只有两点:

  1. 参数的位置不同,GET是url后拼接参数,POST是请求报文主体传递参数
  2. 传递数据的过程不一样,GET产生一个TCP数据包,POST产生两个TCP数据包。

那么我们常提到的区别是从哪里来的呢?

从解释上面的区别开始。

早期的HTTP协议只有GET方法。根据HTTP协议,服务器接收到GET请求后,会将特定资源响应给浏览器,GET方法是通过改写URL实现的,在URL后面加上要传递的数据(格式是URL?key=value&key=value......)。所以在使用GET方法请求资源的时候,请求往往是没有主体的。

那么问题来了,什么是主体?

所谓主体就是request的报文主体,我们知道HTTP请求的报文格式(响应雷同)是这样的

l  起始行

l  头信息

l  主体

GET方法的请求报文信息一般只有起始行和头信息,如下:

<!-- 拼接后的url -->
http://localhost:8801/zhibaxm/servlet/LoginAction?username=%E5%BE%90%E9%80%9A%E8%BE%BE&pwd=123
<!-- 请求头 -->
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Host:localhost:8801
Origin:http://localhost:8801
Referer:http://localhost:8801/zhibaxm/login.html
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36

而对于POST方法,URL不再被改写,相关的表单数据会位于http请求的主体。如下:

<!-- url -->
http://localhost:8801/zhibaxm/servlet/LoginAction
<!-- 请求头 -->
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Host:localhost:8801
Origin:http://localhost:8801
Referer:http://localhost:8801/zhibaxm/login.html
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36
<!-- POST的主体信息 -->
username=%E5%BE%90%E9%80%9A%E8%BE%BE&pwd=123

我们知道每一个请求都是可以有三部分的:起始行,请求头,主体,也就是说,GET和POST的区别不是语法上的,而是规范上的,简单的说就是,你在使用POST的时候如果把参数写在url上也是没有问题的。

但是,我们在使用中确实有很多的不同,咋回事儿呢?这些区别并不是语法本身的不同,而是由于浏览器和服务器差异造成的使用上的区别,例如:大部分浏览器的url长度限制在2K个字节,而大部分服务器最多处理64K大小的url。在使用GET方法时,如果你在报文主体写入了数据,那么不同服务器的处理方式也是不同的,有些服务器会接受有些不会。

造成区别的原因更多不是来自语法本身,而是不同浏览器服务器的限制。

扯完了这些,补充一下应用上区别,毕竟遇到面试的时候,使用上的差别也是不能够忘记的,使用上的区别如下:

GET POST
只可以接受字符串 没有限制
不安全 相对安全
有长度限制 没有长度限制
... ...