1. 程式人生 > >Spring MVC 上傳、下載、顯示圖片

Spring MVC 上傳、下載、顯示圖片

title type sta direct 自動 ctu tco path stp

通過這篇文章你可以了解到:

  1. 使用 SpringMVC 框架,上傳圖片,並將上傳的圖片保存到文件系統,並將圖片路徑持久化到數據庫
  2. 在 JSP 頁面上實現顯示圖片、下載圖片

[TOC]

1. 準備工作

首先我們需要準備好開發環境,本文測試環境是 SSM(Spring 4.3.9 + SpringMVC 4.3.9 + MyBatis 3.4.4) ,數據庫為 MySQL 5.5,數據庫連接池 C3P0 0.9.5.2,構建包 Maven 3.5.0,Tomcat 8.5。

限於篇幅原因,關於 SSM 框架的整合方法,在這篇文章中就不做詳細的講解啦,有關圖片上傳和下載的相關配置,我會特別標註出來說明的。

我們假定有這樣一個很常見的需求場景:用戶註冊。

首先我們來做一下簡單的業務分析,在註冊頁面,用戶填寫自己的相關信息,然後選擇上傳頭像圖片,註冊成功後顯示個人信息,並將圖片顯示在頁面上。

一看就是一個很簡單的需求吧,那我們就來做相應的數據準備工作吧。

1.1 數據庫表準備

數據庫非常簡單,就一張表:t_user

字段 類型 長度 主鍵 描述
user_id int 11 PK,自增 用戶表主鍵
user_name varchar 50 用戶名
user_tel varchar 20 手機號
user_password varchar 20 密碼
user_pic varchar 255 用戶頭像地址

1.2 實體類 User 和 Mapper(DAO)

對應數據庫表 t_user 創建實體類:User

這裏我使用 mybatis-generate 代碼生成器根據 t_user 表結構自動生成實體類 和 Mybatis 的 mapper 文件。

User 實體類的代碼如下(省略了 getter/setter):

package com.uzipi.entity;

public class User {
    private Integer userId;
    private String userName;
    private String userTel;
    private
String userPassword; private String userPic; }

生成的 dao 層 java 代碼如下:

package com.uzipi.dao;

import com.uzipi.entity.User;
import org.mybatis.spring.annotation.MapperScan;

@MapperScan  // 允許 Spring 掃描該 Mapper
public interface UserMapper {
    // 刪除指定 key 的記錄
    int deleteByPrimaryKey(Integer userId);

    // 插入一條記錄(完整記錄)
    int insert(User record);

    // 插入一條記錄(對象中有值時寫入字段,沒有值的置空)
    int insertSelective(User record);

    // 查詢指定 key 的記錄
    User selectByPrimaryKey(Integer userId);

    // 將對象中的內容更新入庫(對象中有值時更新字段,沒有值的屬性不修改)
    int updateByPrimaryKeySelective(User record);

    // 將對象中的內容更新入庫(全屬性)
    int updateByPrimaryKey(User record);
}

生成的 mapper.xml 文件內容比較多,在文章裏就不展示了,後面附件中提供了下載文件供參考。

1.3 pom.xml 依賴包

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.uzipi</groupId>
  <artifactId>house</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>house Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <!-- junit 單元測試 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <!-- log4j 日誌 -->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    <!-- MySQL 數據庫連接驅動 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.24</version>
    </dependency>
    <!-- c3p0 數據庫連接池 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!-- spring-webmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <!-- spring-aspects -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <!-- spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <!-- 文件上傳 -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!-- spring 支持的 json -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.8.7</version>
    </dependency>
    <!-- MyBatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.4</version>
    </dependency>
    <!-- MyBatis 與 Spring 整合 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!-- Servlet API需求包 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!-- JSP相關 -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>
    <!-- JSTL 標準標簽庫 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>house</finalName>
  </build>
</project>

1.4 SSM 框架的整合配置

框架的整合配置 xml 文件請查看附件。

這裏我特別說明一下涉及到圖片(文件)上傳相關的 spring-mvc 配置:

<!-- 配置文件上傳 -->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <!-- 配置文件上傳的最大體積 10M -->
  <property name="maxUploadSize" value="10240000"></property>
</bean>

2. 控制器 UserController

package com.uzipi.controller;

