Request和Response相关介绍

发布于:2025-07-21 ⋅ 阅读:(18) ⋅ 点赞:(0)

Request 和 Response 是什么?

  • Request(请求对象):用来接收浏览器发过来的数据。
  • Response(响应对象):用来把服务器处理后的结果返回给浏览器。


1. request 的作用(获取请求数据)

  • 当你在浏览器访问网页或提交表单时,浏览器会把你的请求(比如你输入的账号、密码等)通过HTTP协议发给服务器(Tomcat)。
  • 这些数据会被Tomcat解析,然后封装到request对象里
  • 在Servlet代码里,你可以通过request对象获取到这些数据,比如获取表单里的用户名、密码等。
  • 拿到数据后,服务器就可以根据这些信息做后续的业务处理,比如登录验证。


2. response 的作用(设置响应数据)

  • 服务器处理完业务后,需要把结果返回给浏览器,比如返回一个网页、提示信息等。
  • 这些返回的数据会被封装到response对象里
  • Tomcat会把response对象里的内容解析出来,按照HTTP协议的格式发回给浏览器。
  • 浏览器收到后,就会把内容展示出来,比如显示网页、弹出提示等。


通过一个案例来初步体验下request和response对象的使用。

写一个表单,请求方式为GET,当我们输入不同的username,并点击提交时,界面上就会出现username,欢迎访问

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="http://localhost:8080/web_demo_war_exploded/demo" method="get">
  用户名:<input type="text" name="username">
  <input type="submit" value="提交">
</form>
</body>
</html>

启动成功后就可以通过浏览器来访问,并且根据传入参数的不同就可以在页面上展示不同的内容: 

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

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("username");
        //避免中文乱码问题
        resp.setHeader("content-type","text/html;charset=utf-8");
        resp.getWriter().write("<h1>"+name+",欢迎访问<h1>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post...");
    }
}

总结一句话

  • request:用来接收浏览器发来的数据。
  • response:用来把服务器的处理结果返回给浏览器。

Request继承体系

一、Request继承体系是什么?

在Java Web开发中,Request对象用来封装浏览器发来的请求数据。

但这个Request对象其实有一套“继承体系”,也就是有父接口、子接口和实现类。

1. ServletRequest(接口)

  • Java官方提供的最基础的请求对象接口,定义了所有请求对象都应该有的方法。
  • 就像“交通工具”这个大类,规定了所有交通工具都应该有“启动、停止”等功能。

2. HttpServletRequest(接口)

  • 继承自ServletRequest,专门针对HTTP协议的请求对象接口,增加了很多和HTTP相关的方法(如获取请求头、参数等)。
  • 就像“汽车”是“交通工具”的一种,除了启动、停止,还能“开空调、开车窗”等。

3. RequestFacade(实现类)

  • 这是Tomcat服务器自己写的一个实现类,真正实现了HttpServletRequest接口的所有方法。
  • 就像“宝马汽车”是“汽车”的一种,宝马公司造出来的具体汽车。

二、Tomcat处理请求的过程

  1. 浏览器发请求(比如你在Chrome里访问一个网站)。
  2. Tomcat服务器接收到请求,会先解析请求数据。
  3. Tomcat用RequestFacade类,把请求数据封装成一个Request对象。
  4. Tomcat把Request对象传给Servlet的service方法,让你的Java代码可以方便地获取请求里的各种信息。

三、生活中的实际例子

例子1:快递包裹

  • ServletRequest:快递包裹的“标准接口”,规定所有快递包裹都要有“收件人、寄件人、内容物”等信息。
  • HttpServletRequest:专门针对“顺丰快递”的包裹,除了基本信息,还多了“顺丰单号、保价服务”等特殊功能。
  • RequestFacade:顺丰公司实际生产出来的某个快递包裹,里面装着你的快递。

例子2:公司员工

  • ServletRequest:公司规定的“员工”标准(必须有姓名、工号等)。
  • HttpServletRequest:技术部员工,除了基本信息,还要有“技术等级、编程语言”等。
  • RequestFacade:张三,技术部的一个具体员工。

ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,那么方法里的HttpServletRequest参数是从哪儿来的呢?

