Servlet进阶开发

Servlet进阶

内容概述

在这里插入图片描述

Cookie和Session的引入

Cookie和Session技术的目的:为了解决http协议是无状态的。http协议无状态的意思是,浏览器发起请求(请求中一般是需要携带数据的),服务器接收到请求后调用相关的后端代码去处理该请求,处理完后会响应浏览器。 本次请求与响应结束后,相关的请求与响应数据就会销毁。

如果浏览器又发送了请求,而且本次请求需要用到上次请求传递过的数据,那么本次请求又要重新带上上次请求传递的数据。这样效率不高,而且用户体验度也差!

如何记住这些想要记住的信息呢?使用Cookie技术或者Session技术。

通过一个生活案例类比,来大致了解Cookie和Session的特点,后面再详细学习。

在这里插入图片描述

Cookie对象

Cookie的引入

问题:

​ 为了解决http协议是无状态的。

​ http协议无状态的意思是,浏览器发起请求(请求中一般是需要携带数据的),服务器接收到请求后调用相关的后端代码去处理该请求,处理完后会响应浏览器。 本次请求与响应结束后,相关的请求与响应数据就会销毁。

​ 如果浏览器又发送了请求,而且本次请求需要用到上次请求传递过的数据,那么本次请求又要重新带上上次请求传递的数据。这样效率不高,而且用户体验度也差!

​ 比如:用户在访问京东的时候,在登录界面输入用户名密码登录了。然后用户在结算的时候,因为http协议的无状态性,导致用户又得重新传递一次登录的信息才行!

解决:

​ 浏览器在发起请求的时候,请求达到服务器,服务器认为本次请求携带的数据比较常用,以后的请求也会用得上,那么服务器就会在响应的时候告诉浏览器,把本次请求的数据给保存起来。然后浏览器以后每次发送请求访问服务器的时候都给带上!

总结:

  1. 服务器决定哪些数据是以后的请求也会用到的
  2. 服务器以响应的方式告诉浏览器将常用的这些数据存储起来,存储在浏览器端
  3. 浏览器以后每次发送请求的时候需要带上这些存储起来的数据

特点:

  1. Cookie是浏览器端的数据存储技术
  2. 浏览器每次发起请求的时候,请求信息中就包含了Cookie中存储的数据
  3. Cookie不适合大量数据的存储(每个Cookie存储的数据不超过4KB)
  4. 不安全,不适合存储重要的数据到浏览器端

实现:

​ Cookie技术。(Cookie的作用就是让浏览器保存数据的)

​ 有一个专门操作Cookie的类 javax.servlet.http.Cookie,在服务器端创建,随着服务器端的响应发送给客户端,保存在浏览器。当下次再访问服务器时把Cookie再带回服务器。

Cookie的创建和发送

​ 通过 new Cookie(“key”,“value”);来创建一个 Cookie 对象,要想将 Cookie 随响应发送到客户端,需要先添加到 response 对象中,response.addCookie(cookie);此时该 cookie 对象则随着响应发送至了客户端。在浏览器上可以看见。

​ 案例:在Servlet中创建Cookie对象:

// 创建Cookie对象
Cookie cookie = new Cookie("uname","lili");
// 发送(响应)Cookie对象
response.addCookie(cookie);

​ 查看Cookie源码发现:(以下为Cookie源码中截取的重点部分)

public class Cookie implements Cloneable, Serializable {
	private final String name;// name被final修饰,一旦复制就不能改变
    private String value;
	// 没有空构造器,只有一个有参构造器:
	public Cookie(String name, String value) {
        validation.validate(name);// 对name进行校验,确保name是唯一的标识
        this.name = name;// 给name赋值
        this.value = value;// 给value赋值
    }
}

​ 访问创建Cookie对象的Servlet,F12 查看,发现浏览器中Cookie 的格式:键值对形式,用“=”连接

在这里插入图片描述

Cookie的获取

​ 在服务器端只提供了一个 getCookies()的方法用来获取客户端回传的所有 cookie 组成的一个数组,如果需要获取单个 cookie 则需要通过遍历,getName()获取 Cookie 的名称,getValue()获取 Cookie 的值。

PS:Cookie是浏览器的技术,如果你关闭了服务器,只要在浏览器中没有失效,Cookie就是一直携带的,可以发送请求到服务器获取Cookie信息。

// 获取Cookie数组
Cookie[] cookies = request.getCookies();
// 判断数组是否为空
if (cookies != null && cookies.length > 0) {
    // 遍历Cookie数组
    for (Cookie cookie : cookies){
        System.out.println(cookie.getName());
        System.out.println(cookie.getValue());
    }
}

再次请求发现:数据确实存储在浏览器中,并通过请求发送给服务器,服务器可以获取到Cookie的信息。

在这里插入图片描述

Cookie设置到期时间

​ 除了 Cookie 的名称和内容外,我们还需要关心一个信息,到期时间,到期时间用来指定该 cookie 何时失效。默认为当前浏览器关闭即失效。(可以自己测试一下)