import com.uzipi.entity.User;
import com.uzipi.service.UserService;
import com.uzipi.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService; // Spring 註入 UserService

    /**
     * 跳轉到註冊頁面
     * @param model
     * @return
     */
    @RequestMapping(value="/register", method = RequestMethod.GET)
    public String register(Model model){
        /*
         為什麽這裏要 new 一個 User 對象?
         因為我們在 JSP 頁面中使用了 spring form 標簽
         spring form 標簽的 modelAttribute 默認需要一個對象用於接收數據
         這裏我們是新增,所以用無參構造創建一個空對象(不是null)
          */
        User user = new User();
        model.addAttribute("user", user); // user 加入到 request 域
        return "user/register"; // 跳轉到 user/register.jsp 頁面
    }

    /**
     * 處理用戶註冊的表單請求
     * @param user
     * @param file
     * @return
     */
    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String doRegister(User user,
                             @RequestParam("imgFile") MultipartFile file,
                             Model model){
        if (userService.saveRegister(user, file)){
            model.addAttribute("user", user);
            return "user/show"; // 註冊成功,跳轉到顯示頁面
        }
        return "redirect:/user/register"; // 註冊失敗,重定向到註冊頁面
    }

    /**
     * 處理圖片顯示請求
     * @param fileName
     */
    @RequestMapping("/showPic/{fileName}.{suffix}")
    public void showPicture(@PathVariable("fileName") String fileName,
                            @PathVariable("suffix") String suffix,
                            HttpServletResponse response){
        File imgFile = new File(Constants.IMG_PATH + fileName + "." + suffix);
        responseFile(response, imgFile);
    }

    /**
     * 處理圖片下載請求
     * @param fileName
     * @param response
     */
    @RequestMapping("/downloadPic/{fileName}.{suffix}")
    public void downloadPicture(@PathVariable("fileName") String fileName,
                                @PathVariable("suffix") String suffix,
                                HttpServletResponse response){
        // 設置下載的響應頭信息
        response.setHeader("Content-Disposition",
                "attachment;fileName=" + "headPic.jpg");
        File imgFile = new File(Constants.IMG_PATH + fileName + "." + suffix);
        responseFile(response, imgFile);
    }

    /**
     * 響應輸出圖片文件
     * @param response
     * @param imgFile
     */
    private void responseFile(HttpServletResponse response, File imgFile) {
        try(InputStream is = new FileInputStream(imgFile);
            OutputStream os = response.getOutputStream();){
            byte [] buffer = new byte[1024]; // 圖片文件流緩存池
            while(is.read(buffer) != -1){
                os.write(buffer);
            }
            os.flush();
        } catch (IOException ioe){
            ioe.printStackTrace();
        }
    }

}