//接口无法创建对象,那么这个参数是哪儿来的呢
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String name = req.getParameter("username");
    resp.setHeader("content-type","text/html;charset=utf-8");
    resp.getWriter().write("<h1>"+name+",欢迎访问<h1>");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("post...");
}

一、接口不能创建对象,参数对象从哪来的?

  • ServletRequest 和 HttpServletRequest 都是接口,接口是不能直接用 new 创建对象的。
  • 但是在 Servlet 的 doGet、doPost 方法里,参数却是 HttpServletRequest req,这个对象是谁创建的呢?

二、Request对象的创建过程

  1. Tomcat 服务器收到浏览器请求后,会先解析请求数据。
  2. Tomcat 会用 RequestFacade 这个类,来实现 HttpServletRequest 接口,把请求数据封装成一个对象。
  3. Tomcat 再把这个对象传给你的 Servlet 的 doGet/doPost 方法,你就能直接用 req.getParameter("username") 这样的方法获取请求参数了。

简单说:你用的 req 对象,其实是 Tomcat 帮你创建好的 RequestFacade 对象,只不过它的类型是 HttpServletRequest。

三、生活中的实际例子

例子1:快递公司和快递单

  • 接口(HttpServletRequest):就像快递公司规定的“快递单标准”,规定了快递单必须有收件人、寄件人、地址等信息。
  • RequestFacade:就像顺丰公司实际印出来的快递单,完全符合标准,但是顺丰自己造的。
  • 你(Servlet):只管用快递单(req对象)填写和查信息,不用关心快递单是谁印的、怎么印的。

例子2:公司员工和岗位说明书

  • 接口:公司规定的“岗位说明书”,比如“程序员”要会写代码、会调试。
  • 实现类:张三是公司招聘的程序员,完全符合岗位说明书的要求。
  • 你(项目经理):只管让张三干活(调用方法),不用管张三是怎么被招聘进来的

四、代码中的体现

protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
    // req其实是Tomcat创建的RequestFacade对象
    String name = req.getParameter("username");
    // ...
}
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post...");
    }
}

启动服务器访问页面,控制台输出org.apache.catalina.connector.RequestFacade@36bf693c 

五、总结

  • ServletRequest:最基础的请求接口,规定了所有请求对象的基本功能。
  • HttpServletRequest:专门针对HTTP协议的请求接口,功能更丰富。
  • RequestFacade:Tomcat实现的具体类,真正用来封装和传递请求数据。

Tomcat会用RequestFacade把浏览器的请求数据封装好,然后传给你的Servlet代码,让你可以方便地获取和处理请求信息。

Request获取请求数据

请求行包含三块内容,分别是请求方式请求资源路径HTTP协议及版本,例如
GET /tomcat_demo_war/index.html?username=suger1201&password=dsaasd HTTP/1.1

对于这三部分内容,request对象都提供了对应的API方法来获取,具体如下:

  • 获取请求方式: GET
    String getMethod()
  • 获取虚拟目录(项目访问路径): /request-demo
    String getContextPath()
  • 获取URI(统一资源标识符): /request-demo/req1
    String getRequestURI()

介绍完上述方法后,咱们通过代码把上述方法都使用下:

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

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        System.out.println("请求方式:" + method);
        String contextPath = req.getContextPath();
        System.out.println("项目访问路径:" + contextPath);
        StringBuffer requestURL = req.getRequestURL();
        System.out.println("URL:" + requestURL);
        String requestURI = req.getRequestURI();
        System.out.println("URI:" + requestURI);
        String queryString = req.getQueryString();
        System.out.println("请求参数:" + queryString);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post...");
    }
}

这里的请求方式是GET

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="get">
  用户名:<input type="text" name="username">
  <input type="submit" value="提交">
</form>
</body>
</html>