​ 我们可以手动设定 cookie 的有效时间(通过到期时间计算),通过 setMaxAge(int time);方法设定 cookie 的最大有效时间,以秒为单位。

到期时间的取值

  • 负整数

    若为负数,表示不存储该 cookie。

    cookie 的 maxAge 属性的默认值就是-1,表示只在浏览器内存中存活,一旦关闭浏览器窗口,那么 cookie 就会消失。

在这里插入图片描述

  • 正整数

    若大于 0 的整数,表示存储的秒数。

    表示 cookie 对象可存活指定的秒数。当生命大于 0 时,浏览器会把 Cookie 保存到硬盘上(PS:不同浏览器磁盘位置不同),就算关闭浏览器(PS:换浏览器不行,不能从谷歌换到火狐),就算重启客户端电脑,cookie 也会存活相应的时间。

  • 若为 0,表示删除该 cookie。

    cookie 生命等于 0 是一个特殊的值,它表示 cookie 被作废!也就是说,如果原来浏览器已经保存了这个 Cookie,那么可以通过 Cookie 的 setMaxAge(0)来删除这个 Cookie。 无论是在浏览器内存中,还是在客户端硬盘上都会删除这个 Cookie。

设置Cookie对象指定时间后失效

// 创建Cookie对象
Cookie cookie = new Cookie("uname","zhangsan");
// 设置Cookie 3天后失效  eg:之前做的登录账号密码 十天内免密登录 就是这样实现的
cookie.setMaxAge(3 * 24 * 60 * 60);
// 发送Cookie对象
response.addCookie(cookie);

可以在f12---->如下位置看到Cookie:

在这里插入图片描述

Cookie的注意点

  1. Cookie保存在当前浏览器中。

    不同的浏览器的cookie保存在磁盘上的位置是不同的,不能跨浏览器使用cookie。

    eg:在一般的站点中常常有记住用户名这样一个操作,该操作只是将信息保存在本机上,换电脑以后这些信息就无效了,跨浏览器也无效。

  2. 浏览器存放Cookie的数量

    不同的浏览器对Cookie也有限定,Cookie的存储有是上限的,,我们也不会去存储成千上万个Cookie,Cookie不适合大量数据的存储,每个Cookie存储的数据不超过4KB。Cookie是存储在客户端(浏览器)的,而且一般是由服务器端创建和设定。后期结合Session来实现回话跟踪。

  3. 同名Cookie问题

    如果服务器端发送重复的Cookie那么会覆盖原有的Cookie。

  4. Cookie存中文问题

    Cookie 中不建议出现中文,如果有中文则通过 URLEncoder.encode()来进行编码,获取时通过 URLDecoder.decode()来进行解码。

    eg:一个Servlet中创建键为中文的Cookie并响应给浏览器:

String name = "姓名";
String value = "丽丽";
// 创建Cookie对象
Cookie cookie = new Cookie(name,value);
// 发送Cookie对象
response.addCookie(cookie);

​ 报错:Cookie是不支持中文的

在这里插入图片描述

​ 改变:

String name = "姓名";
String value = "丽丽";
// 通过 URL Encoder.encode()来进行编码
name = URLEncoder.encode(name);
value = URLEncoder.encode(value);
// 创建Cookie对象
Cookie cookie = new Cookie(name,value);
// 发送Cookie对象
response.addCookie(cookie);

​ eg:另一个Servlet中取出Cookie:

// 获取Cookie数组
Cookie[] cookies = request.getCookies();
// 判断数组是否为空
if (cookies != null && cookies.length > 0) {
    // 遍历Cookie数组
    for (Cookie cookie : cookies){
        // 获取时通过 URLDecoder.decode()来进行解码
        URLDecoder.decode(cookie.getName());
        URLDecoder.decode(cookie.getValue());
    }
}

Cookie的路径

​ Cookie的setPath设置cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些cookie。

**情景一:**当前服务器下任何项目的任意资源都可获取Cookie对象

/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/",表示在当前服务器下任何项目都可访问到Cookie对象
cookie.setPath("/");
response.addCookie(cookie);

**情景二:**当前项目下的资源可获取Cookie对象 (默认不设置Cookie的path)

/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s01",表示在当前项目下任何项目都可访问到Cookie对象
cookie.setPath("/s01"); // 默认情况,可不设置path的值
response.addCookie(cookie);

**情景三:**指定项目下的资源可获取Cookie对象

/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s02",表示在s02项目下才可访问到Cookie对象
cookie.setPath("/s02"); // 只能在s02项目下获取Cookie,就算cookie是s01产生的,s01也不能获取它
response.addCookie(cookie);

**情景四:**指定目录下的资源可获取Cookie对象

/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s01/cook",表示在s02/cook目录下才可访问到Cookie对象
cookie.setPath("/s01/cook"); 
response.addCookie(cookie);

HttpSession对象

Session是什么

​ session和cookie就是为解决HTTP协议的无状态采用的两种解决方案,称为会话跟踪技术。与cookie将信息保存在客户端不同,session将信息保存在服务器端解决。

