Servlet

本文深入介绍了Servlet的工作原理,包括HTTP协议的基础知识、Tomcat服务器、Servlet生命周期、请求与响应处理、Cookie与Session管理。内容涵盖了请求转发与重定向、文件上传与下载,以及Servlet的实现细节。通过对HTTP头信息的分析,展示了如何处理乱码问题,以及在实际应用中如何利用Servlet进行数据共享和会话跟踪。
摘要由CSDN通过智能技术生成

Servlet

1.主要内容

在这里插入图片描述

2.IDEA的下载

​ 官网下载 2019.3.5版本

3.HTTP协议

web交互基本流程

在这里插入图片描述

HTTP 协议(Hypertext Transfer Protocol, 超文本传输协议),是一个客户端请求和响应的标准协议,这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。用户输入地址和端口号之后就可以从服务器上取得所需要的网页 信息。

​ 通信规则规定了客户端发送给服务器的内容格式也规定了服务器发送给客户端的内容格式客户端发送给服务器的 格式叫"请求协议";服务器发送给客户端的格式叫"响应协议"。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxZXYAKP-1613447476977)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210204204837440.png)]

3.1.浏览器中的书写格式

​ 当浏览器获取到信息以后,按照特定格式解析并发送即可。接收到服务器端给出的响应时,也按照HTTP协议进行解 析获取到各个数据,最后按照特定格式展示给用户。

3.2.HTTP协议的特点

1.支持客户/服务器模式

2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

3.灵活:HTTP 允许传输任意类型的数据对象。传输的类型由Content-Type加以标记。

4.无连接:无连接是表示每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。HTTP1.1版本后支持可持续连接。通过这种连接.就有可能在建立一个TCP连接后,发送请求并得到回应,然后发送更多的请求并得到更多的回应.通过把建立和释放TCP连接的开销分摊到多个请求上,则对于每个请求而言,由于TCP而造成的相对开销被大大地降低了。而且,还可以发送流水线请求,也就是说在发送请求1之后的回应到来之前就可以发送请求2也可以认为,一次连接发送多个请求,由客户机确认是否关闭连接,而服务器会认为这些请求分别来自不同的客户端。

5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

3.3.HTTP之URL

​ HTTP(超文本传输协议)是一个基于请求与响应模式的、应用层的协议,常基于 TCP 的连接方式,绝大多数的 Web开发,都是构建在 HTTP 协议之上的 Web 应用。

​ HTTP URL (URL 是一种特殊类型的 URI,包含了用于查找某个资源的足够的信息)的格式 如下:

// http://IP(主机名/域名):端口/访问的资源路径
  • http 表示要通过 HTTP 协议来定位网络资源;

  • host 表示合法的 Internet 主机域名或 者 IP 地址;

  • port 指定一个端口号,为空则使用缺省端口 80;

  • abs_path 指定请求资源的 URI; 如果 URL 中没有给出 abs_path,那么当它作为请求 URI 时,必须以“/”的形式给出,通常 这个工作浏览器自动帮我们完成。

3.4.HTTP 请求

​ HTTP 请求由三部分组成,分别是:请求行、请求头、请求正文。

​ 通过chrome浏览器, F12 —> Network查看。

  1. Get 请求(没有请求体)

  2. past 请求

格式

请求行
请求头1
请求头2
--
请求空行
请求体

请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本。
格式如下: Method Request-URI HTTP-Version CRLF
Method表示请求方法;

​ Request-URI是一个统一资源标识符;

​ HTTP-Version表示请求的 HTTP协议版本;

​ CRLF表示回车和换行;

3.5.HTTP 响应

​ 在接收和解释请求消息后,服务器返回一个 HTTP 响应消息。HTTP 响应也是由三个部分组成,分别是:状态行、消息报头、响应正文。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-spjpNnen-1613447476979)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210204211324183.png)]

3.6.消息头

​ HTTP 消息由客户端到服务器的请求和服务器到客户端的响应组成。请求消息和响应消息都是由开始行(对于请求消息,开始行就是请求行,对于响应消息,开始行就是状态行), 消息报头(可选),空行(只有 CRLF 的行),消息正文(可选)组成。

​ 每一个报头域都是由 名字+":"+空格+值 组成,消息报头域的名字是大小写无关的。

请求头

​ 请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。

	Referer:该请求头指明请求从哪里来 。

​ 如果是地址栏中输入地址访问的都没有该请求头 地址栏输入地址,通过请求可以看到,此时多了一个 Referer 的请求头,并且后面的值 为该请求从哪里发出。比如:百度竞价,只能从百度来的才有效果,否则不算;通常用来做统计工作、 防盗链。

响应头

​ 响应报头允许服务器传递不能放在状态行中的附加响应信息,以及关于服务器的信息和 对 Request-URI 所标识的资源进行下一步访问的信息。

  • Location : Location响应报头域用于重定向接受者到一个新的位置。

    ​ Location响应报头域,常用在更换域名的时候。

response.sendRedirect("http://www.baidu.com");
  • Refresh:自动跳转(单位是秒),可以在页面通过meta标签实现,也可在后台实现。
<meta http-equiv="refresh" content="3;url=http://www.baidu.com">

4.Tomcat服务器

4.1.什么是Tomcat

​ Tomcat是一个符合JavaEE WEB标准的最小的WEB容器,所有的JSP程序一定要有WEB容器的支持才能运行,而 且在给定的WEB容器里面都会支持事务处理操作。

​ Tomcat是由Apache提供的可以用安装版和解压版,安装版可以在服务中出现一个 Tomcat的服务,免安装没有,开发中使用免安装版。Tomcat简单的说就是一个运行Java的网络服务器,底层是 Socket的一个程序,它也是JSP和Servlet的一个容器。Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项目,由Apache、Sun和其他一些公司及个人共同开发而成。

​ 由于有了 Sun的参与和支持,最新的Servlet和JSP规范总是能在Tomcat中得到体现。因为Tomcat技术先进、性能稳定,而且免费,因而深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服 务器。