启动服务器,并向表单中随便输入一个username,然后点击提交,控制台输出如下
请求方式:GET
项目访问路径:/web_demo_war_exploded
URL:http://localhost:8080/web_demo_war_exploded/demo
URI:/web_demo_war_exploded/demo
请求参数:username=Cyderpunk2077%40gmail.com

获取请求头数据

对于请求头的数据,格式为key: value
所以根据请求头名称获取对应值的方法为

String getHeader(String name) 

接下来,在代码中如果想要获取客户端浏览器的版本信息,则可以使用

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

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String agent = req.getHeader("user-agent");
        System.out.println(agent);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post...");
    }
}

启动服务器,控制台输出如下
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36

获取请求体数据 

浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="post">
  用户名:<input type="text" name="username">
  <input type="submit" value="提交">
</form>
</body>
</html>

对于请求体中的数据,Request对象提供了如下两种方式来获取其中的数据,分别是:

  • 获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法
    ServletInputStream getInputStream()
  • 获取字符输入流,如果前端发送的是纯文本数据,则使用该方法
    BufferedReader getReader()

下面我们在Servlet的doPost方法中获取数据

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

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("get??");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //由于获取的是纯文本数据,所以这里用的getReader()
        BufferedReader bufferedReader = req.getReader();
        String line = bufferedReader.readLine();
        System.out.println(line);
    }
}

 form表单提交数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="http://localhost:8080/request-demo/demo" method="post">
    用户名:<input type="text" name="username">
    <input type="submit" value="提交">

</form>
</body>
</html>
BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了。

就像你收到一封信(POST请求),你用 getReader() 就是把信封拆开,直接读里面的信纸内容(请求体),而不是看信封上的地址(请求头或参数)。 

启动服务器,访问 http://localhost:8080/requset-demo/index.html ,填入username并提交,在控制台就可以看到前端发送的请求数据了

小结

HTTP请求数据中包含了请求行请求头请求体,针对这三部分内容,Request对象都提供了对应的API方法来获取对应的值:

  • 请求行
    • getMethod()获取请求方式
    • getContextPath()获取项目访问路径
    • getRequestURL()获取请求URL
    • getRequestURI()获取请求URI
    • getQueryString()获取GET请求方式的请求参数
  • 请求头
    • getHeader(String name)根据请求头名称获取其对应的值
  • 请求体
    • 注意: 浏览器发送的POST请求才有请求体
    • 如果是纯文本数据:getReader()
    • 如果是字节数据如文件数据:getInputStream()

获取请求参数的通用方式

请求参数获取方式

  • GET方式:
    String getQueryString()
  • POST方式:
    BufferedReader getReader();

思考:
GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种统一获取请求参数的方式,从而统一doGet和doPost方法内的代码? 

解决方式一:
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求方式
        String method = req.getMethod();
        String param = "";
        //根据请求方式来获取请求参数
        if ("GET".equals(method)) {
            param = req.getQueryString();
        } else if ("POST".equals(method)) {
            BufferedReader bufferedReader = req.getReader();
            param = bufferedReader.readLine();
        }
        //将请求参数进行打印控制台
        System.out.println(param);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

代码中的一些疑问:

1. "GET".equals(method) 是什么意思?
  • method 是一个字符串变量,保存了请求方式,比如 "GET" 或 "POST"。
  • "GET".equals(method) 的意思是:判断 method 变量的值是不是 "GET"。

2. 为什么不写成 method.equals("GET")?
  • 其实 method.equals("GET") 也可以用,功能是一样的。
  • 但如果 method 变量是 null,method.equals("GET") 会报空指针异常(NullPointerException)。
  • 而 "GET".equals(method) 就算 method 是 null,也不会报错,只会返回 false。

所以这种写法更安全!


3. 还能怎么用?

这种写法适用于任何字符串比较,比如:

if ("POST".equals(method)) { ... }
if ("admin".equals(role)) { ... }
if ("zhangsan".equals(username)) { ... }
4. 生活中的例子

就像你问别人“你是不是张三?”

  • 你直接问“张三.equals(你)”,不管对方是谁(哪怕没人),你都不会出错。
  • 但如果你问“你.equals(张三)”,对方要是没人(null),你就会出错。
