目录

JavaWeb-HttpServletRequest请求域接口

JavaWeb-HttpServletRequest请求域接口

HttpServletRequest请求域接口

HttpServletRequest请求域接口简介

其实关于请求域这个词也蛮熟悉的, 因为我们之前学习过 应用域 这一概念, 应用域的生命周期很长, 伴随这服务器的启动和终止, 作用范围也很广, 对所有的处于当前 webapp 也就是 web 应用的所有Servlet对象都生效


  • HttpServletRequest 是位于 jakarta.servlet.http.* 包下面的一个接口

  • 继承了 ServletRequest接口

    public interface HttpServletRequest extends ServletRequest

  • 之前我们学习过HTTP协议的相关内容, 这个 对象中封装的其实就是网络传输的时候, 发送的HTTP请求(Request)中封装的相关参数内容信息

  • 实现这个接口是Tomcat服务器实现的, 传递对象封装参数也是Tomcat服务器完成好的内容, 我们作为Java程序员, 只需要学习获取其中封装的相关参数即可


关于请求域和应用域的区别

  • 生命周期不同, 应用域伴随着Tomcat的生命周期 而 请求域 只作用域这一次请求之内, 而且http协议的特点就是, 一次请求一次创建一次请求域对象
  • 而且在进行参数设定的时候, 尽量的去选择请求域的参数而不是应用域的参数, 因为小的域的对象占用的资源比较小

请求域接口中的相关方法

上面都说了, 请求域是封装了相关的http协议的参数信息, 所以必定提供了一些方法来让我们程序员获取到这些参数的信息…


获取前端请求参数(getParameter系列方法)

首先我们思考, 前端传递过来的参数应该采用什么数据结构来组织比较好

我们从下面的前端的页面中获取信息

<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>个人信息</title>
</head>
<body>
    <h2>个人信息</h2>
    <form action="" method="get">
        姓名:<input type="text" name="name" value=""><br>
        年龄:<input type="text" name="age" value=""><br>
        性别:<input type="radio" name="sex" value="男">
            <input type="radio" name="sex" value="女"><br>
        爱好:<input type="checkbox" name="hobby" value="吃饭">
            <input type="checkbox" name="hobby" value="睡觉">
            <input type="checkbox" name="hobby" value="打游戏"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

我们执行 http://127.0.0.1:8080/servlet08/test.html

https://i-blog.csdnimg.cn/direct/654722dc88274b32929f1d6cb398f2dd.png#pic_center

我们对URL拆解如下(涉及URLEncoding)

https://i-blog.csdnimg.cn/direct/c68081ec78e843e0b060e059eaeb0594.png

我们可以发现, 前端向后端提交数据的格式其实并不是单纯的键值对的结构存储的, 因为如果是键值对结构存储的话, 一个key只能对应一个value, 但是复选框这种提交信息的结构, 一个key可以对应多个value信息

所以实际上, 我们的前端发来的数据存储格式是一个 特殊的map集合

Map<String, String[]> map = new HashMap<>();

一个String类型的key可以对应一个String[] 数组, 也就是多个value(前端传递参数都是String类型)


上面是一个html页面, 其中包含 test类型文本, 单选框, 复选框

https://i-blog.csdnimg.cn/direct/ae05906228624f70980ff1b5a6273df1.png

上面的方法的解释:

  • String getParameter(String name): 根据key返回String数组中的第一个参数
  • String[] getParameterValues(String name): 根据key返回完整的String[]数组
  • Enumeration< String > getParameterNames(): 返回一个由所有key组成的集合
  • Map<String, String[]> getParameterMap(): 返回一个key和value组成的完整的集合

还拿我们上面写的那个html页面进行测试(设置一下传递的Servlet路径地址)

(注意, 我们下面的method其实写错了, 实际上是post请求)

https://i-blog.csdnimg.cn/direct/73bf636eda8c4bdcaec5ee98f5f3c199.png

Servlet对象的源码如下

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;
import java.util.Enumeration;
import java.util.Map;