​ Tomcat月艮务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用 户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。对于一个初学者来说,可以这样认为,当在一台 机器上配置好Apache服务器,可利用它响应HTML (标准通用标记语言下的一个应用)页面的访问请求。实际上 Tomcat部分是Apache服务器的扩展,但它是独立运行的,所以当你运行tomcat时,它实际上作为一个与Apache 独立的进程单独运行的。

​ 当配置正确时,Apache为HTML页面服务,而Tomcat实际上是在运行J3P页面和Servlet。另外,Tomcat和IIS 等Web月艮务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是 Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache服务器。目前Tomcat最新版本为9.0。

4.2.Tomcat目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WIs0reKD-1613447476980)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210204212430167.png)]

  1. bin:启动和关闭tomcat的bat文件
  2. conf:配置文件server.xml该文件用于配置server相关的信息,比如tomcat启动的端口号,配置主机(Host) ;web.xml文件配置与 web应用(web应用相当于一个web站点);tomcat-user.xml配置用户名密码和相 关权限
  3. lib :该目录放置运行tomcat运行需要的jar包
  4. logs:存放日志,当我们需要查看日志的时候,可以查询信息
  5. webapps:放置我们的web应用
  6. work工作目录:该目录用于存放jsp被访问后生成对应的server文件和.class文件

5.Servlet的实现

​ Servlet 是 Server 与 Applet 的缩写,是服务端小程序的意思。使用 Java 语言编写的服务器端程序,可以像生成动态的 WEB 页,Servlet 主要运行在服务器端,并由服务器调用执行, 是一种按照 Servlet 标准来开发的类。 是 SUN 公司提供的一门用于开发动态 Web 资源的技术。(言外之意:要实现 web 开发,需要实现 Servlet 标准)

​ Servlet 本质上也是 Java 类,但要遵循 Servlet 规范进行编写,没有 main()方法,它的创建、使用、销毁都由Servlet 容器进行管理(如 Tomcat)。(言外之意:写自己的类,不用写 main 方法,别人自动调用)

​ Servlet 是和 HTTP 协议是紧密联系的,其可以处理 HTTP 协议相关的所有内容。这也是 Servlet 应用广泛的原因之一。提供了 Servlet 功能的服务器,叫做 Servlet 容器,其常见容器有很多,如 Tomcat, Jetty, WebLogic Server,WebSphere, JBoss 等等。

5.1.Servlet的实现

package com.xxxx.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;

/**
 * 实现Servlet
 *  1. 创建普通Java类
 *  2. 实现Servlet的规范,继承HttpServlet类
 *  3. 重写service方法,用来处理请求
 *  4. 设置注解,指定访问的路径
 */

//@WebServlet(name = "Servlet01",value = "/ser01")
//@WebServlet(name = "Servlet01",value = {"/ser01","/ser001"})
//@WebServlet(name = "Servlet01",urlPatterns = "/ser01")
//@WebServlet(name = "Servlet01",urlPatterns = {"/ser01","/ser001"})
@WebServlet("/ser01") // 路径中的"/"不要忘记写!
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 打印内容在控制台
        System.out.println("Hello Servlet!");
        // 通过流输出数据到浏览器
        resp.getWriter().write("Hello Servlet!");
    }
}

5.2.Servlet的工作流程

  1. 通过请求头获知浏览器访问的是哪个主机

  2. 再通过请求行获取访问的是哪个一个web应用

  3. 再通过请求行中的请求路径获知访问的是哪个资源

  4. 通过获取的资源路径在配置中匹配到真实的路径,

  5. 服务器会创建servlet对象,(如果是第一次访问时,创建servlet实例,并调用init方法进行初始化操作)

  6. 调用service(request , response)方法来处理请求和响应的操作

  7. 调用service完毕后返回服务器 由服务器讲response缓冲区的数据取出,以http响应的格式发送给浏览器

5.3.Servlet的生命周期

​ Servlet没有main()方法,不能独立运行,它的运行完全由Servlet引擎来控制和调度。所谓生命周期,指的是servlet容器何时创建servlet实例、何时调用其方法进行请求的处理、何时并销毁其实例的整个过程。
​ **·**实例和初始化时机
​ 当请求到达容器时,容器查找该servlet对象是否存在,如果不存在,则会创建实例并进行初始化。

​ **·**就绪/调用/服务阶段
​ 有请求到达容器,容器调用servlet对象的service()方法,处理请求的方法在整个生命周期中可以被多次调用;HttpServlet的service()方法,会依据请求方式来调用doGet()或者doPost()方法。但是,这两个do方法默认情况下,会抛出异常,需要子类去override。
​ **·**销毁时机
​ 当容器关闭时(应用程序停止时),会将程序中的Servlet实例进行销毁。
​ 上述的生命周期可以通过Serviet 中的生命周期方法来观察。在Servlet中有三个生命周期方法不由用户手动调用而是在特定的时机有容器自动调用,观察这三个生命周期方法即可观察到Servlet的生命周期。

init方法,在Servlet实例创建之后执行(证明该Servlet有实例创建了)

public void init(serv1etconfig config) throws servletException {
system.out.print1n("实例创建了...");

service方法,每次有请求到达某个Servlet方法时执行,用来处理请求(证明该Servlet进行服务了)

protected void service(HttpservletRequest req,HttpservletResponse resp)
throws servletException,IOException {
system.out.print1n("服务调用了...");

destroy方法,Servlet 实例销毁时执行(证明该Servlet的实例被销毁了)

public void destroyO {
system.out.print1n("实例销毁了...");

Servlet的生命周期,简单的概括这就分为四个 servlet类加载–>实例化–>服务–>销毁。下面我们描述一下Tomcat 与Servlet是如何工作的,看看下面的时序图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Hs26r8L-1613447476981)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210204215455485.png)]

1.Web Client向Servlet容器(Tomcat)发出Http请求

2.Servlet容器接收web Client的请求

3.Servlet容器创建一个HttpServletRequest对象,将Web Client请求的信息封装到这个对象中