5. 总结
  • "GET".equals(method) 是安全的字符串比较写法,防止空指针异常。
  • 这种写法在Java开发中非常常见,推荐你以后都这样写!

使用request的getMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可以解决上述问题,但是以后每个Servlet都需要这样写代码,实现起来比较麻烦,这种方案我们不采用

解决方式二:

request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的方法即可,在request的方法中都实现了哪些操作呢?

  1. 根据不同的请求方式获取请求参数,例如:username=zhangsan&password=asd123&hobby=1&hobby=2
  2. 把获取到的内容进行分割,username=zhangsan&password=asd123&hobby=1 -> username=zhangsan password=asd123 hobby=1 hobby=2 -> username zhangsan password asd123 hobby 1 hobby 2
  3. 把分割后端数据,存入到一个Map集合中,其中Map集合的泛型为<String,String[]>,因为参数的值可能是一个,也可能有多个,所以value的值的类型为String数组。

基于上述理论,request对象为我们提供了如下方法:

  • 获取所有参数Map集合
    Map<String,String[]> getParameterMap()
  • 根据名称(Key)获取参数值(Value)(数组)
    String[] getParameterValues(String name)
  • 根据名称(Key)获取参数值(Value)(单个值)
    String getParameter(String name)

接下来,我们通过案例来把上述的三个方法进行实例演示:

  1. 随便写一个表单,加上一个复选框,爱好可以多选,所以到时候的参数值就不止一个了
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form action="/web_demo_war_exploded/demo" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        爱好:<input type="checkbox" name="hobby" value="1">Apex
        <input type="checkbox" name="hobby" value="2">Terraria<br>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>

    getParameterMap()

    import javax.servlet.*;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Map;
    
    @WebServlet(urlPatterns = "/demo")
    public class ServletDemo extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //获取所有参数的Map集合
            Map<String, String[]> parameterMap = req.getParameterMap();
            //遍历Map
            for (String key : parameterMap.keySet()) {
                System.out.print(key+":");
                String[] values = parameterMap.get(key);
                for (String v : values) {
                    System.out.print(v+" ");
                }
                System.out.println();
            }
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doGet(req, resp);
        }
    }
    /*
    获取的结果如下
    username:Cyderpunk2077@gmail.com 
    password:dsaasd 
    hobby:1 2 
    */

    getParameterValues(String name)

    @WebServlet(urlPatterns = "/demo")
    public class ServletDemo extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String[] hobbies = req.getParameterValues("hobby");
            for (String s : hobbies) {
                System.out.println(s);
            }
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doGet(req, resp);
        }
    }
    /*
    将两个复选框都勾上,得到结果如下
    1
    2
    */

    getParameter(String name)

    @WebServlet(urlPatterns = "/demo")
    public class ServletDemo extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String username = req.getParameter("username");
            System.out.println(username);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doGet(req, resp);
        }
    }
    //输出结果就是你输入的username

    如果你在post请求方式下输入了中文username并提交,控制台会输出乱码
    解决方案:

  

package com.itheima.request;

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;

@WebServlet("/req")
public class RequestDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
// 先用ISO-8859-1解码,再用UTF-8编码
        username = new String(username.getBytes("ISO-8859-1"), "UTF-8");
        System.out.println(username);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

遇到的问题

new String(username.getBytes("ISO-8859-1"), "UTF-8")是什么作用?

它的作用就是:把已经乱码的字符串“修正”回来,变成正常的中文。

 详细分解

  • username.getBytes("ISO-8859-1")

把“乱码”的字符串,按照ISO-8859-1编码方式转成字节数组。

这一步的意思是:把你看到的乱码,变回原始的字节数据。

  • new String(..., "UTF-8")

用UTF-8编码方式,把上一步得到的字节数组重新解码成字符串。

这一步的意思是:用正确的方式重新“翻译”这些字节,得到正常的中文。

这行代码就是“先把乱码还原成字节,再用UTF-8重新解码,得到正常中文”。