@WebServlet(urlPatterns = "/getparameter")
public class GetParameterServlet extends HttpServlet {

    // 由于是form表单提交的数据, 我们尽量采用重写doPost的方式进行测试
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 最好还是设置一下字符集, 防止出现乱码
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        // 1. 使用getParameterMap获取整个的map形式参数集合
        out.print("<h3>使用getParameterMap获取整个集合</h3>");
        Map<String, String[]> parameterMap = request.getParameterMap();
        for(Map.Entry<String, String[]> entry : parameterMap.entrySet()){
            out.print(entry.getKey() + "=");
            for(String value : entry.getValue()){
                out.print(value + " ");
            }
        }
        out.print("<br>");
        out.print("====================================<br>");

        // 2. 使用getParameterNames获取整个参数集合的key
        out.print("<h3>使用getParameterNames获取整个集合中的key</h3>");
        Enumeration<String> parameterNames = request.getParameterNames();
        while(parameterNames.hasMoreElements()){
            String name = parameterNames.nextElement();
            out.print(name + " ");
        }
        out.print("<br>");
        out.print("====================================<br>");

        // 3. 使用getParameterValues, 根据key获取参数集合的value数组
        String[] hobbys = request.getParameterValues("hobby");
        out.print("hobby=");
        for(String hobby : hobbys){
            out.print(hobby + " ");
        }
        out.print("<br>");
        out.print("====================================<br>");

        // 4. 使用getParameter, 根据key获取到value数组中的第一个值
        String name = request.getParameter("name");
        out.print("name=" + name);
        out.print("<br>");
        out.print("====================================<br>");
    }
}

测试:

下面是form表单中提交的数据信息

https://i-blog.csdnimg.cn/direct/4962eabdf80849faa008f67631409f34.png


下面是在浏览器中输出的内容

https://i-blog.csdnimg.cn/direct/559561473b1942e2ab9a8a8b08cf3ca2.png


存储请求域名参数(Attribute系列方法)

Attribute 这个词其实我们很熟悉了, 因为之前学习 ServletContext 就出现过这个词, 也出现了和下面一模一样的一系列方法, 当时是设置应用域对象, 但是现在是设置请求域对象

https://i-blog.csdnimg.cn/direct/fd67062b979f44559e4f31a2122b13bb.png

https://i-blog.csdnimg.cn/direct/778ccef264e3481bb1f8ebd554f2f366.png

  • void setAttribute(String name, Object o): 设置请求域参数
  • Object getAttribute(String name): 获取请求域参数
  • Enumeration< String > getAttributeNames(): 获取所有请求域的key组成的集合
  • void removeAttribute(String name): 移除 key 为参数的请求域信息

没啥可说的, 直接上测试代码

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;
import java.util.Enumeration;

// 使用注解来配置Servlet
@WebServlet("/attribute")
public class AttributeInfoServlet extends HttpServlet {

    // 重写doGet方法
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        // 1. 使用 void setAttribute(String name, Object o) 设置请求域参数
        request.setAttribute("name", "Jack");
        request.setAttribute("age", 18);

        // 2. 使用 Object getAttribute(String name) 获取请求域参数
        Object name = request.getAttribute("name");
        Object age = request.getAttribute("age");
        out.print("<h3>" + name + " " + age + "</h3>");

        out.print("<br>========================================<br>");

        // 3. 使用 Enumeration< String > getAttributeNames() 获取所有的请求域参数key集合
        Enumeration<String> attributeNames = request.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String attributeName = attributeNames.nextElement();
            out.print("<h3>" + attributeName + "</h3>");
        }

        out.print("<br>========================================<br>");

        // 4. 使用 void removeAttribute(String name) 移除参数
        request.removeAttribute("name");
        Object name1 = request.getAttribute("name");
        out.print("<h3>" + name1 + "</h3>");
    }
}

测试结果如下

https://i-blog.csdnimg.cn/direct/3f1babf55d97440fa2bf67bc84c2303a.png#pic_center