​ PS : 一次请求,指的是客户端发送数据到服务器端,服务器端接收并相应,这可以称之为一次请求。由于HTTP协议是无状态的,你无法从一个请求中拿到另一个请求中的数据,多个请求之间的数据无法共享,那么就引入会话的概念。会话怎么理解呢?就好比你和我坐下来聊天谈话,你问我答,你问我答,你再问我前面的问题我还是可以回答你,整个会话聊天中的内容我们可以随时交谈使用。那我们这里说的会话(Session)指的是什么:

​ 对于服务器而言,每一个连接到它的客户端,都产生一个会话 (Session),服务端为他们之间的会话创建一个session对象。servlet 容器使用 javax.servlet.http.HttpSession接口创建 HTTP 客户端和 HTTP 服务器之间的会话(构建HttpSession对象- Session在服务器端对应的实现是:HttpSession对象是 javax.servlet.http.HttpSession 的实例。)。Session是在服务器端生成的,存储在服务器端,即存在内存中。

​ Session生成的同时,会生成一个与之相关联的的SessionID, 此SessionID的存储是需要Cookie来完成的。SessionID是以名称为JSESSIONID、其值应该是一个既不会重复,又不容易被找到规律以仿造的字符串组成。SessionID会随着此次Http 响应,一并返回到客户端,并保存在客户端中。到当前请求再次发出后,该SessionID会随着Http头部,传到服务器中,服务器依据当前SessionID 得到与之对应的session。

​ 一次会话(一个用户的多次请求)期间共享数据。我们可以通过 request.getSession()方法,来获取当前会话的 session 对象。

// 如果session对象存在,则获取;如果session对象不存在,则创建
HttpSession session = request.getSession();

案例:

在Servlet中获取Session内容:

package com.msb.testsession;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/testsession01")
public class TestSession01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取Session对象:
        HttpSession session = req.getSession();
        // 获取Session的唯一标识符:
        String id = session.getId();
        System.out.println(id);
        // 断判是否是新的session对象:
        System.out.println(session.isNew());
    }
}

启动服务器:

注意:把这个勾选去掉,否则直接访问项目会产生Session会话,影响测试结果。

在这里插入图片描述

**第一次访问Servlet:**http://localhost:8888/servletdemo02/ts01

后端控制台结果:

在这里插入图片描述

浏览器中结果:

在这里插入图片描述

**第二次访问Servlet:**http://localhost:8888/servletdemo02/ts01

后台控制台:

在这里插入图片描述

浏览器中结果:

在这里插入图片描述

你也可以请求其它页面,只要在同一个会话中,JSESSIONID都有效。(客户端请求服务器,会话即开启)

结论:

第一次访问发送请求到后端,第一次没有创建会话(Session)之前,那么由服务器创建一个HttpSession,并且将HttpSession对应的Sessionid绑定到http协议中响应回客户端。

第二次再访问,请求的时候在http请求协议中会在Cookie中携带Sessionid ,那么后台就会根据这个id取出对应的HttpSession。

测试,关闭服务器再重新测试,观察Sessionid的变化。

测试,关闭浏览器再重新测试,观察Sessionid的变化。

发现:一次会话(浏览器打开到关闭,访问多页页面,都在一个会话中)结束,结束后再次访问时,那么会创建新的Session。

PS:在浏览器Application中可以看到JSESSIONID:

在这里插入图片描述

标识符 JSESSIONID

​ Session 既然是为了标识一次会话,那么此次会话就应该有一个唯一的标志,这个标志就是 sessionId。

​ 每当一次请求到达服务器,开启了会话,服务器第一步会查看是否从客户端回传一个名为 JSESSIONID 的 cookie,如果没有则认为这是一次新的会话,会创建 一个新的 session 对象,并用唯一的 sessionId 为此次会话做一个标志。如果有 JESSIONID 这 个cookie回传,服务器则会根据 JSESSIONID 这个值去查看是否含有id为JSESSION值的session 对象,认为是之前标志过的一次会话,返回该 session 对象,数据达到共享。

​ 服务器在创建好Session对象后,服务器会将Session对象的id以Cookie的形式保存在客户端(浏览器),用户在发请求的时候,就会带上该Cookie,也就是SessionId到服务器,服务器会根据该id找到用户的Session对象。存储SessionId的Cookie不需要我们自己创建,我们在调用服务器完成Session创建的时候,Tomcat服务器会自动的创建Cookie,Cookie里面保存的是SessionId并响应给浏览器。但是注意,Cookie默认的有效期为浏览器运行期间,浏览器关闭,Cookie即失效。

​ 不管是存储了用户SessionId的Cookie信息丢失,还是服务器存储的Session对象被销毁,(无论你是关闭浏览器还是关闭服务器,相当于会话断开了)默认只要服务器接收到用户发起的请求后,如果找不到对应的Session对象,都会重新创建,并将新的SessionId以Cookie的形式保存到浏览器中 。

session的使用