4.Servlet容器创建一个HttpServletResponse对象

5.Servlet容器调HttpServlet对象service方法,把Request与Response作为参数,传给HttpServlet

6.HttpServlet调用HttpServletRequest对象的有关方法,获取 Http请求信息

7.HttpServlet调用HttpServletResponse对象的有关方法,生成响应数据

8.Servlet容器把 HttpServlet的响应结果传给Web Client

6.HttpServlectRequest对象

​ HttpServletRequest对象:主要作用是用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等 都属于客户端发来的信息,service()方法中形参接收的是HttpServletRequest接口的实例化对象,表示该对象主要 应用在HTTP协议上,该对象是由Tomcat封装好传递过来。

​ HttpServletRequest 是 ServletRequest 的子接口,ServletRequest 只有一个子接口,就是 HttpServletRequest。 既然只有一个子接口为什么不将两个接口合并为一个?

​ 从长远上讲:现在主要用的协议是HTTP协议,但以后可能出现更多新的协议。若以后想要支持这种新协议,只需要 直接继承ServletRequest接口就行了。

​ 在HttpServletRequest接口中,定义的方法很多,但都是围绕接收客户端参数的。但是怎么拿到该对象呢?不需 要,直接在Service方法中由容器传入过来,而我们需要做的就是取出对象中的数据,进行分析处理。

6.1.接收请求

常用方法

1.方法

getRequestURL()获取客户端发出请求时的完整URL
getRequestURI()获取请求行中的资源名称部分(项目名称开始)
getQueryString()获取请求行中的参数部分
getMethodf)获取客户端请求方式
getProtocol()**获取HTTP版本号 **
getContextPath()**获取webapp名字 **

获取请求参数

1.方法

getParameter(name)获取指定名称的参数
getParameterValues(String name)获取指定名称参数的所有值

2.示例

/**
 * 获取请求
 */
@WebServlet("/s01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /* 常用方法 */
        // 获取请求时的完整路径 (从http开始,到"?"前面结束)
        String url = request.getRequestURL() + "";
        System.out.println("获取请求时的完整路径:" + url);
        
        // 获取请求时的部分路径 (从项目的站点名开始,到"?"前面结束)
        String uri = request.getRequestURI();
        System.out.println("获取请求时的部分路径:" + uri);
        
        // 获取请求时的参数字符串 (从"?"后面开始,到最后的字符串)
        String  queryString = request.getQueryString();
        System.out.println("获取请求时的参数字符串:" + queryString);
        
        // 获取请求方式 (GET和POST)
        String method = request.getMethod();
        System.out.println("获取请求方式:" + method);
        
        // 获取当前协议版本 (HTTP/1.1)
        String prototol = request.getProtocol();
        System.out.println("获取当前协议版本:" + prototol);
        
        // 获取项目的站点名 (项目对外访问路径)
        String webapp = request.getContextPath(); // 上下文路径
        System.out.println("获取项目的站点名:" + webapp);


        /* 获取请求的参数 */
        // 获取指定名称的参数值,返回字符串 (重点!!!)
        String uname = request.getParameter("uname");
        String upwd = request.getParameter("upwd");
        System.out.println("uname:" + uname + ",upwd:" + upwd);

        // 获取指定名称的参数的所有参数值,返回字符串数组 (用于复选框传值)
        String[] hobbys = request.getParameterValues("hobby");
        // 判断数组是否为空
        if (hobbys != null && hobbys.length > 0) {
            for (String hobby: hobbys) {
                System.out.println("爱好:" + hobby);
            }
        }
    }
}

6.2.接收乱码问题

​ 由于现在的 request 属于接收客户端的参数,所以必然有其默认的语言编码,主要是由于在解析过程中默认使用的编码方式为 ISO-8859-1(此编码不支持中文),所以解析时一定会出现乱码。要想解决这种乱码问题,需要设置 request 中的编码方式,告诉服务器以何种方式来解析数据。或者在接收到乱码数据以后,再通过相应的编码格式还原。

方式一:

request.setcharacterEncoding("UTF-8") ;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sx2pZ0Jg-1613447476981)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210205223034639.png)]

方式二:

new string(request.getParameter(name).getBytes("ISO-8859-1"), "UTF-8"");

​ 借助了String 对象的方法,该种方式对任何请求有效,是通用的。

Tomcat8起,以后的GET方式请求是不会出现乱码的。

6.3.接收转发

​ 请求转发,是一种服务器的行为,当客户端请求到达后,服务器进行转发,此时会将请求对象进行保存,地址栏中的URL 地址不会改变,得到响应后,服务器端再将响应发送给客户端,从始至终只有一个请求发出。实现方式如下,达到多个资源协同响应的效果。

/**
 * 请求转发跳转
 *    request.getRequestDispatcher(url).forward(request,response);
 *   可以让请求从服务端跳转到客户端(或跳转到指定Servlet)
 *
 *   特点:
 *      1. 服务端行为
 *      2. 地址栏不发生改变
 *      3. 从始至终只有一个请求
 *      4. request数据可以共享
 **/
@WebServlet("/s03")
public class Servlet03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 接收客户端请求的参数
        String uname = request.getParameter("uname");
        System.out.println("Servlet03 uname :" + uname);

        // 请求转发跳转到Servlet04
         request.getRequestDispatcher("s04").forward(request,response);
        
        // 请求转发跳转到jsp页面
         request.getRequestDispatcher("login.jsp").forward(request,response);
        
        // 请求转发跳转到html页面
        request.getRequestDispatcher("login.html").forward(request,response);
    }
}

6.4.request作用域

通过该对象可以在一个请求中传递数据,作用范围:在一次请求中有效,即服务器跳转有效。

/**
 *  request作用域
 *      通过该对象可以在一个请求中传递数据,作用范围:在一次请求中有效,即服务器跳转有效。(请求转发跳转时有效)
 *      // 设置域对象内容
 *      request.setAttribute(String name, Object value);
 *      // 获取域对象内容
 *      request.getAttribute(String name);
 *      // 删除域对象内容
 *      request.removeAttribute(String name);
 */