获取客户端的相关地址信息

我们需要掌握下面的三个获取地址相关信息的方法

  • getRemoteAddr(): 获取客户端主机IP
  • getRemotePort(): 获取客户端的应用端port(端口号)
  • getRemoteHost(): 获取客户端主机名称

下面是关于上面的三个方法的测试代码(我们直接给出)

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;


// 使用注解代替web.xml进行Servlet的配置
@WebServlet(urlPatterns = "/addr")
public class GetAddr extends HttpServlet {

    // 重写doGet方法

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 设置返回的类型以及获取输出流信息
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        // 1. 使用getRemoteAddr获取客户端的IP信息
        String remoteAddr = request.getRemoteAddr();
        out.print("<h3>客户端的IP地址为</h3><br>");
        out.print(remoteAddr);

        out.print("<br>=====================================");

        // 2. 使用getRemotePort获取客户端的端口号的信息
        int remotePort = request.getRemotePort();
        out.print("<h3>客户端的端口号为</h3><br>");
        out.print(remotePort);

        out.print("<br>======================================");

        // 3. 使用getRemoteHost获取客户端的主机名称
        String remoteHost = request.getRemoteHost();
        out.print("<h3>客户端的主机名号为</h3><br>");
        out.print(remoteHost);
        
    }
}

在浏览器上面访问这个资源, 可以得到下面的内容

https://i-blog.csdnimg.cn/direct/4f8e3aac01864236809681ddf46aab56.png#pic_center

注意我们的 端口号其实不是固定的, 每一次请求的端口号都是在一个范围之内进行随机的, 因为我们的规范建议客户端的端口号设置为变化的, 服务器端的端口号设置为不变的…


获取项目的根路径

这个方法其实用的还是很多的, 因为我们在 大量的场景中都需要动态获取根路径, 也就是项目路径 , 我们在先前的内容中其实也提到过这个方法…

  • getContextPath(): 获取项目部署的路径…

测试就省略了, 主要是想说这个方法的作用非常的重要, 我们好多地方获取项目的路径都需要这个方法…


关于转发和重定向的细致剖析

首先要了解, 不管是转发和重定向, 其目的都是为了实现资源的跳转

也就是Java中有两种方式实现资源的跳转

  • 转发
  • 重定向

转发代码实现及相关问题

https://i-blog.csdnimg.cn/direct/252b76f16f994ab58d51199779ef9b7c.png

https://i-blog.csdnimg.cn/direct/03fe72c5e29c4da593dd1f0ce02bc710.png

  • getRequestDispatch(String servletName)
    通过给定的转发的ServletName地址获取一个分发器对象
  • forward(request, response)
    把当前Servlet对象的请求响应对象作为参数传递到转发当中去, 从而实现位于同一个请求域的作用…

转发的代码实现

首先创建一个AServlet对象(相关注释都在代码中)

import bean.User;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;

// 使用注解信息简化Servlet配置
// 我们把这个 AServlet 作为资源访问的入口, 然后对 BServlet进行资源的转发(所以二者本质上还是一次请求, 共享同一个请求域)
@WebServlet(urlPatterns = "/a")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 因为要测试转发是不是在一次请求之内转发(也就是多个Servlet共享同一个request和response对象)
        // 我们设置相关的请求域参数(我们把用户定义在了另一个包当中, 等会我们复制代码就不展示User类了, 应该可以看懂)
        request.setAttribute("user", new User("huahua", "19", "zz"));

        // 获取分发器对象, 调用分发器对象的forward方法对这次请求进行转发
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/b");
        requestDispatcher.forward(request, response);
    }
}

创建一个BServlet对象作为AServlet的转发请求的地址

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import bean.User;

import java.io.*;

// 使用注解简化Servlet配置
// 这个BServlet作为转发的接收方, 接收AServlet的转发
@WebServlet(urlPatterns = "/b")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        // 获取AServlet中的应用域的参数
        Object user = request.getAttribute("user");
        // 强制类型转化
        User us = (User) user;
        // 输出其中的信息内容
        out.print(us);
    }
}