​ Session 用来表示一次会话,在一次会话中数据是可以共享的,这时 session 作为域对象存在(会话是一个域,在其中共享数据),可以通过setAttribute(name,value) 方法向域对象中添加数据,通过 getAttribute(name) 从域对象中获取数据,通过 removeAttribute(name) 从域对象中移除数据。

​ 数据存储在 session 域对象中,当 session 对象不存在了,数据也就不能共享了。这就不得不谈到 session 的生命周期。

案例:

​ 请求Servlet,将数据存于Session中(存两个,删一个),然后请求转发到另一个Servlet,从另一个Servlet中取出Session:

package com.msb.testsession;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/ts02")
public class TestSession02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取Session对象:
        HttpSession session = req.getSession();
        // 在session域对象中存入内容:
        session.setAttribute("uname1","lili");
        session.setAttribute("uname2","feifei");

        // 移除指定名称的session域对象:
        session.removeAttribute("uname2");

        // 请求转发到另一个Servlet:
        req.getRequestDispatcher("/ts03").forward(req, resp);

    }
}
package com.msb.testsession;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/ts03")
public class TestSession03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取Session对象:
        HttpSession session = req.getSession();
        // 获取指定名称的session域对象:
        String uname = (String)session.getAttribute("uname1");
        System.out.println(uname);
        String uname2 = (String)session.getAttribute("uname2");
        System.out.println(uname2);


    }
}

控制台结果:

在这里插入图片描述

session对象的销毁

默认时间到期

​ 可以对生成的 Session 设置过期时间,如果不设置过期时间,默认的Session过期时间是30分钟(在不同的服务器中,它的过期时间略有不同,以Tomcat为案例来说)。

​ 那么 session 的默认时间可以改么?答案是肯定的。可以在 Tomcat 中的 conf 目录下的 web.xml 文件中进行修改。

<!-- session 默认的最大不活动时间。单位:分钟。 -->
<session-config>
	<session-timeout>30</session-timeout>
</session-config>

​ 当客户端第一次请求 servlet 并且操作 session 时,session 对象生成,Tomcat 中 session 默认的存活时间为 30min**(指的是这30min完全不操作),即你不操作界面的时间,一旦有操作,session 会重新计时。**

​ 当然我们也可以通过 getMaxInactiveInterval() 方法来查看当前 Session 对象的最大不活动时间。

// 获取session的最大不活动时间
int time = session.getMaxInactiveInterval();

​ 实际案例:在登录某个网站的时候,比如长时间不用以后,登录信息就过期了,你再操作就让你重新登录了,那就是因为登录数据都存在session中了,超过有效时间,session就消失了,那么就需要重新登录。

自己设定到期时间

​ 当然除了以上的修改方式外,我们也可以在程序中自己设定 session 的生命周期,通过session.setMaxInactiveInterval(int) 来设定 session 的最大不活动时间,单位为秒。

// 获取session对象 
HttpSession session = request.getSession();
// 设置session的最大不活动时间
session.setMaxInactiveInterval(15); // 15秒
立刻失效

​ 或者我们也可以通过 session.invalidate() 方法让 session 立刻失效

// 销毁session对象
session.invalidate();

​ 代码:

package com.msb.testsession;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/ts04")
public class TestSession04 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取session对象:
        HttpSession session = req.getSession();
        // 立即失效:
        session.invalidate();
        // 设置session的最大不活动时间:
        //session.setMaxInactiveInterval(30);
        // 获取session的最大不活动时间:
        /*int time = session.getMaxInactiveInterval();
        System.out.println(time);*/
    }
}

关闭浏览器

​ 从前面的 JESSIONID 可知道,session 依赖 cookie 实现,并且该 cookie 的有效时间为关闭浏览器,从而 session 在浏览器关闭时也相当于失效了(因为没有 JSESSION 再与之对应)。

关闭服务器

​ 当关闭服务器时,session 销毁。

​ Session 失效则意味着此次会话结束,数据共享结束。

ServletContext对象

​ 每一个 web 应用都有且仅有一个ServletContext 对象,又称 Application 对象,从名称中可知,该对象是与应用程序相关的。在 WEB 容器启动的时候,会为每一个 WEB 应用程序创建一个对应的 ServletContext 对象。

​ 该对象有两大作用,第一、作为域对象用来共享数据,此时数据在整个应用程序中共享; 第二、该对象中保存了当前应用程序相关信息。例如可以通过 getServerInfo() 方法获取当前服务器信息 ,getRealPath(String path) 获取资源的真实路径等。

ServletContext对象的获取

​ 获取 ServletContext 对象的途径有很多。比如:

  1. 通过 request 对象获取

    ServletContext servletContext = request.getServletContext();
    
  2. 通过 session 对象获取

    ServletContext servletContext = request.getSession().getServletContext();      
    
  3. 通过 servletConfig 对象获取,在 Servlet 标准中提供了 ServletConfig 方法

    ServletConfig servletConfig = getServletConfig();
    ServletContext servletContext = servletConfig.getServletContext();
    
  4. 直接获取,Servlet 类中提供了直接获取 ServletContext 对象的方法

    ServletContext servletContext = getServletContext();
    

ServletContext的使用