@WebServlet("/s05")
public class Servlet05 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Servlet05...");

        // 设置域对象内容
       request.setAttribute("name","admin");
       request.setAttribute("age",18);
       List<String> list = new ArrayList<>();
       list.add("aaa");
       list.add("bbb");
       request.setAttribute("list",list);

       // 请求转发跳转到Servlet06
        request.getRequestDispatcher("s06").forward(request,response);
        
       // 请求转发跳转到jsp 并通过域对象传递数据
       request.getRequestDispatcher("index.jsp").forward(request,response);
    }
}

@WebServlet("/s06")
public class Servlet06 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Servlet06...");
        // 获取域对象内容
        String name = (String) request.getAttribute("name");
        System.out.println("name:" + name);
        Integer age  = (Integer) request.getAttribute("age");
        System.out.println("age:" + age);
        List<String> list = (List<String>) request.getAttribute("list");
        System.out.println(list.get(0));

    }
}

.JSP文件
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <h2>index页面</h2>
  <%-- 如果要在jsp中写Java代码,需要写在脚本段中--%>
  <%
    // 获取域对象
    // 获取域对象内容
    String name = (String) request.getAttribute("name");
    System.out.println("name:" + name);
    Integer age  = (Integer) request.getAttribute("age");
    System.out.println("age:" + age);
    List<String> list = (List<String>) request.getAttribute("list");
    //System.out.println(list.get(0));

  %>
  </body>
</html>

​ request域对象中的数据在一次请求中有效,则经过请求转发,request域中的数据依然存在,则在请求转发的过程 中可以通过request来传输/共享数据。

7.HttpServlectResponse对象

​ Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的 request 对象和代表响应的response 对象。

​ request 和 response 对象代表请求和响应:获取客户端数据,需要通过 request 对象;向客户端输出数据,需要通过 response 对象

​ HttpServletResponse 的主要功能用于服务器对客户端的请求进行响应,将 Web 服务器处理后的结果返回给客户端。service()方法中形参接收的是 HttpServletResponse 接口的实例化对象,这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。

7.1.响应数据

​ 接收到客户端请求后,可以通过 HttpSerMletResponse,对象直接进行响应,响应时需要获取输出流。有两种形式:

getWrite() 获取字符流(只能响应回字符)

getOutoutStream() 获取字节流(能响应—切数据)

响应回的数据到客户端被浏览器解析。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RD8EuUJa-1613447476982)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210205223932659.png)]

//字符输出流
Printwriter writer = response.getwriter();writer.write("He11o");
writer.write("<h2>He1lo</h2>");
//字节输出流
servletoutputstream out =response.getoutputstream();
out.write("He11o".getBytes();
out.write("<h2>He11o</h2>".getBytes();
@WebServlet("/s01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /* getWriter() 字符输出流 (输出字符串) */
        // 获取字符输出流
        PrintWriter writer = response.getWriter();
        // 输出数据
        writer.write("Hello");
        
        /* getOutputStream()   字节输出流 (输出一切数据) */
        // 得到字节输出流
        ServletOutputStream out = response.getOutputStream();
        // 输出数据
        out.write("Hi".getBytes());
    }
}

设置响应类型,默认是字符串

/设置响应MIME类型
response.setHeader ("content-type","text/html"); // htm1
7.2.响应乱码问题

在响应中,如果我们响应的内容中含有中文,则有可能出现乱码。这是因为服务器响应的数据也会经过网络传输,服 务器端有一种编码方式,在客户端也存在一种编码方式,当两端使用的编码方式不同时则出现乱码。

/**
 * 字符流响应数据
 *  乱码原因:
 *      对于 getWriter()获取到的字符流,响应中文必定出乱码,由于服务器端在进行编码时默认会使用 ISO-8859-1 格式的编码,该编码方式并不支持中文。
 *  乱码解决:
 *      1. 设置服务端的编码格式
 *         response.setCharacterEncoding("UTF-8");
 *      2. 设置客户端的编码格式
 *         response.setHeader("content-type","text/html;charset=UTF-8");
 *     总结:设置客户端和服务端的编码都支持中文,且保持一致。
 
 *      同时设置客户端和服务端的编码格式
 *      response.setContentType("text/html;charset=UTF-8");
 */
@WebServlet("/s02")
public class Servlet02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 设置服务端的编码格式
        response.setCharacterEncoding("UTF-8");
        
        // 设置客户端的编码格式和响应的MIME类型
        response.setHeader("content-type","text/html;charset=UTF-8");*/

       // 同时设置客户端和服务端的编码格式
        response.setContentType("text/html;charset=UTF-8");

        /* getWriter() 字符输出流 (输出字符串) */
       // 获取字符输出流
        PrintWriter writer = response.getWriter();
        // 输出数据
        writer.write("<h2>你好</h2>");
    }
}

getOutputStream()字节乱码

​ 对于 getOutputStream()方式获取到的字节流,响应中文时,由于本身就是传输的字节, 所以此时可能出现乱码, 也可能正确显示。当服务器端给的字节恰好和客户端使用的编码方式一致时则文本正确显示,否则出现乱码。无论如 何我们都应该准确掌握服务器和客户端使用的是那种编码格式,以确保数据正确显示。

/**
 * 字节流响应数据
 *  乱码问题:
 *      对于 getOutputStream()方式获取到的字节流,响应中文时,由于本身就是传输的字节, 所以此时可能出现乱码,也可能正确显示。当服务器端给的字节恰好和客户端使用的编码方式一致时则文本正确显示,否则出现乱码。
 *  乱码解决:
 *        1. 设置服务端的编码格式
 *           response.setCharacterEncoding("UTF-8");
 *        2. 设置客户端的编码格式
 *           response.setHeader("content-type","text/html;charset=UTF-8");
 *       总结:设置客户端和服务端的编码都支持中文,且保持一致。
 
 *        同时设置客户端和服务端的编码格式
 *        	 response.setContentType("text/html;charset=UTF-8");
 */