方法 作用 缺点
req.getParameterMap() 获取所有参数的 Map(参数名→值数组 ) 需遍历处理
req.getParameter("username") 获取单个参数的第一个值 无法直接获取多值参数(如 hobby )
req.getParameterValues("hobby") 获取单个参数的所有值(返回数组 ) 需逐个参数调用

Request请求转发

请求转发(forward):一种在服务器内部的资源跳转方式。

  1. 浏览器发送请求给服务器,服务器中对应的资源A接收到请求
  2. 资源A处理完请求后将请求发给资源B
  3. 资源B处理完后将结果响应给浏览器
  4. 请求从资源A到资源B的过程就叫请求转发

 测试步骤

  1. 创建一个RequestDemo1类,接收/req5的请求,在doGet方法中打印这里是RequestDemo1
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.*;
    import java.io.IOException;
    
    @WebServlet("/RequestDemo1")
    public class RequestDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是RequestDemo1");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }

  2. 创建一个RequestDemo2类,接收/req6的请求,在doGet方法中打印这里是RequestDemo2
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.*;
    import java.io.IOException;
    
    @WebServlet("/RequestDemo2")
    public class RequestDemo2 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是RequestDemo2");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
  3. 在RequestDemo1的方法中使用req.getRequestDispatcher("/RequestDemo2").forward(req,resp)进行请求转发
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.*;
    import java.io.IOException;
    
    @WebServlet("/RequestDemo1")
    public class RequestDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是RequestDemo1");
            //加上这行
            request.getRequestDispatcher("/RequestDemo2").forward(request, response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }

  4. 启动测试
    当我们访问RequestDemo1时,控制台会得到如下输出

        这里是RequestDemo1
        这里是RequestDemo2

        说明请求已经转发到了/RequestDemo2  

在转发的同时我们还可以传递数据给/RequestDemo2


request对象提供了三个方法:
  • 存储数据到request域[范围,数据是存储在request对象]中
    void setAttribute(String name,Object o);
  • 根据key获取值
    Object getAttribute(String name);
  • 根据key删除该键值对
    void removeAttribute(String name);

