通过这篇文章你可以了解到:

  1. 使用 SpringMVC 框架,上传图片,并将上传的图片保存到文件系统,并将图片路径持久化到数据库
  2. 在 JSP 页面上实现显示图片、下载图片

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 上传和下载图片的步骤就算结束啦。

如果各位同学在测试的过程中遇到什么问题,可以留言、邮箱(yotow@foxmail.com)或者QQ我(281901158)。

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

百度网盘: https://pan.baidu.com/s/1c3SSvj6  密码:goma

Spring MVC 上传、下载、显示图片的更多相关文章

  1. Spring框架学习(8)spring mvc上传下载

    内容源自:spring mvc上传下载 如下示例: 页面: web.xml: <?xml version="1.0" encoding="UTF-8"?& ...

  2. spring mvc上传下载文件

    前端jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEnc ...

  3. Spring MVC上传文件原理和resolveLazily说明

    问题:使用Spring MVC上传大文件,发现从页面提交,到进入后台controller,时间很长.怀疑是文件上传完成后,才进入.由于在HTTP首部自定义了“Token”字段用于权限校验,Token的 ...

  4. Spring MVC上传文件

    Spring MVC上传文件 1.Web.xml中加入 <servlet> <servlet-name>springmvc</servlet-name> <s ...

  5. Spring MVC 上传文件

    Spring MVC上传文件需要如下步骤: 1.前台页面,form属性 method设置为post,enctype="multipart/form-data"  input的typ ...

  6. django上传并显示图片

    环境 python 3.5 django 1.10.6 步骤 创建名为 testupload的项目 django-admin startproject testupload 在项目testupload ...

  7. 高可用的Spring FTP上传下载工具类(已解决上传过程常见问题)

    前言 最近在项目中需要和ftp服务器进行交互,在网上找了一下关于ftp上传下载的工具类,大致有两种. 第一种是单例模式的类. 第二种是另外定义一个Service,直接通过Service来实现ftp的上 ...

  8. spring boot上传 下载图片。

    https://blog.csdn.net/a625013/article/details/52414470 build.gradle buildscript { repositories { mav ...

  9. spring mvc上传、下载的实现

    下载 //下载 @RequestMapping(value="/download") public ResponseEntity<byte[]> download() ...

随机推荐

  1. phoenix SQLNestedException: Cannot create PoolableConnectionFactory

    java通过phoenix的jdbc链接hbase数据库遇到如下情况: 查看日志发现phoenix维护的表system.function 的文件缺失了(在hdfs上),就是有节点掉了. 用命令 $HB ...

  2. Python实现电子词典

    代码一览: dictionary/├── code│   ├── client.py│   ├── func.py│   ├── server.py│   └── settings.py├── dat ...

  3. 深入解析ES6中的promise

    作者 | Jeskson来源 | 达达前端小酒馆 什么是Promise Promise对象是用于表示一个异步操作的最终状态(完成或失败)以及其返回的值. 什么是同步,异步 同步任务会阻塞程序的执行,如 ...

  4. asp.net core nginx配置问题解决

    1.无法访问nginx到发布的站点,但是使用原配置(nginx.conf)却可以.使用新建的配置conf.d/netcore.conf不行. 2.在windows浏览中访问http://xxx.xxx ...

  5. Salesforce LWC学习(八) Look Up组件实现

    本篇参考https://www.salesforcelwc.in/2019/10/lookup-in-lwc.html,感谢前人种树. 我们做lightning的时候经常会遇到Look up 或者MD ...

  6. @Import导入自定义选择器

    @Import导入自定义选择器 之前一篇博文:Spring中的@Import注解已经详细介绍了@Import注解,不赘述. 需求描述 通过@import注解自定义组件选择器,将满足我们自定义的规则的b ...

  7. Android中getprop命令的使用

    (1)getprop 在Android系统中,使用getprop命令可以从系统中读取一些设备信息,属性的文件例如: init.rc default.prop /system/build.prop 查询 ...

  8. 【题解】最大 M 子段和 Max Sum Plus Plus [Hdu1024] [51nod1052]

    [题解]最大 M 子段和 Max Sum Plus Plus [Hdu1024] [51nod1052] 传送门:最大 \(M\) 子段和 \(Max\) \(Sum\) \(Plus\) \(Plu ...

  9. 『正睿OI 2019SC Day4』

    总结 今天是一场欢乐的\(ACM\)比赛,于是我队得到了全场倒数的好排名. 好吧,其实还是怪自己不能怪队友啦.对于\(ACM\),可能最主要的还是经验不足,导致比赛的时候有点紧张.虽然队友为了磕一道题 ...

  10. SQL分类之DML:增删改表中的数据

    DML:增删改表中的数据 1.添加数据: 语法: insert into 表名(列名1,列名2,...列名n) values(值1,值2,...值n): 注意: 1.列名和值要一一对应. 2.如果表名 ...