@WebServlet("/s03")
public class Servlet03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 同时设置客户端和服务端的编码格式
        response.setContentType("text/html;charset=UTF-8");
        /* getOutputStream()   字节输出流 (输出一切数据) */
        // 得到字节输出流
        ServletOutputStream out = response.getOutputStream();
        // 输出数据
        out.write("<h2>你好</h2>".getBytes("UTF-8"));
    }
}

总结:要想解决响应的乱码,只需要保证使用支持中文的编码格式。并且保证服务器端 和客户端使用相同的编码方式即可

7.3.重定向

​ 重定向是一种服务器指导,客户端的行为。客户端发出第一个请求,被服务器接收处理后,服务器会进行响应,在响 应的同时,服务器会给客户端一个新的地址(下次请求的地址 response.sendRedirect(url);),当客户端接收到响应后,会立刻、马上、自动根据服务器给的新地址发起第二个请求,服务器接收请求并作出响应,重定向完成。

/**
 * 重定向
 *   服务端指导,客户端行为
 *   存在两次请求
 *   地址栏会发生改变
 *   request对象不共享
 */
@WebServlet("/s04")
public class Servlet04 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Servlet04...");

        // 接收参数
        String uname = request.getParameter("uname");
        System.out.println("Servlet04 " + uname);

        // 重定向跳转到s05
        response.sendRedirect("s05");
    }
}

@WebServlet("/s05")
public class Servlet05 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Servlet05...");

        // 接收参数
        String uname = request.getParameter("uname");
        System.out.println("Servlet05 " + uname);

    }
}

从描述中可以看出重定向当中有两个请求存在,并且属于客户端行为。

通过观察浏览器我们发现第一次请求获得的响应码为 302,并且含有一个 location 头信息。并且地址栏最终看到的地址是和第一次请求地址不同的,地址栏已经发生了变化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Knw1bc7Z-1613447476982)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210205225044795.png)]

7.4.请求转发与重定向的区别

请求转发和重定向比较

请求转发(req.getRsqiiestDispatcher().fQrw3rd())重定向(resp.sendRedirect())
一次请求.数据在request域中共享两次请求.request域中数据不共享
服务器端行为客户端行为
地址栏不发生变化地址栏发生变化
绝对地址定位到站点后绝对地址可写到http://

两者都可进行跳转,根据实际需求选取即可。

/**
 * 重定向与请求转发的区别
 *    1. 请求转发的地址栏不会发生改变,重定向的地址栏会发生改变
 *    2. 请求转发只有一次请求,重定向有两次请求
 *    3. 请求转发时request对象可共享,重定向时request对象不共享
 *    4. 请求转发是服务端行为,重定向是客户端行为
 *    5. 请求转发时的地址只能是当前站点下(当前项目)的资源,重定向时地址可以是任何地址
 *
 */
@WebServlet("/s06")
public class Servlet06 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Servlet06...");

        // 接收参数
        String uname = request.getParameter("uname");
        System.out.println(uname);

        // 设置request域对象
        request.setAttribute("upwd","123456");

        // 请求转发
        request.getRequestDispatcher("index.jsp").forward(request,response);

        // 重定向
        response.sendRedirect("index.jsp");

        // 重定向跳转到百度
        response.sendRedirect("http://www.baidu.com");

        // 请求转发到百度(404)
        request.getRequestDispatcher("http://www.baidu.com").forward(request,response);

    }
}

.JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
      <!-- Java脚本段 -->
      <%
          // 获取参数
        String uname = request.getParameter("uname");
        // 获取request作用域
        String upwd = (String) request.getAttribute("upwd");

        // 输出内容
        out.print(uname);
        out.print("--------");
        out.print(upwd);

      %>
  </body>
</html>

8.Cookie对象

​ Cookie是浏览器提供的一种技术,通过服务器的程序能将一些只须保存在客户端,或者在客户端进行处理的数据,放在本地的计算机上,不需要通过网络传输,因而提高网页处理的效率,并且能够减少服务器的负载,但是由于Cookie是服务器端保存在客户端的信息,所以其安全性也是很差的。例如常见的记住密码则可以通过
Cookie来实现。

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

​ Cookie的格式:键值对用“="链接,多个键值对间通过”;“隔开。

8.1. Cookie的创建的发送

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

/**
 * Cookie的创建和发送
 */
@WebServlet("/cook01")
public class Cookie01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Cookie的创建
        Cookie cookie = new Cookie("name","admin");
        // 发送(响应)Cookie对象
        response.addCookie(cookie);
    }
}

按F12查看

Response Headers
HTTP/1.1 200
set-cookie: uname=zhangsan

8.2. Cookie的获取

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

/**
 *  Cookie的获取
 *  返回的是cookie数组
 */
@WebServlet("/cook02")
public class Cookie02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       // 获取cookie数组
        Cookie[] cookies = request.getCookies();
        // 判断cookie是否为空
        if (cookies != null && cookies.length > 0) {
            // 遍历cookie数组
            for (Cookie cookie: cookies ) {
                // 获取cookie的名称和值
                String name = cookie.getName();
                String value = cookie.getValue();
                System.out.println("名称:" +name + ",值:" + value);
            }
        }
    }
}

8.3. Cookie设置到期时间

​ 除了 Cookie 的名称和内容外,我们还需要关心一个信息,到期时间,到期时间用来指定该 cookie 何时失效。默认为当前浏览器关闭即失效。我们可以手动设定 cookie 的有效时间(通过到期时间计算),通过 setMaxAge(int time);方法设定 cookie 的最大有效时间,以秒为单位。

/**
 * Cookie的到期时间
 *      负整数
 *          表示只在浏览器内存中存活,关闭浏览器失效。(默认值-1)
 *      正整数
 *          表示存活指定秒数。单位秒。
 *      0
 *          表示删除cookie
 */