接着上个需求来:

  1. 在RequestDemo1的doGet方法中转发请求之前,将数据存入request域对象中.
    @WebServlet("/RequestDemo1")
    public class RequestDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是RequestDemo1");
            //存储数据
            request.setAttribute("msg","HELLO~");
            //请求转发
            request.getRequestDispatcher("/RequestDemo2").forward(request, response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
  2. 在RequestDemo2的doGet方法从request域对象中获取数据,并将数据打印到控制台.
    @WebServlet("/RequestDemo2")
    public class RequestDemo2 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是RequestDemo2");
            Object msg = request.getAttribute("msg");
            System.out.println(msg);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
  3. 启动访问测试,得到的输出结果如下

        这里是RequestDemo1
        这里是RequestDemo2
        HELLO~

请求转发的特点
  • 浏览器地址栏路径不发生变化
    虽然后台从/这里是RequestDemo1转发到/这里是RequestDemo2,但是浏览器的地址一直是/这里是RequestDemo1,未发生变化
  • 只能转发到当前服务器的内部资源
    不能从一个服务器通过转发访问另一台服务器
  • 一次请求,可以在转发资源间使用request共享数据
    虽然后台从/RequestDemo1转发到/RequestDemo2,但是这个只有一次请求
遇到的问题
1. request.setAttribute 是什么?
  • 这是 HttpServletRequest 提供的方法,用来在一次请求的范围内存储数据。
  • 你可以把它理解为“在本次请求的背包里放东西”,后续在请求转发(forward)到其他 Servlet 或 JSP 时,可以随时取出来用。
2. 语法和用法
request.setAttribute("键", 值);
  • "msg" 是你给数据起的名字(key)。
  • "HELLO~" 是你要存的数据(value),可以是任意对象。
3. 作用和场景
  • 作用:在一次请求内,多个 Servlet/JSP 之间共享数据。
  • 常见场景:请求转发(forward)时传递数据,比如表单校验、页面跳转时传递提示信息等。
4. 取出数据的方法

在被转发的 Servlet 或 JSP 中,可以这样取出数据:

Object msg = request.getAttribute("msg");
System.out.println(msg); // 输出:HELLO~
5. 注意事项
  • request.setAttribute 存的数据只在本次请求有效,请求结束就没了。
  • 如果用的是重定向(redirect),数据不会带过去,只有请求转发(forward)才可以。
6. 总结
  • request.setAttribute("msg", "HELLO~"); 就是把数据存到本次请求的“背包”里,方便后续在同一次请求的其他地方取用。
  • 常用于Servlet/JSP 之间的数据传递。

Response对象

Reponse的继承体系和Request的继承体系也非常相似:

Response设置响应数据功能介绍

HTTP响应数据总共分为三部分内容,分别是==响应行、响应头、响应体==,对于这三部分内容的数据,respone对象都提供了哪些方法来进行设置?

  1. 响应行
    响应行包含三块内容,分别是 HTTP/1.1[HTTP协议及版本] 200[响应状态码] ok[状态码的描述]
    对于响应头,比较常用的就是设置响应状态码:
    void setStatus(int sc);
  2. 响应头
    响应头的格式为key:value形式
    设置响应头键值对:
    void setHeader(String name,String value);
  3. 响应体
    对于响应体,是通过字符、字节输出流的方式往浏览器写,
    获取字符输出流:
    PrintWriter getWriter();

    获取字节输出流

    ServletOutputStream getOutputStream();

     

Response请求重定向 

  • 重定向的实现方式
    response.setStatus(302);
    response.setHeader("location","资源B的访问路径");
    //或
    resposne.sendRedirect("资源B的访问路径");
  • 创建一个ResponseDemo1类,接收/ResponseDemo1的请求,在doGet方法中打印这里是ResponseDemo1
    @WebServlet("/ResponseDemo1")
    public class ResponseDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是ResponseDemo1");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
  • 创建一个ResponseDemo2类,接收/ResponseDemo1的请求,在doGet方法中打印这里是ResponseDemo2
    @WebServlet("/ResponseDemo2")
    public class ResponseDemo2 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是ResponseDemo2");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
  • 在ResponseDemo1的方法中使用response.sendRedirect("/web_demo_war_exploded/RequestDemo2");
    @WebServlet("/ResponseDemo1")
    public class ResponseDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("这里是ResponseDemo1");
            //重定向
            response.sendRedirect("/web_demo_war_exploded/RequestDemo2");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    
  • 启动测试,访问/ResponseDemo1,控制台得到如下输出,同时地址栏的地址也变更为/RequestDemo2

这里是RequestDemo1
这里是RequestDemo2

重定向的特点
  • 浏览器地址栏路径发送变化
    当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化
  • 可以重定向到任何位置的资源(服务内容、外部均可)
    因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。
  • 两次请求,不能在多个资源使用request共享数据
    因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据

 

简单解释重定向和请求转发的区别

 

一、重定向(Redirect)
1. 什么是重定向?

重定向就是服务器告诉浏览器“你去别的地方找”,然后浏览器自己重新发起请求。

2. 生活中的例子

场景:你去银行办业务

  1. 你走进银行A,问“我要办信用卡”。
  1. 银行A的工作人员说:“我们这里不办信用卡,请您去银行B,地址是XX路XX号。”
  1. 你走出银行A,自己走到银行B。
  1. 在银行B办完信用卡。
3. 重定向的特点
  • 浏览器地址栏会变:因为你去了新的地方,地址栏显示银行B的地址。
  • 可以跳转到任何地方:银行A可以让你去银行B,也可以让你去邮局、超市等。
  • 两次请求:你去了银行A一次,又去了银行B一次。
  • 不能带信息:银行A不能直接把你的资料传给银行B,你得重新说明。
4. 代码示例 
// 重定向到另一个页面
resp.sendRedirect("/login.html");
二、请求转发(Forward)
1. 什么是请求转发?

请求转发就是服务器内部帮你转接,浏览器不知道发生了跳转。

2. 生活中的例子

场景:你打电话给客服

  1. 你打电话给客服A,问“我要投诉”。
  1. 客服A说:“这个问题我帮你转接给投诉部门,请稍等。”
  1. 客服A在内部把你的电话转给了投诉部门。
  1. 投诉部门直接和你通话,解决问题。
3. 请求转发的特点
  • 浏览器地址栏不变:因为你一直以为在和客服A通话,地址栏还是显示客服A的号码。
  • 只能转接到内部:客服A只能转给公司内部的部门,不能转给其他公司。
  • 一次请求:你只打了一次电话,只是内部转接了。
  • 可以带信息:客服A可以把你的问题、会员号等信息直接转给投诉部门。
4. 代码示例
// 请求转发到另一个Servlet
req.getRequestDispatcher("/demo2").forward(req, resp);

三、对比总结

| 方面 | 重定向 | 请求转发 |

|------|--------|----------|

| 地址栏 | 会变 | 不变 |

| 请求次数 | 两次 | 一次 |

| 跳转范围 | 任何地方 | 只能内部 |

| 数据传递 | 不能 | 可以 |

| 效率 | 较低 | 较高 |

四、什么时候用哪个?

一、什么时候用重定向?

1. 登录成功后跳转到首页

场景: 用户登录成功,需要跳转到网站首页。

为什么用重定向?

  • 登录成功后,如果用户刷新页面,不会重复提交登录信息。
  • 地址栏会变成首页地址,用户可以直接收藏或分享首页链接。

代码示例:

// 登录验证成功后
if (loginSuccess) {
    resp.sendRedirect("/index.html"); // 重定向到首页
}

2. 表单重复提交避免

场景: 用户提交订单后,如果刷新页面,不会重复提交订单。

为什么用重定向?

  • 用户提交订单后,重定向到订单成功页面。
  • 如果用户刷新页面,只会刷新订单成功页面,不会重复提交订单。

代码示例:

// 订单提交成功后
if (orderSuccess) {
    resp.sendRedirect("/order-success.html"); // 重定向到成功页面
}
二、什么时候用请求转发?

1. 页面内部模块跳转

场景: 在一个页面内部,根据不同的条件显示不同的内容。

为什么用请求转发?

  • 保持同一个URL,用户不知道内部发生了跳转。
  • 可以在不同模块间传递数据。

代码示例:

// // 用户访问 /dashboard
if (userRole.equals("admin")) {
    // 管理员看到管理面板
    req.getRequestDispatcher("/admin-dashboard.jsp").forward(req, resp);
} else if (userRole.equals("user")) {
    // 普通用户看到用户面板
    req.getRequestDispatcher("/user-dashboard.jsp").forward(req, resp);
} else {
    // 游客看到欢迎页面
    req.getRequestDispatcher("/guest-dashboard.jsp").forward(req, resp);
}

场景:根据商品类型显示不同的详情模板

// 用户访问 /product/detail?id=123
String productType = product.getType();
if (productType.equals("phone")) {
    // 手机显示手机专用模板
    req.getRequestDispatcher("/phone-detail.jsp").forward(req, resp);
} else if (productType.equals("laptop")) {
    // 笔记本显示笔记本专用模板
    req.getRequestDispatcher("/laptop-detail.jsp").forward(req, resp);
} else if (productType.equals("book")) {
    // 书籍显示书籍专用模板
    req.getRequestDispatcher("/book-detail.jsp").forward(req, resp);
}

效果: 用户访问的都是 /product/detail,但根据商品类型显示不同模板。

场景:根据订单状态显示不同的处理页面

// 用户访问 /order/status?id=456
String orderStatus = order.getStatus();
switch (orderStatus) {
    case "pending":
        // 待付款状态
        req.getRequestDispatcher("/order-pending.jsp").forward(req, resp);
        break;
    case "paid":
        // 已付款状态
        req.getRequestDispatcher("/order-paid.jsp").forward(req, resp);
        break;
    case "shipped":
        // 已发货状态
        req.getRequestDispatcher("/order-shipped.jsp").forward(req, resp);
        break;
    case "delivered":
        // 已送达状态
        req.getRequestDispatcher("/order-delivered.jsp").forward(req, resp);
        break;
}

效果: 用户访问的都是 /order/status,但根据订单状态显示不同内容。

路径问题

  • 问题1:转发的时候路径上没有加虚拟目录web_demo_war_exploded,而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?

  • 其实判断的依据很简单,只需要记住下面的规则即可:

    • 浏览器使用:需要加虚拟目录(项目访问路径)
    • 服务端使用:不需要加虚拟目录

解释为什么浏览器要加虚拟目录,服务端不加。

一、什么是虚拟目录?

虚拟目录就是项目在服务器上的访问路径,比如:

  • 你的项目叫 web_demo
  • 访问路径是 http://localhost:8080/web_demo/
  • 这里的 /web_demo/ 就是虚拟目录

二、为什么浏览器要加虚拟目录?
// 重定向
resp.sendRedirect("/web_demo/login.html");

原因:

  • 重定向是服务器告诉浏览器“你去访问这个地址”
  • 浏览器收到指令后,自己重新发起请求
  • 浏览器是外部客户端,不知道你的项目在服务器上的具体位置
  • 所以必须告诉浏览器完整的访问路径(包含虚拟目录)

生活中的例子

  • 你问路,别人告诉你“去银行”,但没说具体地址
  • 你必须知道银行的完整地址(包含街道、门牌号),才能找到
三、为什么服务端不加虚拟目录?

请求转发的情况

// 请求转发
req.getRequestDispatcher("/login.html").forward(req, resp);

原因:

  • 请求转发是服务器内部操作
  • 服务器知道你的项目在文件系统中的真实位置
  • 不需要通过外部的虚拟目录来定位资源
  • 直接使用内部路径即可

生活中的例子

  • 你在公司内部,同事说“去找张三”
  • 你知道张三在哪个办公室,不需要知道公司的门牌号
问题2:在重定向的代码中,``web_demo_war_exploded`是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?

在代码中动态去获取项目访问的虚拟目录,request对象中提供了getContextPath()方法

@WebServlet("/ResponseDemo1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("这里是ResponseDemo1");
        //获取虚拟目录
        String contextPath = request.getContextPath();
        //把虚拟目录拼在前面
        response.sendRedirect(contextPath + "/RequestDemo2");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Response响应字符数据

要想将字符数据写回到浏览器,我们需要两个步骤:

  • 通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();

  • 通过字符输出流写数据: writer.write(“aaa”);

接下来,我们实现通过些案例把响应字符数据给实际应用下:

  1. 返回一个简单的字符串hello world
    @WebServlet("/RequestDemo1")
    public class RequestDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            PrintWriter printWriter = response.getWriter();
            printWriter.write("hello world");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }

    返回一串html字符串,并且能被浏览器解析

  2. 返返回一串html字符串,并且能被浏览器解析,需要注意设置响应数据的编码为utf-8
    @WebServlet("/RequestDemo1")
    public class RequestDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //设置响应的数据格式及数据的编码
            response.setContentType("text/html;charset=utf-8");
            PrintWriter printWriter = response.getWriter();
            printWriter.write("<h1>你好<h1>");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }

Response响应字节数据

要想将字节数据写回到浏览器,我们需要两个步骤:

  • 通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();

  • 通过字节输出流写数据: outputStream.write(字节数据);

接下来,我们实现通过些案例把响应字符数据给实际应用下:

  1. 返回一个图片文件到浏览器
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.*;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    @WebServlet("/RequestDemo1")
    public class RequestDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            FileInputStream fis = new FileInputStream("D:\\background.jpg");
            ServletOutputStream os = response.getOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fis.read(buffer)) != -1){
                os.write(buffer,0,len);
            }
            fis.close();
            //不需要关闭ServletOutputStream,response会帮我们关闭
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }

 


网站公告

今日签到

点亮在社区的每一天
去签到