SpringMVC 的请求和响应

91 阅读5分钟

Servlet

如果我们使用原生的 Servlet 来开发,是使用 HttpServletRequest 和 HttpServletResponse 对象来获取请求参数并返回响应结果的,我们测试以下几种情况:

  1. 请求数据以 form-data 格式发送
  2. 请求数据以 x-www-form-urlencoded 格式发送
  3. 请求以 json 格式发送

请求数据以 form-data 格式发送,并用三种方式获取请求数据:

@WebServlet(urlPatterns = "/servletFormData")
public class ServletFormData extends HttpServlet {
​
​
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
​
        ServletInputStream inputStream = req.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        StringBuilder sb = new StringBuilder();
        while((line = reader.readLine()) != null){
            sb.append(line);
        }
        System.out.println("读取 inputStream 内容:" + sb.toString());
​
        Map<String, String[]> parameterMap = req.getParameterMap();
        ObjectMapper objectMapper = new ObjectMapper();
        System.out.println("读取 parameterMap 中的内容" + objectMapper.writeValueAsString(parameterMap));
​
        Collection<Part> parts = req.getParts();
        for (Part part : parts) {
            String contentType = part.getContentType();
            String name = part.getName();
​
            System.out.println("part contentType: " + contentType + " name: " + name);
        }
​
        // 设置响应内容类型
        resp.setContentType("text/html");
        // 获取输出流对象
        PrintWriter out = resp.getWriter();
        // 输出响应内容
        out.println("<html><body>");
        out.println("<h1>Hello, World!</h1>");
        out.println("</body></html>");
​
    }
​
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
​
        ServletInputStream inputStream = req.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        StringBuilder sb = new StringBuilder();
        while((line = reader.readLine()) != null){
            sb.append(line);
        }
        System.out.println("读取 inputStream 内容:" + sb.toString());
​
        Map<String, String[]> parameterMap = req.getParameterMap();
        ObjectMapper objectMapper = new ObjectMapper();
        System.out.println("读取 parameterMap 中的内容" + objectMapper.writeValueAsString(parameterMap));
​
        Collection<Part> parts = req.getParts();
        for (Part part : parts) {
            String contentType = part.getContentType();
            String name = part.getName();
​
            System.out.println("part contentType: " + contentType + " name: " + name);
        }
​
        // 设置响应内容类型
        resp.setContentType("text/html");
        // 获取输出流对象
        PrintWriter out = resp.getWriter();
        // 输出响应内容
        out.println("<html><body>");
        out.println("<h1>Hello, World!</h1>");
        out.println("</body></html>");
​
    }
}

postman 请求参数如下:

image.png

在上面的例子中,我们以 form-data 的 MIME 格式发送了一个两个表单数据 name 和 age 以及一个文本数据 filename,在 doGet 请求中用 InputStream、paramterMap 和 part 三种方式接收数据。

输出结果是只有 InputStream 获取到了数据,parameterMap 和 part 都没有获取到数据,这是因为底层 socket 连接获取数据只能是 InputStream,不管是 parameterMap 还是 part,都是用的 InputStream 来获取的数据做处理,而 IO 流就像一根水管,水流出之后是不能重复的,所以第一次从 InputStream 将数据全部读取出来之后,后面的 parameterMap 和 part 在从 InputStream 读取数据就读取不到了,所以为空。

如果我们把 InputStream 获取数据的代码注释掉,那么就会发现 parameterMap 和 part 方式都获取到了数据,由此可知 parameterMap 和 part 会把数据缓存在内存中(这里我把 parameterMap 和 part 调换了位置也是一样的结果)。

结果是 form-data 类型的数据 InputStream、parameterMap(获取不到文件数据) 和 part 都可以获取到。

请求数据以 x-www-form-urlencoded 格式发送

还是之前的 Servlet,使用 x-www-form-urlencoded 类型发送数据,postman 请求参数如下:

image.png

这种类型的数据不能使用 part 方法接收,不然会报错,所以需要将 part 部分的代码注释掉。

测试结果是 x-www-form-urlencoded 类型的数据 InputStream 和 paramterMap 可以获取到数据。

请求以 json 格式发送

image.png

以 json 格式发送的数据只有 InputStream 可以获取到。

响应就简单了,先设置 Content-Type,然后直接用 HttpServletResponse 对象来返回数据即可,浏览器会根据你设置的 Content-Type 来解析数据。

Spring MVC

请求

同样也是 form-data、x-www-form-urlencoded 和 json 三种格式的数据,Spring MVC 接收数据的方式常见的有四种:

  1. @PathVariable
  2. @RequestParam
  3. @RequestBody
  4. MultipartFile

@PathVariable 用来从 url 中获取数据,一帮用在 GET 请求中,如下所示:

