SpringMVC返回JSON数据以及文件上传、过滤静态资源

时间:2022-07-25
本文章向大家介绍SpringMVC返回JSON数据以及文件上传、过滤静态资源,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

返回JSON数据

在如今前后端分离的趋势下,后端基本不需要再去关心前端页面的事情,只需要把数据处理好并通过相应的接口返回数据给前端即可。在SpringMVC中,我们可以通过@ResponseBody注解来返回JSON数据或者是XML数据。

这个注解的作用是将控制器方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,也就是HTTP响应的内容体,一般我们都是用来返回JSON数据,因为默认是按JSON格式进行转换的。

需要注意的是,在使用此注解之后不会再走视图解析器,而是直接将数据写入到输出流中,他的效果等同于使用response对象输出指定格式的数据。

因为这个注解依赖jackson,所以要想使用这个注解,我们首先需要配置jackson的包,配置依赖如下:

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.4</version>
    </dependency>

并且在Spring配置文件中,需要加上一句配置,用于开启@ResponseBody注解:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="org.zero01"/>
    <!-- 需要加上句才能开启@ResponseBody注解 -->
    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/pages/" p:suffix=".jsp"
    />

</beans>

@ResponseBody注解可以写在类或方法上,写在类上是对该类中的所有方法都生效,代码示例如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@ResponseBody
@Controller
public class Test {

    @RequestMapping("/test.do")
    public Student test() {
        Student student = new Student();
        student.setSname("zero");
        student.setAge(16);
        student.setAddress("USA");

        return student;
    }
}

写在方法上则是只对该方法生效,代码示例如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class Test {

    @RequestMapping("/test.do")
    @ResponseBody
    public Student test() {
        Student student = new Student();
        student.setSname("zero");
        student.setAge(16);
        student.setAddress("USA");

        return student;
    }
}

因为数据会被直接写入到输出流中,所以以上代码的效果等同于如下代码:

@RequestMapping("/login")
public void test(HttpServletResponse response){
    Student student = new Student();
    student.setSname("zero");
    student.setAge(16);
    student.setAddress("USA");

  response.getWriter.write(JSONObject.fromObject(user).toString());
}

通过Postman访问,返回的数据如下:

以上只是用了一个普通的pojo对象作为演示的返回数据,除此之外@ResponseBody 注解,可以将如下类型的数据转换成JSON格式:

  • 基本数据类型,如 boolean , String , int 等
  • Map 类型数据
  • 集合或数组
  • 实体对象
  • 实体对象集合

如果需要 @ResponseBody 注解作用在类上时,我们可以直接使用 @RestController 注解,这个注解相当于@ResponseBody + @Controller注解,这样我们就不需要写两个注解了,示例:

package org.zero01.test;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Test {

    @RequestMapping("/test.do")
    public Student test() {
        Student student = new Student();
        student.setSname("zero");
        student.setAge(16);
        student.setAddress("USA");

        return student;
    }
}

通过Postman访问,返回的数据如下:

既然能发送数据到客户端,那么与之相对的就能接收客户端发送的数据,而@RequestBody注解可以接收客户端发送的JSON数据,并绑定到相应的方法参数上,如下示例:

package org.zero01.test;

import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class Test {

    @RequestMapping("/test.do")
    public void test(@RequestBody  Student student) {
        System.out.println(new JSONObject(student));
    }
}

通过Postman访问,发送JSON数据如下:

控制台打印结果如下:

{"address":"USA","sname":"Json","age":20}

文件上传

文件上传是一个十分常见的需求,特别是像论坛、博客之类的网站经常需要上传图片什么的,例如上传用户头像或文章配图等。如果我们使用Java的IO来完成文件的上传是蛮费劲的,需要写比较多的代码。不过在SpringMVC中,它帮我们封装了文件上传中IO读写的细节。使得我们能够很轻易的就可以完成文件上传的代码编写,下面就来简单介绍一下如何使用SpringMVC来完成文件上传。

SpringMVC中文件上传的实现是依赖fileupload包的,所以需要先配置依赖如下:

<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.3</version>
</dependency>

然后在Spring配置文件中,装配CommonsMultipartResolver类:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
          p:defaultEncoding="utf-8"  // 设置编码格式
          p:maxUploadSize="10485760"  // 设置文件的总大小
/>

注意,这个id值必须为multipartResolver,这是因为要对应上DispatcherServlet中的常量值,如下图:

配置完成之后就可以开始编写代码了,控制器代码如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpSession;
import java.io.File;

@Controller
public class UploadFile {