在 Controller 中,有幾個地方是需要我們註意的,不然會遇到坑:

  • 當有多個文件上傳時,如果用 MultipartFile 接口來接收,最好是用註解 @RequestParam("inputName") 指明該文件對應表單中的 input 標簽的 name 屬性。如果 name 都是同名的,可以使用 `MultipartFile [] 文件數組來接收。
  • 註意看處理顯示圖片和下載圖片的請求映射中,我用 {fileName}.{suffix} 這段代碼將圖片名和圖片的後綴區分開,因為 GET 方式的 URL 請求地址中的 "." 點號會被當作通配符處理掉,有多種方式可以解決。我這種方式是一種,你也可以用 "." 轉義字符來避免其通配符的作用。
  • 處理圖片顯示和圖片下載的請求區別在於:是否設置了下載響應頭 response.setHeader("Content-Disposition","attachment;fileName=" + "headPic.jpg"); 當設置了該響應頭時,使用 response 輸出流將會被當作附件提供給客戶端下載,反之就是將流中的內容輸出到頁面上。
  • 處理圖片流時,要註意 buffer 的大小,過小會導致下載速度變慢,過大會占用較多的帶寬,需要考慮平衡。

3. 業務層 UserService

package com.uzipi.service;

import com.uzipi.dao.UserMapper;
import com.uzipi.entity.User;
import com.uzipi.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper; // Spring 註入 UserMapper 對象

    /**
     * 用戶註冊,記錄用戶信息並處理上傳的圖片
     * @param user
     * @param file
     * @return
     */
    public boolean saveRegister(User user, MultipartFile file){
        if (file != null){
            // 原始文件名
            String originalFileName = file.getOriginalFilename(); 
            // 獲取圖片後綴
            String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); 
            // 生成圖片存儲的名稱,UUID 避免相同圖片名沖突,並加上圖片後綴
            String fileName = UUID.randomUUID().toString() + suffix;
            // 圖片存儲路徑
            String filePath = Constants.IMG_PATH + fileName;
            File saveFile = new File(filePath);
            try {
                // 將上傳的文件保存到服務器文件系統
                file.transferTo(saveFile);
                // 記錄服務器文件系統圖片名稱
                user.setUserPic(fileName);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 持久化 user
        return userMapper.insertSelective(user) > 0;
    }

    /**
     * 查找指定 key 的 user 對象
     * @param userId
     * @return
     */
    public User findByUserId(int userId){
        return userMapper.selectByPrimaryKey(userId);
    }
}

Service 層中要註意的幾個問題:

  • 我們在向數據庫存入圖片的路徑記錄時,最好是將文件名和後綴名也一並記錄。這裏有兩種方案供參考:(1)將文件名和後綴名存入一個字段(例子中用到的方案);(2)文件名存入一個字段,後綴名存入一個字段,方便後期篩選不同的文件格式,可以對圖片文件進行讀取和分類查詢分析等操作。
  • 上傳的原始文件名存在命名沖突的問題,為了避免文件名沖突被覆蓋,我們可以使用 UUID 來生成唯一的文件名,如果有時候業務需要保存原始文件名的話,可以考慮在數據庫表中再增加一個字段用於持久化原始的文件名。
  • 文件剛上傳上來時,是存儲在臨時目錄中,我們可以在 spring-mvc.xml 中配置臨時目錄的位置。但存儲在臨時目錄中的圖片並不長久,重啟服務器之後會被清理掉。我們可以利用 MultipartFile 接口提供的 transferTo(File dest) 方法將臨時文件轉移到我們設置的文件系統目錄中。

4. JSP 頁面

頁面沒有加樣式,僅實現了功能,所以不是很好看啦。

4.1 用戶註冊頁面 register.jsp

註冊頁面中使用了 spring form 標簽。關於 spring form 標簽,這裏簡單提一下,在沒有 減輕 JSP 代碼工作量 的需求前提下,還是推薦使用原生的 form 表單標簽,因為 spring form 最終還是會被渲染成原生的 form 標簽的樣子,中間多了一道轉換,必然會降低些許頁面的渲染速度。

register.jsp 代碼如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
    <title>用戶註冊</title>
    <base href="<%=request.getContextPath()%>/"/>
    <style>
        li {list-style: none;}
    </style>
</head>
<body>
    <form:form action="user/register" method="post" enctype="multipart/form-data" modelAttribute="user">
        <li>
            <form:input path="userName" placeholder="用戶名"/>
        </li>
        <li>
            <form:password path="userPassword" placeholder="密碼"/>
        </li>
        <li>
            <form:input path="userTel" placeholder="手機號"/>
        </li>
        <li>
            <input type="file" name="imgFile" />
        </li>
        <li>
            <input type="submit" value="註冊" />
        </li>
    </form:form>
</body>
</html>

register.jsp 需要註意的地方:

  • 涉及到文件上傳,form 標簽就需要加上 enctype="multipart/form-data" ,這大家應該都知道吧。
  • 使用了 spring form 標簽,需要 modelAttribute="user" 這段屬性。因此我們要在跳轉到該頁面之前,往 request 域中添加一個 user 對象(名字可以自定義),如果不寫上這個屬性,SpringMVC會默認給一個 "command"。
  • 假如 modelAttribute 對象中有引用類型的成員屬性,恰好我們要填寫的表單元素中有一個值正好是該引用對象的屬性值,我們可以直接使用 xxx.xxx 的形式指明該屬性值,提交表單時,springMVC 會自動幫助我們封裝該屬性對象。

4.2 用戶信息顯示頁面 show.jsp

用戶顯示頁面比較簡單,主要是為了區分出 “顯示圖片” 和 “下載圖片” 兩種請求。

show.jsp 代碼如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>用戶個人信息</title>
    <base href="<%=request.getContextPath()%>/"/>
    <style>
        li {list-style: none;}
    </style>
</head>
<body>
    <h4>個人信息</h4>
    <li>
        <!-- 頭像顯示 -->
        <img src="user/showPic/${user.userPic}" style="width:100px; height: 100px;"/>
    </li>
    <li>
        用戶名:${user.userName}
    </li>
    <li>
        手機號:${user.userTel}
    </li>
    <li>
        <a href="user/downloadPic/${user.userPic}">下載頭像圖片</a>
    </li>
</body>
</html>

頁面比較簡單,就一個地方可以說明下,可能有的同學還不太明白:

我在 <head> 標簽中加入了 <base href="<%=request.getContextPath()%>/"/> 這段代碼,目的是為了將當前頁面的相對位置定位到 webapp 的根目錄下,這樣可以避免請求跳轉之後,出現同一個 JSP 頁面的相對路徑不一樣的情況。

到這裏,關於 SpringMVC 上傳和下載圖片的步驟就算結束啦。

如果各位同學在測試的過程中遇到什麽問題,可以留言、郵箱([email protected])或者QQ我(281901158)。

源代碼當然是少不了的啦,java 代碼和 SQL 文件打包在一起了。

百度網盤: https://pan.baidu.com/s/1c3SSvj6? 密碼:goma

Spring MVC 上傳、下載、顯示圖片