​ ServletContext 也可当做域对象来使用,通过向 ServletContext 中存取数据,可以使得整个应用程序共享某些数据。当然不建议存放过多数据,因为 ServletContext 中的数据一旦存储进去没有手动移除将会一直保存。

package com.msb.testsession;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/tsc01")
public class TestServletContext01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取ServletContext对象
		ServletContext servletContext = request.getServletContext();
		// 获取项目存放的真实路径
		String realPath = servletContext.getRealPath("/");
		// 获取当前服务器的版本信息
		String serverInfo = servletContext.getServerInfo();
		// 设置域对象
		servletContext.setAttribute("name","zhangsan");
    }
}

​ 可以在项目的其它位置取出:(可以关闭服务器,因为不限于会话,是在整个服务器启动期间都好使的,对整个应用都好使。)

package com.msb.testsession;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/tsc02")
public class TestServletContext02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取ServletContext对象
		ServletContext servletContext = request.getServletContext();
		// 获取域对象
		String name = (String) servletContext.getAttribute("name");
		// 移除域对象
		servletContext.removeAttribute("name");
    }
}

Servlet的三大域对象

认识域对象

什么是域对象
域对象类似于之前学习的map集合,可以存放键值对的数据。不同的是域对象中数据的使用有一定的区域范围限制。

三大域对象

​ request域 对应HttpServeltRequest对象 也叫请求域
​ session域 对应HttpSession对象 也叫会话域
​ application域 对应ServletContext对象 也叫应用域
常用API
​ setAttribute(key, value) 向域中添加/修改数据,无则添加,有则修改
​ getAttribute(key) 获取域中的数据
​ removeAttribute(key) 从域中移除指定key的数据

request域

有效范围
在一次请求内有效,比如:转发。一般用于存储单次请求之内的业务数据。
生命周期
创建:浏览器每次请求,服务器都会重新创建
使用:在请求的Servlet中或者请求转发后的Servlet中使用
销毁:请求响应结束后,该请求对象就会被销毁

Session域

有效范围

​ 单次会话内有效,可以跨多个请求。
​ 一般用来存储用户状态数据,比如用户的登录信息。

生命周期

  1. 创建:从第一次发出请求,会话开始
  2. 使用:本次会话内,浏览器和服务器之间发生的多次请求和响应都有效
  3. 销毁:会话结束,比如:达到最大不活动时间、手动清除、浏览器关闭

Application域

有效范围

​ 当前web项目内都有效,可以跨请求,跨会话访问。
​ 一般放一些全局的和项目本身相关的数据,如在线人数,不建议向这里放业务数据。

生命周期

  1. 创建:服务器启动

  2. 使用:服务器运行期间都有效

    1. 销毁:服务器关闭

文件上传和下载

​ 在上网的时候我们常常遇到文件上传的情况,例如上传头像、上传资料等;当然除了上传,遇见下载的情况也很多,接下来看看我们 servlet 中怎么实现文件的上传和下载。

文件上传

​ 实现案例:前台上传文件到服务器保存。

​ 思路:文件上传涉及到前台页面的编写和后台服务器端代码的编写,前台发送文件,后台接收并保存文件,这才是一个完整的文件上传。

前台页面

​ 在做文件上传的时候,会有一个上传文件的界面,创建一个html页面即可:
在这里插入图片描述

​ 首先我们需要一个表单,并且表单的请求方式为 POST;(必须)

​ 其次我们的 form 表单的 enctype 必须设为"multipart/form-data",即 enctype=“multipart/form-data”,意思是设置表单的类型为文件上传表单。默认情况下这个表单类型是 “application/x-www-form-urlencoded”, 不能用于文件上传。只有使用了multipart/form-data 才能完整地传递文件数据。

​ upload.html中代码为:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
    <!--
    文件上传表单
        1. 表单提交类型 method="post"
        2. 表单类型 enctype="multipart/form-data"
        3. 表单元素类型  文件域设置name属性值
    -->
    <form method="post" action="uploadServlet" enctype="multipart/form-data">
      提交文件名字:<input type="text" name="filename" > <br>
      文件:<input type="file" name="uploadfile" > <br>
      <input type="submit" value="点击上传">
    </form>
</body>
</html>
后台实现

​ 因为前台 enctype 设为"multipart/form-data",所以后台必须配套使用注解 @MultipartConfig ,才可以将一个 Servlet 标识为支持文件上传。,否则Servlet没有文件上传能力。Servlet 将 multipart/form-data 的 POST 请求封装成 Part对象,通过 Part对象对上传的文件进行操作。

package com.msb.upload;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/uploadServlet")
@MultipartConfig // 如果是文件上传表单,一定要加这个注解
public class UploadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置请求的编码格式
        req.setCharacterEncoding("UTF-8");
        // 获取普通表单项 (文本框)
        String uname = req.getParameter("filename"); // "filename"代表的是文本框的name属性值
        // 通过 getPart(name) 方法获取Part对象 (name代表的是页面中file文件域的name属性值)
        Part part = req.getPart("uploadfile");
        // 通过Part对象,获取上传的文件名
        String fileName = part.getSubmittedFileName();
        // 将文件上传到指定位置
        part.write("D:/" + fileName);
        // 也可以上传到项目路径下: req.getServletContext().getRealPath("/") + fileName
    }
}