https://i-blog.csdnimg.cn/direct/fccac0268418455a84eca00ea6d5b3c5.png

我们现在在浏览器中访问AServlet的资源, 显然, 跳转到了BServlet中

  • 可以发现, 在A中设置的请求域参数, B中同样可以获取, 所以可以判定二者位于同一个请求域
  • 通过URL可以发现, 虽然资源跳转到了BServlet, 但是URL中的地址还是显示的AServlet的地址, 所以我们可以了解到, 其实转发是一种Tomcat服务器内部进行的资源跳转, 和浏览器无关( 和重定向区分的重要依据 )
  • 根据上面的提示, 我们可以了解到, 转发是同一次请求的转发, 也就是只能在一种方法当中之间进行转发, 全部都在doGet内部转发, 或者全部都在doPost请求中进行转发…

转发不可以在不同的方法之间完成跳转, 测试如下

假设我们把 BServlet中的 doGet 方法转换为 doPost 方法

https://i-blog.csdnimg.cn/direct/a780eb13b89c41048cfd664bc2c36b4b.png

其他代码完全不变, 此时再次向AServlet发送请求

https://i-blog.csdnimg.cn/direct/1640949b908a4c0eb8647e1cf30aeed2.png

会发现直接报错, 报错信息是 405 method not allowed

其实针对上述问题, 我们还是有解决方案的, 只需要在doGet方法内部调用doPost就可以避免这种问题

https://i-blog.csdnimg.cn/direct/86ade48636cc470d98e40931ff6f238f.png

继续访问AServlet, 会发现程序还是可以正常执行

https://i-blog.csdnimg.cn/direct/8e7e0d481b2d40fd8ad56bdfb4cb9caf.png


重定向代码实现及相关问题

和转发不同, 重定向调用的API位于 response对象中

通过 response 对象调用 sendReDirect(/项目路径/Servlet路径) 方法进行重定向

代码测试

CServlet如下

import bean.User;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;

// 使用注解简化开发
@WebServlet(urlPatterns = "/c")
public class CServlet extends HttpServlet {

    // 重写doGet方法
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 在CServlet类中设置请求域参数, 然后在DServlet中获取这个请求域参数, 查看是否可以获取得到...
        request.setAttribute("user", new User("huahua", "19", "zz"));

        // 调用 sendRedirt 进行重定向操作(重定向要加上项目的地址), 重定向至DServlet
        response.sendRedirect(request.getContextPath() + "/d");
    }
}

DServlet

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;

// 使用注解简化开发
@WebServlet(urlPatterns = "/d")
public class DServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        // 尝试接收CServlet中设置的请求域参数, 查看是不是可以获取到(其实本质是查看是不是一次请求)
        Object user = request.getAttribute("user");

        out.println(user == null ? "不是一个请求" : "是一个请求");
    }
}

向CServlet发送请求

https://i-blog.csdnimg.cn/direct/4930ae5714a74dc08b6ae5318cc97c45.png

获取响应结果如下

https://i-blog.csdnimg.cn/direct/f5dac99249d84f98b0e4f596eeb5e94d.png

  • 很明显的看到URL中的资源明显的发生了改变

我们不妨抓个包看一看刚才发生了什么

会发现出现了两次请求…

https://i-blog.csdnimg.cn/direct/fe5afe0a97d94d2498cd51f79ec35482.png

第一次向CServlet发送了请求, 这是第一次的请求响应信息

https://i-blog.csdnimg.cn/direct/7b47bcf984704af2a1be2b22d9e9f810.png

可以发现, 响应时的状态码是 302 Found

https://i-blog.csdnimg.cn/direct/d07437ef7a284e25b5b4bd3a2b328e3f.png

也就是 发生了重定向的操作

第二次请求是直接通过浏览器向DServlet发送了一个请求而不是Tomcat资源内部的跳转, 具体不再演示了