@WebServlet("/cook03")
public class Cookie03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        /* 到期时间:负整数 (默认值-1.表示只在浏览器内存中存活,关闭浏览器失效)*/
        Cookie cookie = new Cookie("uname1","zhangsan");
        cookie.setMaxAge(-1); // 关闭浏览器失效
        response.addCookie(cookie);


        /* 到期时间:正整数 (表示存活指定秒数,会将数据存在磁盘中) */
        Cookie cookie2 = new Cookie("uname2","lisi");
        cookie2.setMaxAge(30); // 存活30秒
        response.addCookie(cookie2);


        /* 到期时间:零 (表示删除cookie) */
        Cookie cookie3 = new Cookie("uname3","wangwu");
        cookie3.setMaxAge(0); // 删除cookie
        
        // 发送Cookie对象
        response.addCookie(cookie3);
    }
}

到期时间的取值

  • 负整数

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

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

  • 正整数

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

表示 cookie 对象可存活指定的秒数。当生命大于 0 时,浏览器会把 Cookie 保存到硬盘上,就算关闭浏览器, 就算重启客户端电脑,cookie 也会存活相应的时间。

若为 0,表示删除该 cookie。

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

8.4. Cookie的注意点

/**
 * Cookie的注意点
 *  1. Cookie只在当前浏览器中有效(不夸浏览器和电脑)
 *  2. Cookie不能存中文
 *      如果一定要存中文,则需要通过URLEncoder.encode()方法进行编码,获取时通过URLDecoder.decode()方法进行解码
 *  3. 如果出现同名的Cookie对象,则会覆盖
 *  4. Cookie的存储数量是有上限的,不同浏览器的上限不同。Cookie存储的大小是有效的,在4kb左右
 */
@WebServlet("/cook04")
public class Cookie04 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

       /* Cookie存中文 */
        String name = "姓名";
        String value = "张三";

        // 将中文通过URLEncoder进行编码
        name = URLEncoder.encode(name);
        value = URLEncoder.encode(value);

        // 创建Cookie对象
        Cookie cookie = new Cookie(name,value);
        // 响应Cookie
        response.addCookie(cookie);

        // 获取Cookie时,通过URLDecoder进行解码
        Cookie[] cookies = request.getCookies();
        // 判断非空
        if (cookies != null && cookies.length > 0) {
            // 遍历
            for (Cookie cook : cookies ) {
                // 解码
                System.out.println(URLDecoder.decode(cook.getName()));
                System.out.println(URLDecoder.decode(cook.getValue()));
            }
        }

        // 将原来已有的Cookie对象重新设置
        Cookie cookie2 = new Cookie("name","zhangsan");
        response.addCookie(cookie2);
    }
}

8.5. Cookie的路径

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

如果我们设置path,如果当前访问的路径包含了cookie的路径(当前访问路径在cookie路径基础上要比cookie的范围小)cookie就会加载到request对象之中。cookie的路径指的是可以访问该cookie的顶层目录,该路径的子路径也可以访问该cookie

/**
 * Cookie的路径
 *      1. 当前服务器下任何项目的任意资源都可获取Cookie对象
 *          设置路径为"/",表示在当前服务器下任何项目都可访问到Cookie对象
 *      2. 当前项目下的资源可获取Cookie对象 (默认不设置Cookie的path)
 *          默认不设置Cookie的path 或者 设置为当前站点名
 *      3. 指定项目下的资源可获取Cookie对象
 *      4. 指定目录下的资源可获取Cookie对象
 *
 *      总结:只有访问的路径中包含Cookie对象的path值,才可以获取到该cookie对象
 *
 */
@WebServlet("/cook05")
public class Cookie05 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        /* 当前服务器下任何项目的任意资源都可获取Cookie对象 */
        Cookie cookie01 = new Cookie("cookie01","cookie01");
        // 设置路径为"/",表示在当前服务器下任何项目都可访问到Cookie对象
        cookie01.setPath("/");
        response.addCookie(cookie01);

        /* 当前项目下的资源可获取Cookie对象 (默认不设置Cookie的path) */
        Cookie cookie02 = new Cookie("cookie02","cookie02");
        // 默认不设置Cookie的path 或者 设置为当前站点名
        cookie02.setPath("/sc04");
        response.addCookie(cookie02);

        /* 指定项目下的资源可获取Cookie对象 */
        Cookie cookie03 = new Cookie("cookie03","cookie03");
        // 设置指定项目的站点名
        cookie03.setPath("/sr03");
        response.addCookie(cookie03);

        /* 指定目录下的资源可获取Cookie对象 */
        Cookie cookie04 = new Cookie("cookie04","cookie04");
        // 设置指定项目的站点名
        cookie04.setPath("/sc04/cook02");
        response.addCookie(cookie04);
    }
}

9.HttpSession对象

HttpSession对象是 javax.servlet.http.HttpSession 的实例,该接口并不像 HttpServletRequest 或HttpServletResponse 还存在一个父接口,该接口只是一个纯粹的接口。这因为 session 本身就属于 HTTP 协议的范畴。

​ 对于服务器而言,每一个连接到它的客户端都是一个 session,servlet 容器使用此接口创建 HTTP 客户端和 HTTP 服务器之间的会话。会话将保留指定的时间段,跨多个连接或来自用户的页面请求。一个会话通常对应于一个用户,该用户可能多次访问一个站点。可以通过此接口查看和操作有关某个会话的信息,比如会话标识符、创建时间和最后一次访问时间。在整个 session 中,最重要的就是属性的操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xa84lzvO-1613447476983)(C:\Users\Barry\AppData\Roaming\Typora\typora-user-images\image-20210206125607630.png)]

​ session 无论客户端还是服务器端都可以感知到,若重新打开一个新的浏览器,则无法取得之前设置的 session,因为每一个 session 只保存在当前的浏览器当中,并在相关的页面取得。

Session 的作用就是为了标识一次会话,或者说确认一个用户;并且在一次会话(一个用户的多次请求)期间共享数据。我们可以通过 request.getSession()方法,来获取当前会话的 session 对象。