    @RequestMapping(value = "/upload-file.do", method = RequestMethod.POST)
    public String uploadFile(MultipartFile multipartFile, HttpSession session) {

        System.out.println("表单字段名称:" + multipartFile.getName());
        System.out.println("上传的文件名称:" + multipartFile.getOriginalFilename());
        System.out.println("上传的文件类型:" + multipartFile.getContentType());
        System.out.println("上传的文件大小:" + multipartFile.getSize() + " byte");
        System.out.println("上传的文件是否为空:" + (multipartFile.isEmpty() ? "是" : "否"));

        // 只允许上传.jpg格式的图片文件,真正的文件类型需要通过ContentType来进行判断
        if (!multipartFile.getContentType().equals("image/jpeg")) {
            session.setAttribute("errMsg", "只能上传.jpg格式的图片文件");
            System.err.println("文件上传失败n");

            return "error";
        }

        // 得到uploadFile的绝对路径
        String realPath = session.getServletContext().getRealPath("uploadFile");
        // 将文件放在这个路径下
        File file = new File(realPath, multipartFile.getOriginalFilename());
        // 创建uploadFile目录
        file.getParentFile().mkdir();

        try {
            // 将上传的文件写入到本地中
            multipartFile.transferTo(file);
            session.setAttribute("imgPath", multipartFile.getOriginalFilename());
            System.out.println("文件上传完成n");

            return "upload";
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("文件上传失败n");
            session.setAttribute("errMsg", e.toString());

            return "error";
        }
    }
}

upload.jsp文件内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件上传</title>
</head>
<style>
    .form-div {
        text-align: center;
        margin: 0 auto;
    }

    .img-div {
        text-align: center;
        margin-top: 50px;
    }

    .img-div img {
        border: none;
        width: 100px;
        height: 100px;
        line-height: 100px;
        font-size: 12px;
    }
</style>
<body>
<div class="form-div">
    <h3>文件上传</h3>
    <form action="/upload-file.do" method="post" enctype="multipart/form-data">
        <input type="file" name="multipartFile"/>
        <button type="submit">上传</button>
    </form>
</div>
<div class="img-div">
    <img src="uploadFile/${sessionScope.imgPath}" alt="等待上传..." onerror="this.style.display='none'"/>
</div>
</body>
</html>

error.jsp文件内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<div align="center">
    <h2>文件上传失败,原因:</h2>
    <p>${sessionScope.errMsg}</p>
</div>
</body>
</html>

上传文件:

上传成功:

控制台打印如下:

表单字段名称:multipartFile
上传的文件名称:kfc.jpg
上传的文件类型:image/jpeg
上传的文件大小:13327 byte
上传的文件是否为空:否
文件上传完成

以上我们完成了单个文件的上传,如果要实现多文件上传也很简单,在方法参数上改成声明MultipartFile数组,然后使用循环遍历上传的文件并写入到本地即可,修改控制器代码如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpSession;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

@Controller
public class UploadFile {

    @RequestMapping(value = "/upload-file.do", method = RequestMethod.POST)
    public String uploadFile(MultipartFile[] multipartFiles, HttpSession session) {

        // 得到uploadFile的绝对路径
        String realPath = session.getServletContext().getRealPath("uploadFile");
        File file = new File(realPath);

        // uploadFile目录不存在的话就需要创建
        if (!file.exists()) {
            file.mkdir();
        }

        try {
            List<String> fileNames = new ArrayList<String>();
            for (MultipartFile multipartFile : multipartFiles) {

                System.out.println("表单字段名称:" + multipartFile.getName());
                System.out.println("上传的文件名称:" + multipartFile.getOriginalFilename());
                System.out.println("上传的文件类型:" + multipartFile.getContentType());
                System.out.println("上传的文件大小:" + multipartFile.getSize() + " byte");
                System.out.println("上传的文件是否为空:" + (multipartFile.isEmpty() ? "是" : "否"));

                // 只允许上传.jpg格式的图片文件,真正的文件类型需要通过ContentType来进行判断
                if (!multipartFile.getContentType().equals("image/jpeg")) {
                    session.setAttribute("errMsg", "只能上传.jpg格式的图片文件");
                    System.err.println("文件上传失败n");

                    return "error";
                }

                // 将文件放在uploadFile目录下
                file = new File(realPath, multipartFile.getOriginalFilename());

                // 将上传的文件写入到本地中
                multipartFile.transferTo(file);
                fileNames.add(multipartFile.getOriginalFilename());
                System.out.println("文件上传完成n");
            }
            session.setAttribute("fileNames", fileNames);
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("文件上传失败n");
            session.setAttribute("errMsg", e.toString());

            return "error";
        }
        return "upload";
    }
}

修改upload.jsp文件内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<html>
<head>
    <title>文件上传</title>
</head>
<style>
    .form-div {
        text-align: center;
        margin: 0 auto;
    }

    .img-div {
        text-align: center;
        margin-top: 50px;
    }

    .img-div img {
        border: none;
        width: 100px;
        height: 100px;
        line-height: 100px;
        font-size: 12px;
    }
