---
title: 一文搞懂java Servlet
date: 2020-12-12 13:49:39
tags:
- JavaWeb
- servlet
- session
- cookie
categories:
- JavaWeb
---

## servlet简介

Servlet（Server Applet），全称Java Servlet，是用Java编写的，<font color=red>运行在 Web 服务器或应用服务器上的程序，它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。</font>其主要功能在于交互式地浏览和修改数据，生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口，广义Servlet是指任何实现了这个Servlet接口的类，一般情况下，人们将Servlet理解为后者。

<!--more-->

下图显示了 Servlet 在 Web 应用程序中的位置。

![](https://i.loli.net/2020/11/26/sl8OL1tcj4WDmfN.png)

## Servlet工作模式：

① 客户端发送请求至服务器

*   这些请求可以是显式的数据，包括网页上的 HTML 表单，或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单
*   也可以是隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。

② 服务器启动并调用Servlet，Servlet根据客户端请求生成响应内容并将其传给服务器

③ 服务器将响应返回客户端，同第①个，这些响应也可以是显式的数据或隐式的数据

## Servlet工作原理：

(1)Servlet接口定义了Servlet与servlet容器之间的契约。这个契约是：Servlet容器将Servlet类载入内存，并产生Servlet实例和调用它具体的方法。<font color=red>在一个应用程序中，每种Servlet类型只能有一个实例</font>。

(2)用户请求使Servlet容器调用Servlet的Service()方法，并传入一个ServletRequest对象和一个ServletResponse对象。这些对象都是由Servlet容器（例如TomCat）封装好的，并不需要程序员去实现，程序员可以直接使用。

*   ServletRequest中封装了当前的Http请求，ServletResponse表示当前用户的Http响应

(3)对于每一个应用程序，Servlet容器还会创建一个ServletContext对象。这个对象中封装了上下文（应用程序）的环境详情。<font color=red>每个应用程序只有一个ServletContext</font>。每个Servlet容器也都有一个封装Servlet配置的ServletConfig对象。

## Servlet的使用

Java Servlet 是运行在带有支持 Java Servlet 规范的解释器的 web 服务器上的 Java 类。Servlet 可以使用 **javax.servlet** 和 **javax.servlet.http** 包创建，它是 Java 企业版的标准组成部分。它们之间的关系如下图所示：

![](https://i.loli.net/2020/11/26/1CgmXzM8NBK4vcx.png)

### 使用

创建一个类**实现Servlet接口**，重写其中的方法

```java
public class LoginServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //初始化方法
    }
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
    @Override
    public void service(ServletRequest res, ServletResponse resp) throws ServletException, IOException {
        //处理get/post请求的方法
    }
    @Override
    public String getServletInfo() {
        return null;
    }
    @Override
    public void destroy() {
        //销毁的方法
    }
}
```

**继承HttpServlet类（常用）**

```java
public class LoginServlet implements Servlet {
    @Override
    public void doGet(HttpServletRequest res, HttpServletResponse resp) throws ServletException, IOException {
        //处理get请求的方法
    }
    @Override
    public void doPost(HttpServletRequest res, HttpServletResponse resp) throws ServletException, IOException {
        //处理get请求的方法
    }
   
}
```

### 部署配置

容器（Tomcat）在得到客户端的请求后，但不知道去交给哪一个servlet去处理，所以需要进行部署，有两种方式：

**方式一：web.xml文件部署Servlet的映射关系**

```xml
<servlet>
    <servlet-name>自定义名称</servlet-name>
    <servlet-class>处理请求的类的完整路径</servlet-class>
</servlet>
<servlet-mapping><!-- mapping 表示映射 -->
    <servlet-name>自定义名称</servlet-name>
    <url-pattern>请求名</url-pattern>
</servlet-mapping>
```

>   这个xml标签的执行顺序：
>
>   请求过来以后->web.xml->servlet-mapping标签中的url-pattern标签中的内容和请求名进行匹配->匹配成功后找对应的servlet-mapping标签中的servlet-name->去servlet标签中找和上一个servlet-name相同的name值->去找servlet标签中的servlet-class中处理类的完整路径。

**方式二：@WebServlet注解**

**注解这个方式是Servlet3.0版本后才支持的**，它里面可以添加如下属性

| 属性名               | 类型           | 描述                                                         |
| -------------------- | -------------- | ------------------------------------------------------------ |
| name                 | String         | 指定Servlet 的 name 属性，默认为类的全限定名                 |
| value或者urlPatterns | String         | 指定Servlet处理的url。等价于web.xml中的`<url-pattern>`标签。 |
| asyncSupported       | boolean        | 指定Servlet是否支持异步操作模式                              |
| displayName          | String         | 指定servlet的显示名                                          |
| initParams           | webInitParam[] | 配置初始化参数                                               |
| loadOnStartup        | int            | 指定servlet的加载顺序，默认不配置或数值为负数时表示客户端第一次请求Servlet时再加载；0或正数表示启动应用就加载，正数情况下，数值越小，加载该Servlet的优先级越高； |

例如：

```java
@WebServlet(value = "/login")
public class LoginServlet implements Servlet  {
	//code
}
```

## Servlet的生命周期

当客户端首次发送第一次请求后，由容器(web服务器(tomcat))去解析请求，根据请求找到对应的servlet，判断该类的对象是否存在，不存在则创建servlet实例，调取init()方法进行初始化操作，初始化完成后调取service()方法，由service()判断客户端的请求方式，如果是get，则执行doGet()，如果是post则执行doPost()。处理方法完成后，作出响应结果给客户端。单次请求处理完毕。

当用户发送第二次以后的请求时，会判断对象是否存在，但是不再执行init()，而直接执行service方法，调取doGet()/doPost()方法。

当服务器关闭时调取destroy()方法进行销毁。

![](https://i.loli.net/2020/11/26/5sKVlqR1SDomOpF.png)

##  Servlet接收/响应请求

客户端发送数据给服务器端的请求方式：

| 请求方式           | 请求类型     | 举例                                            |
| ------------------ | ------------ | ----------------------------------------------- |
| 通过form表单       | get/post提交 | < form action="请求" method="get/post">< /form> |
| 通过a标签发送数据  | get提交      | < a href = "请求">< /a>                         |
| 通过地址栏直接拼接 | get请求      | url/请求?key=value&key=value                    |
| js提交数据         | get请求      | location.href=“目标请求?key=value&key=value”    |

>   **get/post请求的比较：**
>
>   1.  地址栏呈现
>
>       *   GET请求：请求的数据会附加在URL之后，以`?`分割URL和传输数据，多个参数用`&`连接。
>       *   POST请求：POST请求会把请求的数据放置在HTTP请求包的包体中。
>
>       因此，GET请求的数据会暴露在地址栏中，而POST请求则不会。
>
>   2.  传输数据的大小
>
>       *   在HTTP规范中，没有对URL的长度和传输的数据大小进行限制。但是在实际开发过程中，对于GET，特定的浏览器和服务器对URL的长度有限制。因此，在使用GET请求时，传输数据会受到URL长度的限制。
>       *   对于POST，由于不是URL传值，理论上是不会受限制的，但是实际上各个服务器会规定对POST提交数据大小进行限制，Apache、IIS都有各自的配置。
>
>   3.  安全性
>
>       POST的安全性比GET的高。比如：在进行登录操作，通过GET请求，用户名和密码都会暴露再URL上，因为登录页面有可能被浏览器缓存以及其他人查看浏览器的历史记录的原因，此时的用户名和密码就很容易被他人拿到。除此之外，GET请求提交的数据还可能会造成Cross-site request frogery攻击

HttpServletRequest和HttpServletResponse对象中分别封装了当前的Http请求和响应，它们中都有一些方法来处理请求和响应。

HttpServletRequest类常用方法：

| 修饰              | 方法名                               | 描述                                                         |
| ----------------- | ------------------------------------ | ------------------------------------------------------------ |
| String            | getParameter(String name)            | 根据表单组件名称获取提交数据，返回值是String（服务器在接收数据时使用字符串统一接收） |
| String[]          | getParameterValues(String name)      | 获取表单组件对应多个值时的请求数据                           |
| void              | setCharacterEncoding(String charset) | 指定每个请求的编码(针对post请求才起作用)                     |
| RequestDispatcher | getRequestDispatcher(String path)    | **转发**,返回一个RequestDispatcher对象，该对象的forward( )方法用于转发请求 |
| Session           | getSession()                         | 取得当前会话的Session对象                                    |
| void              | setAttribute(“key”,value)            | 存值                                                         |
| void              | getAttribute(“key”)                  | 取值，需要向下转型                                           |

HttpServletResponse类常用方法：

| 修饰        | 方法名                    | 描述                                                         |
| ----------- | ------------------------- | ------------------------------------------------------------ |
| void        | addCookie(Cookie var1)    | 给这个响应添加一个cookie                                     |
| void        | sendRedirect(String var1) | **重定向**，发送一条响应码，将浏览器跳转到指定的位置         |
| PrintWriter | getWriter()               | 获得字符流，通过字符流的write(String s)方法可以将字符串设置到response 缓冲区中，随后Tomcat会将response缓冲区中的内容组装成Http响应返回给浏览器端，可以返回html/js代码等 |
| void        | setContentType()          | 设置响应内容的类型                                           |

>   **重定向与转发的区别**
>
>   转发：`httpServletRequest.getRequestDispatcher("目标地址").forward(传递参数)`
>
>   重定向：`httpServletResponse.sendRedirect("目标地址")`
>
>   相同点：都用来跳转页面
>
>   不同点：
>
>   *   重定向时地址栏会改变，request中存储的数据会丢失。转发时地址栏显示的是请求页面的地址，request数据可以保存。
>
>   *   转发属于一次请求一次响应，重定向属于两次请求(地址栏修改了两次)两次响应。
>
>   ![](https://i.loli.net/2020/11/26/qinVUdSPcCf4uvb.png)

## 会话（Session和Cookie）

会话的概念：从打开浏览器到关闭浏览器,期间访问服务器就称为一次会话

会话跟踪是Web程序中常用的技术，用来跟踪用户的整个会话。保持对用户会话期间的数据管理。常用的会话跟踪技术是Cookie与Session。

*   Cookie通过在客户端记录信息确定用户身份
*   Session通过在服务器端记录信息确定用户身份。

###  Session

**session会话的数据可以在多个页面中共享,即使重定向页面,数据不会丢失**

**Session常用方法：**

| 修饰        | 方法名                                | 描述                                                         |
| ----------- | ------------------------------------- | ------------------------------------------------------------ |
| void        | setAttribute(String key,Object value) | 以key/value的形式保存对象值,将数据存储在服务器端             |
| Object      | getAttribute(String key)              | 通过key获取对象值                                            |
| Enumeration | getAttributeNames()                   | 返回Session中存在的属性名(key)                               |
| long        | getLastAccessedTime()                 | 返回Session的最后活跃时间。                                  |
| long        | getCreationTime()                     | 返回Session的创建日期。返回类型为long，常被转化为Date类型，例如：Date createTime = new Date(session.get CreationTime()) |
| void        | invalidate()                          | 设置session对象失效                                          |
| String      | getId()                               | **获取sessionid,当第一次登录成功后，session会产生一个唯一的id**，浏览器之后访时如果发现id值还是之前id，那么说明 当前访问的属于同一个会话 |
| void        | setMaxInactiveInterval(int interval)  | 设定session的有效时间，单位为s秒，默认的有效时间:30分钟      |
| int         | getMaxInactiveInterval()              | 获取session的有效时间(以秒为单位)                            |
| boolean     | isNew()                               | 返回该Session是否是新创建的                                  |
| void        | removeAttribute(String key)           | 从session中删除指定名称(key)所对应的对象                     |

**Session的生命周期**

Session保存在服务器端。**为了获得更高的存取速度，服务器一般把Session放在内存**里。每个用户都会有一个独立的Session。如果Session内容过于复杂，当大量客户访问服务器时可能会导致内存溢出。因此，Session里的信息应该尽量精简。

Session在用户第一次访问服务器的时候自动创建。需要注意**只有访问JSP、Servlet等程序时才会创建Session，只访问HTML、IMAGE等静态资源并不会创建Session**。如果尚未生成Session，也可以用`request.getSession(true)`强制生成Session。

Session生成后，只要用户继续访问，服务器就会更新Session的最后访问时间，并维护该Session。用户每访问服务器一次，无论是否读写Session，服务器都认为该用户的Session“活跃（active）”了一次。

由于会有越来越多的用户访问服务器，因此Session也会越来越多。为防止内存溢出，服务器会把长时间内没有活跃的Session从内存删除。这个时间就是**Session的超时时间**。如果超过了超时时间没访问过服务器，Session就自动失效了。

Session的超时时间为`maxInactiveInterval`属性，可以通过对应的`getMaxInactiveInterval()`获取，通过`setMaxInactiveInterval(long interval)`修改。Session的超时时间也可以在web.xml中修改。另外，通过调用Session的`invalidate()`方法可以使Session失效。

```xml
<session-config>
	<session-timeout>30</session-timeout>  //30分钟
</session-config>
```

### Cookie

Cookie是客户端(一般指浏览器)请求服务器后,服务器发给客户端的一个辨认标识，保存在客户端，当客户端再次向服务器发送请求时，会携带着这个辨认标识，服务器就可以通过这个标识来识别客户端的身份或状态等。

**Cookie的设置和获取**

通过HttpServletResponse.addCookie的方式设置Cookie

```java
Cookie cookie = new Cookie("jieguo","true");
response.addCookie(cookie);
```

服务端获取客户端携带的cookie：通过HttpServletRequest获取

```java
Cookie[] cookies = request.getCookies();
if(cookies != null)
    for(Cookie c : cookies){
        String name = c.getName();//获取Cookie名称
        if("jieguo".equals(name)){
            String value = c.getValue();//获取Cookie的值
            bool = Boolean.valueOf(value);//将值转为Boolean类型	
        }
	}
}
```

**删除Cookie**

通过设置同名Cookie的最大存活时间为0，删除Cookie是指使浏览器不再保存Cookie，使Cookie立即失效

举例：使name为username的Cookie立即失效

```java
//1.创建一个name为username的Cookie
Cookie cookie = new Cookie("username", "aaa");
//2.设置Cookie的有效时间为0
cookie.setMaxAge(0);//删除cookie的关键
//3.将cookie发送给浏览器，来替换同名Cookie
response.addCookie(cookie);
```

**Cookie的有效时间**

Cookie发送给浏览器以后，浏览器并不会永久保存，也就是到了一定的时间以后浏览器会自动销毁Cookie。
Cookie的默认有效时间为一次会话(一次打开关闭浏览器的过程)，我们也可以手动指定Cookie的有效时间

```java
//setMaxAge用来设置Cookie的最大有效时间，需要int型的参数，代表有效的秒数
cookie.setMaxAge(秒数)；
    //当参数大于0时，会设置为指定的秒数
    cookie.setMaxAge(30);
    //当参数等于0时,浏览器不会保存Cookie,Cookie立即失效
    cookie.setMaxAge(0);
    //当参数小于0时，和不设置是一样，当前会话有效
    cookie.setMaxAge(-100);
//设置一个永久有效的Cookie，并非永久，只是使Cookie的生命很长而已
cookie.setMaxAge(60*60*24*365*10);
```

>   **Session和Cookie的区别**
>
>   *   Cookie数据保存在客户端，Session数据保存在服务器端。
>   *   Session是由应用服务器维持的一个服务器端的存储空间，用户在连接服务器时，会由服务器生成一个唯一的SessionID，用该SessionID 为标识符来存取服务器端的Session存储空间。而SessionID这一数据则是保存到客户端，用Cookie保存的，用户提交页面时，会将这一SessionID提交到服务器端，来存取Session数据。这一过程，是不用开发人员干预的。所以一旦客户端禁用Cookie，那么Session也会失效。
>   *   Cookies是属于Session对象的一种。但有不同，Cookies不会占服务器资源，是存在客服端内存或者一个Cookie的文本文件中；而Session则会占用服务器资源。所以，尽量不要使用Session，而使用Cookies。但是我们一般认为Cookie是不可靠的，Cookies是保存在本机上的，但是其信息的完全可见性且易于本地编辑性，往往可以引起很多的安全问题。