/**
 * Session对象
 *  session对象的获取
 *      request.getSession()
 *      当获取session对象时,会先判断session对象是否存在,如果存在,则获取session对象;如果不存在,则创建session对象
 *  常用方法
 *      获取session的会话标识符      getId()
 *      获取session的创建时间        getCreationTime()
 *      获取最后一次访问时间         getLastAccessedTime()
 *      判断是否是新的session对象    isNew()
 */
@WebServlet("/ser01")
public class Session01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 如果session对象存在,则获取;如果session对象不存在,则创建
        HttpSession session = request.getSession();

        // 获取session的会话标识符
        String id = session.getId();
        System.out.println(id);
        
        // 获取session的创建时间
        System.out.println(session.getCreationTime());
        
        // 获取最后一次访问时间
        System.out.println(session.getLastAccessedTime());
        
        // 判断是否是新的session对象
        System.out.println(session.isNew());
    }
}

9.1.标识符JSESSIONID

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

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

​ 这里提到一个叫做 JSESSIONID 的 cookie,这是一个比较特殊的 cookie,当用户请求服务器时,如果访问了session,则服务器会创建一个名为 JSESSIONID,值为获取到的 session(无论是获取到的还是新创建的)的sessionId 的 cookie 对象,并添加到 response 对象中,响应给客户端,有效时间为关闭浏览器。

​ 所以 Session 的底层依赖 Cookie 来实现。

9.2.session域对象

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

/**
 * Session域对象
 *      setAttribute()      设置域对象
 *      getAttribute()      获取域对象
 *      removeAttribute()   移除域对象
 *
 *      请求转发
 *          一次请求
 *          request作用域有效
 *          session作用域有效
 *      重定向
 *          两次请求
 *          request作用域无效
 *          session作用域有效
 */
@WebServlet("/ser02")
public class Session02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        /* session域对象 */
        // 获取session对象
        HttpSession session = request.getSession();
        // 设置session域对象
        session.setAttribute("uname","admin");
        session.setAttribute("upwd","123456");
        // 移除session域对象
        session.removeAttribute("upwd");

        /* request域对象 */
        request.setAttribute("name","zhangsan");

        // 请求转发跳转到jsp页面
        request.getRequestDispatcher("index.jsp").forward(request,response);
        // 重定向跳转到index.jsp页面
        response.sendRedirect("index.jsp");

    }
}

.JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>获取域对象</title>
  </head>
  <body>
  <%
    // 获取session域对象
    String uname = (String) request.getSession().getAttribute("uname");
    String upwd = (String) request.getSession().getAttribute("upwd");

    // 获取request对象
    String name = (String) request.getAttribute("name");

    out.print("uname:" + uname + ",upwd:" + upwd + ",name:" + name );

  %>
  </body>
</html>

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

9.3.session对象的销毁

/**
 * Session对象的销毁
 *      1. 默认到期时间
 *          Tomcat 中 session 默认的存活时间为 30min,即你不操作界面的时间,一旦有操作,session 会重新计时。
 *          可以在 Tomcat 中的 conf 目录下的 web.xml 文件中进行修改。
 *          session 默认的最大不活动时间。单位:分钟。
 *          <session-config>
 * 	            <session-timeout>30</session-timeout>
 *          </session-config>
 *      2. 手动设置到期时间
 *          可以在程序中自己设定 session 的生命周期。
 *          通过session.setMaxInactiveInterval(int) 来设定 session 的最大不活动时间,单位为秒。
 *          可以通过 getMaxInactiveInterval() 方法来查看当前 Session 对象的最大不活动时间。
 *      3. 立即销毁
 *          可以通过 session.invalidate() 方法让 session 立刻失效
 *      4. 关闭浏览器
 *          session底层依赖cookie,cookie对象默认只在浏览器内存中存活,关闭浏览器即失效
 *      5. 关闭服务器
 *          当关闭服务器时,session 销毁。
 */
@WebServlet("/ser03")
public class Session03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        /* session域对象 */
        // 获取session对象
        HttpSession session = request.getSession();
        // 设置session域对象
        session.setAttribute("uname","admin");

        // Session 对象的最大不活动时间
        System.out.println("Session 对象的最大不活动时间:" + session.getMaxInactiveInterval());
        // 修改最大不活动时间
        session.setMaxInactiveInterval(15); // 15秒失效*/

        // 立即销毁  可以通过 session.invalidate() 方法让 session 立刻失效
        session.invalidate();

    }
}

10.ServletContext对象

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

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

/**
 * 获取ServletContext对象
 *  1. 通过request对获取
 *  2. 通过session对象获取
 *  3. 通过ServletConfig对象获取
 *  4. 在Servlet中直接获取
 *
 *  常用方法
 *      1. 获取当前服务器的版本信息
 *      2. 获取项目的真实路径
 *
 */
@WebServlet("/s01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 通过request对获取
        ServletContext servletContext1 = request.getServletContext();

        // 通过session对象获取
        ServletContext servletContext2 = request.getSession().getServletContext();

        // 通过ServletConfig对象获取
        ServletContext servletContext3 = getServletConfig().getServletContext();

        // 直接获取
        ServletContext servletContext4 = getServletContext();

        // 常用方法
        // 1. 获取当前服务器的版本信息
        String serverInfo = request.getServletContext().getServerInfo();
        System.out.println("获取当前服务器的版本信息: " + serverInfo);
        
        // 2. 获取项目的真实路径
        String realPath = request.getServletContext().getRealPath("/");
        System.out.println("获取项目的真实路径:" + realPath);
    }
}

ServletContext域对象

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

//获取servletcontext对象
servletcontext servletcontext = request.getservletcontext();
//设置域对象
servletcontext.setAttribute("name" , "zhangsan");
//获取域对象
String name = (String) servletcontext.getAttribute("name");
//移除域对象
servletcontext.removeAttribute("name") ;

Servlet的三大域对象

​ 1.request域对象
​ 在一次请求中有效。请求转发有效,重定向失效。

​ 2.session域对象
​ 在一次会话中有效。请求转发和重定向都有效,session销毁后失效。