文件下载

​ 文件下载,即将服务器上的资源下载(拷贝)到本地,我们可以通过两种方式下载。第一种是通过超链接本身的特性来下载;第二种是通过代码下载。

超链接下载

​ 当我们在 HTML 或 JSP 页面中使用a标签时,原意是希望能够进行跳转,但当超链接遇到浏览器不识别的资源时会自动下载;当遇见浏览器能够直接显示的资源,浏览器就会默认显示出来,比如 txt、png、jpg 等。当然我们也可以通过 download 属性规定浏览器进行下载。但有些浏览器并不支持。

默认下载

<!-- 当超链接遇到浏览器不识别的资源时,会自动下载 -->
<a href="test.zip">超链接下载</a>

指定 download 属性下载

<!-- 当超链接遇到浏览器识别的资源时,默认不会下载。通过download属性可进行下载 -->
<a href="test.txt" download>超链接下载</a>

​ download 属性可以不写任何信息,会自动使用默认文件名。如果设置了download属性的值,则使用设置的值做为文件名。当用户打开浏览器点击链接的时候就会直接下载文件。

案例:利用超链接实现下载

在web下新建目录download,然后将资源粘贴进去:

在这里插入图片描述

需要注意的是:想要目录下资源被浏览器访问到,需要单独设置一下download目录,设置为资源路径:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

编写代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <!--浏览器能够识别的资源-->
  <a href="download/hello2023.txt">文本文件</a>
  <a href="download/冰墩墩2023.png">图片文件</a>
  <!--浏览器不能够识别的资源-->
  <a href="download/test2023.rar">压缩文件</a>
  <hr>
  <a href="download/hello2023.txt" download>文本文件</a>
  <a href="download/冰墩墩2023.png" download="a.png">图片文件</a>
</body>
</html>
后台代码实现下载

实现案例:页面录入要下载的文件名,然后点击下载按钮进行下载。

前台实现:

​ 录入文件名,点击下载按钮进行下载:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <form action="downloadServlet">
    文件名:<input type="text" name="filename" placeholder="请输入要下载的文件名">
    <input type="submit" value="下载">
  </form>
</body>
</html>

后台实现步骤

  1. 需要通过 response.setContentType 方法设置 Content-type 头字段的值, 为浏览器无法使用某种方式或激活某个程序来处理的 MIME 类型,例 如 “application/octet-stream” 或 “application/x-msdownload” 等。(设置这个以后,浏览器才会不识别文件而进行下载,否则就会直接识别文件,如文本、图片等)

  2. 需要通过 response.setHeader 方法设置 Content-Disposition 头的值 为 “attachment;filename=文件名” (设置这个以后,浏览器才会出现下载框并有下载文件名字)

  3. 读取下载文件,调用 response.getOutputStream 方法向客户端写入附件内容。

package com.msb.download;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置请求信息的解码格式:
        req.setCharacterEncoding("UTF-8");
        // 设置响应信息的编码格式:
        resp.setCharacterEncoding("UTF-8");
        // 设置浏览器的编码格式:
        resp.setContentType("text/html;charset=UTF-8");
        // 获取文件名:
        String filename = req.getParameter("filename");
        // 获取资源的真实路径:
        String realPath = req.getServletContext().getRealPath("/download/");
        // 封装File对象:
        File file = new File(realPath + filename);

        // 判断文件file是否存在:
        if(file.exists()){
            // 下载:
            // 设置响应类型:
            resp.setContentType("application/octet-stream");
            // 设置头信息
            resp.setHeader("Content-Disposition", "attachment;filename=" + filename);
            // 得到输入流:
            FileInputStream fis = new FileInputStream(file);
            // 得到输出流:
            ServletOutputStream os = resp.getOutputStream();
            // 完成输入输出操作:边输入边输出
            int n = fis.read();
            while(n != -1){
                os.write(n);
                n = fis.read();
            }
        }else{
            resp.getWriter().write("文件不存在,下载失败!");
        }

    }
}

过滤器

介绍

​ Filter 即为过滤,用于在 Servlet 之外对 Request 或者 Response 进行修改。它主要用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理。

​ 生活类比:饮水机过滤石

在这里插入图片描述

​ 使用 Filter 的完整流程: Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再 对服务器响应进行后处理。

单个过滤器

在这里插入图片描述

多个过滤器

​ 在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合 起来称之为一个 Filter 链。(从一个过滤器将请求转发给下一个过滤器。)

在这里插入图片描述

若是一个过滤器链:先配置先执行(请求时的执行顺序);响应时: 以相反的顺序执行。

​ 在 HttpServletRequest 到达 Servlet 之前,拦截客户的请求 (HttpServletRequest )。根据需要检查HttpServletRequest,也可以修改 HttpServletRequest 头和数据。

