1. 程式人生 > >SpringMVC返回JSON數據以及文件上傳、過濾靜態資源

SpringMVC返回JSON數據以及文件上傳、過濾靜態資源

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 靜態資源處理類,每次訪問請求過來,會先經過DefaultServletHttpRequestHandler判斷是否是靜態文件,如果是靜態文件,則進行處理,不是則放行交由DispatcherServlet控制器進行處理。

所以這時候就能正常在瀏覽器中訪問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"/>

數字越小優先級越高,瀏覽器訪問如下:

SpringMVC返回JSON數據以及文件上傳、過濾靜態資源