​ 3.servletContext域对象
在整个应用程序中有效。服务器关闭后失效。

11.文件的上传与下载

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

文件上传

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

/**
 * 文件上传
 *      使用注解 @MultipartConfig将一个 Servlet 标识为支持文件上传。
 *      Servlet 将 multipart/form-data 的 POST 请求封装成 Part对象,通过 Part 对上传的文件进行操作。
 */

@WebServlet("/uploadServlet")
@MultipartConfig // 如果是文件上传,必须要设置该注解!!!
public class UploadServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("文件上传...");
        // 设置请求的编码格式
        request.setCharacterEncoding("UTF-8");

        // 获取普通表单项(获取参数)
        String uname = request.getParameter("uname"); // 表单中表单元素的name属性值
        System.out.println("uname:" + uname);

        // 获取Part对象 (Servlet 将 multipart/form-data 的 POST 请求封装成 Part对象)
        Part part = request.getPart("myfile"); // 表单中file文件域的name属性值
        // 通过Part对象得到上传的文件名
        String fileName = part.getSubmittedFileName();
        System.out.println("上传文件名:" + fileName);
        // 得到文件存放的路径
        String filePath = request.getServletContext().getRealPath("/");
        System.out.println("文件存放的路径:" + filePath);
        // 上传文件到指定目录
        part.write(filePath +"/"+ fileName);

    }
}

前台页面

​ 在做文件上传的时候,会有一个上传文件的界面,首先我们需要一个表单,并且表单的请求方式为POST;其次我们的 form表单的enctype必须设为"multipart/form-data",即enctype=“multipart/form-data”,意思是设置表单的类型为文件上传表单。默认情况下这个表单类型是"application/x-www-form-urlencoded",不能用于文件上传。只有使用了multipart/form-data才能完整地传递文件数据。

 <!--
        文件上传
            1.准备表单
            2.设置表单的提交类型为POST请求method="post"
            3.设置表单类型为文件上传表单 enctype="multipart/form-data"
            4.设置文件提交的地址
            5.准备表单元素
                1.普通的表单项type="text"
                2.文件项type="file"
            6.设置表单元素的name属性值(表单提交一定要设置表单元素的name属性值,否则后台无法接受数据!
 -->

<form method="post" enctype="multipart/form-data" action="uploadServlet">
    姓名:<input type="text" name="uname" > <br>
    文件:<input type="file" name="myfile"> <br>
    <! --button默认的类型是提交类型 type="submit" -->
    <button>提交</button>
</form>

11.2.文件下载

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

11.2.1.超链接下载

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

默认下载

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

指定download属性下载

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

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

11.2.2.后台实现下载
package com.xxxx.servlet;

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;
import java.io.InputStream;

/**
 * 文件下载
 *
 *  1. 需要通过 response.setContentType 方法设置 Content-type 头字段的值, 为浏览器无法使用某种方式或激活某个程序来处理的 MIME 类型,例 如 "application/octet-stream" 或 "application/x-msdownload" 等。
 *  2. 需要通过 response.setHeader 方法设置 Content-Disposition 头的值 为 "attachment;filename=文件名"
 *  3. 读取下载文件,调用 response.getOutputStream 方法向客户端写入附件内容。
 */
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("文件下载...");

        // 设置请求的编码格式
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        // 获取参数 (得到要下载的文件名)
        String fileName = request.getParameter("fileName");
        // 参数的非空判断  trim():去除字符串的前后空格
        if (fileName == null || "".equals(fileName.trim())) {
            response.getWriter().write("请输入要下载的文件名!");
            response.getWriter().close();
            return;
        }
        // 得到图片存放的路径
        String path = request.getServletContext().getRealPath("/download/");
        // 通过路径得到file对象
        File file = new File(path + fileName);
        // 判断文件对象是否存在并且是一个标准文件
        if (file.exists() && file.isFile()) {
            // 设置响应类型 (浏览器无法使用某种方式或激活某个程序来处理的 MIME 类型)
            response.setContentType("application/x-msdownload");
            // 设置响应头
            response.setHeader("Content-Disposition", "attachment;filename=" + 				fileName);
            // 得到file文件输入流
            InputStream in = new FileInputStream(file);
            // 得到字节输出流
            ServletOutputStream out = response.getOutputStream();
            // 定义byte数组
            byte[] bytes = new byte[1024];
            // 定义长度
            int len = 0;
            // 循环输出
            while ((len = in.read(bytes)) != -1){
            	// 输出
                out.write(bytes,0,len);
            }
            // 关闭资源
            out.close();
            in.close();
        } else {
            response.getWriter().write("文件不存在,请重试!");
            response.getWriter().close();
        }

    }
}
  // 获取参数 (得到要下载的文件名)
    String fileName = request.getParameter("fileName");
    // 参数的非空判断  trim():去除字符串的前后空格
    if (fileName == null || "".equals(fileName.trim())) {
        response.getWriter().write("请输入要下载的文件名!");
        response.getWriter().close();
        return;
    }
    // 得到图片存放的路径
    String path = request.getServletContext().getRealPath("/download/");
    // 通过路径得到file对象
    File file = new File(path + fileName);
    // 判断文件对象是否存在并且是一个标准文件
    if (file.exists() && file.isFile()) {
        // 设置响应类型 (浏览器无法使用某种方式或激活某个程序来处理的 MIME 类型)
        response.setContentType("application/x-msdownload");
        // 设置响应头
        response.setHeader("Content-Disposition", "attachment;filename=" + 				fileName);
        // 得到file文件输入流
        InputStream in = new FileInputStream(file);
        // 得到字节输出流
        ServletOutputStream out = response.getOutputStream();
        // 定义byte数组
        byte[] bytes = new byte[1024];
        // 定义长度
        int len = 0;
        // 循环输出
        while ((len = in.read(bytes)) != -1){
        	// 输出
            out.write(bytes,0,len);
        }
        // 关闭资源
        out.close();
        in.close();
    } else {
        response.getWriter().write("文件不存在,请重试!");
        response.getWriter().close();
    }

}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值