</style>
<body>
<div class="form-div">
    <h3>文件上传</h3>
    <form action="/upload-file.do" method="post" enctype="multipart/form-data">
        <input type="file" name="multipartFiles" multiple/>
        <button type="submit">上传</button>
    </form>
</div>
<div class="img-div">
    <c:forEach items="${sessionScope.fileNames}" var="fileName">
        <img src="uploadFile/${fileName}" alt="等待上传..." onerror="this.style.display='none'"/>
    </c:forEach>
</div>
</body>
</html>

上传文件:

上传成功:

控制台输出结果如下:

表单字段名称:multipartFiles
上传的文件名称:1.jpg
上传的文件类型:image/jpeg
上传的文件大小:4816 byte
上传的文件是否为空:否
文件上传完成

表单字段名称:multipartFiles
上传的文件名称:2.jpg
上传的文件类型:image/jpeg
上传的文件大小:2824 byte
上传的文件是否为空:否
文件上传完成

表单字段名称:multipartFiles
上传的文件名称:3.jpg
上传的文件类型:image/jpeg
上传的文件大小:4836 byte
上传的文件是否为空:否
文件上传完成

表单字段名称:multipartFiles
上传的文件名称:4.jpg
上传的文件类型:image/jpeg
上传的文件大小:3368 byte
上传的文件是否为空:否
文件上传完成

表单字段名称:multipartFiles
上传的文件名称:5.jpg
上传的文件类型:image/jpeg
上传的文件大小:2379 byte
上传的文件是否为空:否
文件上传完成

过滤静态资源

有些情况下,我们可能会在web.xml中配置DispatcherServlet来拦截整个根目录下的所有访问,让所有访问都需要经过该Servlet的分配,例如:

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

这样一来所有的访问请求都会经过DispatcherServlet,而DispatcherServlet只会把访问请求分配到控制器中,如果在控制器中没有找到相应的处理请求的方法,就会报404错误。所以当我们需要访问静态资源而不是访问控制器的时候就无法正常访问到,例如我在webapp目录下创建了一个普通的文本文件:

然后在浏览器中访问该文件就会报404错误:

这是因为控制器中并没有映射test.txt这样一个uri,所以最终DispatcherServlet没有找到相应的映射地址就会报出404错误。

要解决这个问题也很简单,只需要在Spring配置文件中,增加这一句配置即可:

<mvc:default-servlet-handler/>

这句配置信息相当于在DispatcherServlet上串联了这个 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler 静态资源处理类。只要是DispatcherServlet没办法处理的访问请求,也就是没有在控制器里找到相应的接收请求的方法。那么最后就会交给DefaultServletHttpRequestHandler来判断是否访问的是静态文件,如果是静态文件,并且在本地找到了该文件,则进行处理,没有找到则返回404状态码。

所以这时候就能正常在浏览器中访问test.txt文件了:

但是这个DefaultServletHttpRequestHandler类只能处理普通的静态资源文件,如果当静态资源文件存放在一些较为特殊的目录下,例如WEB-INF目录下,那么它就无法进行处理,会报404错误。而且它的处理优先级比较低,只有当DispatcherServlet遍历完所有的控制器之后没有找到对应的映射地址,才会将访问请求交给DefaultServletHttpRequestHandler类去处理。所以在静态资源文件的访问很频繁的情况下,就会显得比较慢。

不过好在还有另一个标签可以完成资源文件的过滤,而且我们一般也是使用这个标签来完成静态资源文件的映射。例如我将test.txt文件放在WEB-INF目录下:

然后在Spring配置文件中,加上&lt;mvc:resources/&gt;标签,如下:

<mvc:resources mapping="test.txt" location="/WEB-INF/test.txt"/>

mapping属性用于指定映射的uri地址,location用于指定目标文件所在的路径。

浏览器访问如下:

因为映射的是uri地址,所以uri上可以随便加个虚拟目录什么的:

<mvc:resources mapping="/test/test.txt" location="/WEB-INF/test.txt"/>

如果需要映射整个目录下的文件,只需要使用通配符即可:

<mvc:resources mapping="/test/**" location="/WEB-INF/"/>

除了以上用到的两个属性之外,还有一个order属性,这个属性可以在有同名文件的情况下,指定哪个先被匹配。例如WEB-INF下有一个test.txt文件,而test目录下也有一个test.txt文件,如下图:

而这两个文件都被映射在了同一个/test/目录下,这种情况下就需要指定哪个文件优先被匹配了,如下:

<mvc:resources mapping="/test/**" location="/WEB-INF/" order="1"/>
<mvc:resources mapping="/test/**" location="/WEB-INF/test/" order="2"/>

数字越小优先级越高。