​ 在HttpServletResponse 到达客户端之前,拦截响应( HttpServletResponse)。根据需要检查 HttpServletResponse,也可以修改 HttpServletResponse头和数据。

​ PS:框架中会学习拦截器,道理和这个类似。

实现

​ 可以通过实现一个叫做javax.servlet.Fileter的接口来实现一个过滤器,其中定义了 三个方法,init(), doFilter(), destroy()分别在相应的时机执行。

​ Filter 的实现只需要两步:

​ Step1: 编写 java 类实现 Filter 接口,并实现其 doFilter 方法。

​ Step2: 通过@WebFilter注解设置它所能拦截的资源。

​ web 服务器在调用 doFilter 方法时,会传递一个 filterChain 对象进来,filterChain 对象是 filter 接口中最重要的一个对象,它提供了一个 doFilter 方法,开发人员可以根据需求决定 是否调用此方法,调用该方法,则 web 服务器就会调用 web 资源的 service 方法,即 web 资源就会被访问,否则 web 资源不会被访问。(本质是放行,调用doFilter方法后,即请求可以到达资源)

案例:编写过滤器,只拦截servlet01

​ 在com.msb.filter包下编写过滤器:

package com.msb.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebFilter("/servlet01")
public class AFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("A过滤器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 放行前对请求做预处理:
        System.out.println("A过滤器执行-过滤请求");
        // 放行:放行以后才可以到达资源
        filterChain.doFilter(servletRequest,servletResponse);
        // 放行后对请求做后处理:
        System.out.println("A过滤器执行-过滤响应");
    }

    @Override
    public void destroy() {
        System.out.println("A过滤器销毁");
    }
}

​ 在com.msb.servlet包下编写两个Servlet:

package com.msb.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/servlet01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet01中逻辑执行");
    }
}

package com.msb.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/servlet02")
public class Servlet02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet02中逻辑执行");
    }
}

​ 结果:

在这里插入图片描述

请求第一个Servlet,会经过过滤器:http://localhost:8888/s4/servlet01

在这里插入图片描述

请求第二个Servlet,不会经过过滤器:http://localhost:8888/s4/servlet02
在这里插入图片描述

停止服务器:

在这里插入图片描述

案例:实现过滤器链

​ 加入过滤器B,B中拦截所有资源:

package com.msb.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebFilter("/*")
public class BFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("B过滤器初始化");
    }

    @Override
    public void destroy() {
        System.out.println("B过滤器销毁");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 放行前对请求做预处理:
        System.out.println("B过滤器执行-过滤请求");
        // 放行:放行以后才可以到达资源
        filterChain.doFilter(servletRequest,servletResponse);
        // 放行后对请求做后处理:
        System.out.println("B过滤器执行-过滤响应");
    }
}

访问servlet01,会经过过滤器A和B:

在这里插入图片描述

过滤器链,请求时:先配置的先执行(这里的先配置指的是字母在前的过滤器),响应时:反过来。

过滤器应用案例

案例1-中文乱码处理

以往操作:在每个Servlet中都要加请求和响应乱码的解决代码,以防止中文乱码问题:

// 设置请求信息的解码格式:
req.setCharacterEncoding("UTF-8");
// 设置响应信息的编码格式:
resp.setCharacterEncoding("UTF-8");
// 设置浏览器的编码格式:
resp.setContentType("text/html;charset=UTF-8");

有了过滤器以后,不用每个Servlet都加上乱码解决代码了,只要经过过滤器就可以了,编写过滤器:

package com.msb.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebFilter("/*")
public class EncodeFilter implements Filter {


	public void destroy() {		
	}

	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
		// 设置请求信息的解码格式:
        req.setCharacterEncoding("UTF-8");
        // 设置响应信息的编码格式:
        resp.setCharacterEncoding("UTF-8");
        // 设置浏览器的编码格式:
        resp.setContentType("text/html;charset=UTF-8");
		
		// 放行资源
		chain.doFilter(req, resp);		
	}

	public void init(FilterConfig fConfig) throws ServletException {
		
	}

}

Servlet中:

package com.msb.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/servlet03")
public class Servlet03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 接收请求参数:(中文)
        String uname = req.getParameter("uname");
        System.out.println(uname);
        // 响应数据给页面:(中文)
        resp.getWriter().write("你好");
    }
}

案例2-用户非法访问拦截

等JSP学完以后,在登录案例中讲解。请查看JSP笔记中:

在这里插入图片描述

监听器

监听器的应用场景

​ 实际生活中是存在监听器的,比如:某个饭店的摄像头。可以随时观察每个顾客的动向,监听对象:去吃饭的人。

​ 在web项目中,也有监听器,监听对象:三大域对象request、session、application。三大域对象作为数据流转的载体,但是这些域对象都不是我们创建的,也不是我们销毁的。

​ 我们可以给三大域对象添加监听,监听三大域对象的创建及销毁,在监听到他们有动态的时候,执行一些方法。这就是监听器。

监听器的介绍

作用:

​ 监听三大域对象的销毁、创建及数据资源的变更。