@GetMapping(path = "/doSomething/{id}")
@ResponseBody
public String doSomething(@PathVariable String id){
    System.out.println("id: " + id);
    return "success";
}

id 就是 url 中的 {id} 的值。

@RequestParam

@PostMapping(path = "/requestParamterTest")
@ResponseBody
public String requestParamterTest(@RequestParam Map<String, Object> paramterMap) throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("paramterMap: " + objectMapper.writeValueAsString(paramterMap));
    return "success";
}

先测试以下 form-data 格式,postman 如下:

image.png

获取到 name 和 age 两个表单项,然后测试 x-www-form-urlencoded;

image.png

同样获取数据成功,接着测试 JSON 格式数据;

image.png

获取失败,看起来 @RequestParam 类似于 原生 Servlet 的 parameterMap 方法获取数据。

@RequestBody

@PostMapping(path = "/requestBodyTest")
@ResponseBody
public String requestBodyTest(@RequestBody Map<String, Object> paramterMap) throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("paramterMap: " + objectMapper.writeValueAsString(paramterMap));
    return "success";
}

以上面同样的 postman 请求测试 @RequestBody,form-data 格式下,这个组合直接报错了;

测试 x-www-form-urlencoded 类型,这个组合也报错了;

测试 JSON 类型,获取数据成功,由此可得,@RequestBody 注解是专门用来解析 JSON 格式数据的。而原生 Servlet 只能用 InputStream 来获取 JSON 格式数据,所以你如果在方法中传入 HttpServletRequest,用它的 InputStream 来获取数据会发现获取不到。

@PostMapping(path = "/requestBodyTest")
@ResponseBody
public String requestBodyTest(@RequestBody Map<String, Object> paramterMap, HttpServletRequest request) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("paramterMap: " + objectMapper.writeValueAsString(paramterMap));
​
    InputStream inputStream = request.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    String line;
    StringBuilder sb = new StringBuilder();
    while((line = reader.readLine()) != null){
        sb.append(line);
    }
    System.out.println("读取 inputStream 内容:" + sb.toString());
​
    return "success";
}

MultipartFile 是专门用来接收文件的;

@PostMapping(path = "/multipartFile")
@ResponseBody
public String multipartFile(@RequestParam Map<String, Object> paramterMap, MultipartFile file) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("paramterMap: " + objectMapper.writeValueAsString(paramterMap));
​
    String s = new String(file.getBytes());
    System.out.println(s);
    return "success";
}

form-data 格式请求数据下,表单项中的普通数据被 parameterMap 结束,文件项被 file 接收。

响应

Spring MVC 中最常见的响应方式是:

  1. @ResponseBody
  2. ResponseEntity
  3. HttpServletResponse

@ResponseBody 用来将返回值解析成数据,如果方法返回 String 并且没有标注 @ResponseBody 的话,Spring MVC 会把返回的 String 当成重定向请求。

标注了 @ResponseBody 之后,如果返回值是 String,则以 text/plain 格式响应,如果返回值是 POJO,则以 text/json 格式响应,如果返回值时 byte[],则以 application/octet-stream 格式响应。

@PostMapping(path = "/requestBodyTest1")
@ResponseBody
public MyEntity requestBodyTest1(@RequestBody Map<String, Object> paramterMap, HttpServletRequest request) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("paramterMap: " + objectMapper.writeValueAsString(paramterMap));
​
    InputStream inputStream = request.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    String line;
    StringBuilder sb = new StringBuilder();
    while((line = reader.readLine()) != null){
        sb.append(line);
    }
    System.out.println("读取 inputStream 内容:" + sb.toString());
​
    return new MyEntity("bob", 12);
}
​
@PostMapping(path = "/requestBodyTest2")
@ResponseBody
public byte[] requestBodyTest2(@RequestBody Map<String, Object> paramterMap, HttpServletRequest request) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("paramterMap: " + objectMapper.writeValueAsString(paramterMap));
​
    InputStream inputStream = request.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    String line;
    StringBuilder sb = new StringBuilder();
    while((line = reader.readLine()) != null){
        sb.append(line);
    }
    System.out.println("读取 inputStream 内容:" + sb.toString());
​
    InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("readme.txt");
​
​
    return toByteArr(resourceAsStream);
}
​
private byte[] toByteArr(InputStream inputStream) throws IOException {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[2048];
    int read = -1;
    while((read = inputStream.read(buffer)) != -1){
        byteArrayOutputStream.write(buffer, 0, read);
    }
​
    return byteArrayOutputStream.toByteArray();
}

ResponseEntity 和 @ResponseBody 类似,只不过它可以设置响应头。

HttpServletResponse 是比较底层的响应 API,可以用它来响应所有类型。