特点:

  1. 监听方法由tomcat根据监听结果来调用执行

  2. 监听方法中的逻辑代码由我们根据需要编写

监听器种类:(三类共八个监听器)

Request

ServletRequestListener           (监听request对象创建和销毁) 
ServleRequestAttributeListener   (监听域对象中的数据添加 替换 删除)

Session

HttpSessionListener              (监听session对象创建和销毁)
HttpSessionAttributeListener     (监听session域对象中的数据添加 修改 删除)
HttpSessionBindingListener       (监听session对象监听器绑定和解绑定接口)
HttpSessionActivationListener     (监听session对象钝化和活化状态接口)

Application

ServletContextListener            (监听application对象创建和销毁)
ServletContextAttributeListener   (监听application域对象中的数据添加 修改 删除)

PS:监听器的使用频率已经不高了,学习过程可以弱化这部分。所以你只要了解一下监听器是什么,能做什么,能实现什么效果,做个小案例练习一下即可。

监听器的使用

监听器的使用步骤:

  1. 创建一个类,实现监听器接口(Listener)
  2. 实现里面的方法
  3. 加入注解@WebListener配置监听器才会生效

案例:监听session的生命周期。

监听器:

package com.msb.listener;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * @Author: zhaoss
 * 想要监听session的生命周期,那么需要实现session的监听接口
 * 并重写两个方法:sessionCreated,sessionDestroyed
 */
@WebListener
public class SessionListener implements HttpSessionListener {
    /**
     * session被创建的时候调用
     * @param se
     */
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("session被创建了");
    }

    /**
     * session被销毁的时候调用
     * @param se
     */
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session被销毁了");
    }
}

两个Servlet一个创建session一个销毁session:

package com.msb.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/servlet04")
public class Servlet04 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取session:
        HttpSession session = req.getSession();
        System.out.println("session对象被创建了");
    }
}

package com.msb.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: zhaoss
 */
@WebServlet("/servlet05")
public class Servlet05 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取session:
        HttpSession session = req.getSession();
        session.invalidate();
        System.out.println("session对象被销毁了");
    }
}

访问两次servlet04,访问一次servlet05,结果:

在这里插入图片描述

监听器应用案例

​ **案例:**网站在线人数的监控

​ **逻辑:**新过来一个用户,会创建新的session,当有新的session创建的时候,在线人数+1,当有session对象销毁的时候,在线人数-1。人数变量存在application作用域中,这样所有的会话请求(不同用户)都可以取出在线人数。

实现步骤:

​ Step1:创建一个监听器,需要实现某种接口,根据需求选取 HttpSessionListener ,用来检测 Session 的创建和销毁。

​ Step2:通过@WebListener注解配置该监听器

  1. 编写监听器:在类中定义一个成员变量用来存储当前的 session 个数。(OnlineListener.java)
/**
 * 在线人数统计
 * 	当有新的session对象被创建,则在线人数+1;
 * 	有session对象被销毁,在线人数-1;
 * @author zhaoss
 *
 */
@WebListener
public class OnlineListener implements HttpSessionListener {
	
	// 默认在线人数
	private Integer onlineNumber = 0;

	/**
	 * 当有新的session对象被创建,则在线人数+1;
	 */
	@Override
	public void sessionCreated(HttpSessionEvent se) {
		// 人数+1
		onlineNumber++;
		// 将人数存到session作用域中
		// se.getSession().setAttribute("onlineNumber", onlineNumber);
		// 将人数存到application作用域中
		se.getSession().getServletContext().setAttribute("onlineNumber", onlineNumber);
	}

	/**
	 * 有session对象被销毁,在线人数-1;
	 */
	@Override
	public void sessionDestroyed(HttpSessionEvent se) {
		// 人数-1
		onlineNumber--;
		// 将人数存到session作用域中
		// se.getSession().setAttribute("onlineNumber", onlineNumber);
		// 将人数存到application作用域中
		se.getSession().getServletContext().setAttribute("onlineNumber", onlineNumber);
	}

}

2.编写Servlet:做一个测试的 Servlet ,用来登录,和显示当前在线人数。编写退出操作,退出时候传入一个参数logout并重新访问servlet06,加入判断,如果识别到退出操作,就销毁session(OnlineServlet.java)

/**
 * 在线人数统计
 */
@WebServlet("/servlet06")
public class OnlineServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		// 得到参数
		String key = request.getParameter("key");
		
		// 判断是否为空 (不为空,且值为logout则为退出操作)
		if (key != null && "logout".equals(key)) {
            // 传递了参数,表示要做用户退出操作
			request.getSession().invalidate();
            return;
        }
			
        // 创建session对象
        HttpSession session = request.getSession();
        // 获取sessio作用域中的在线人数
        Integer onlineNumber = (Integer) session.getServletContext().getAttribute("onlineNumber");						

        // 输出在线人数在页面
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("在线人数:"+onlineNumber+"</h2><h4><a href='servlet06?key=logout'>退出</a><h4>");	
	
	}
}

用不同的浏览器构建不同会话,访问serlvet06:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

留不住的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值