文件上传

上传文件过程分析

SpringMVC 的文件上传技术:MultipartResolver 接口

  • MultipartResolver 接口定义了文件上传过程中的相关操作,并对通用性操作进行了封装。
  • MultipartResolver 接口底层实现类 CommonsMultipartResovler。
  • CommonsMultipartResovler 并未自主实现文件上传下载对应的功能,而是调用了 apache 的文件上传下载组件。

SpringMVC 文件上传实现

  1. Maven 依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
  1. 页面表单:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/fileupload" method="post" enctype="multipart/form-data">
上传文件: <input type="file" name="file"/><br/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
  1. SpringMVC 配置:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
  1. 控制器:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException; @Controller
public class FileUploadController { // 参数中定义 MultipartFile 参数,用于接收页面提交的 type=file 类型的表单(要求表单中的name名称与方法入参名相同)
@RequestMapping(value="/fileupload")
public String fileupload(MultipartFile file, HttpServletRequest request) throws IOException {
//设置保存的路径
String realPath = request.getServletContext().getRealPath("/images");
file.transferTo(new File(realPath, "file.png")); // 将上传的文件保存到服务器
return "page.jsp";
}
}

文件上传常见问题

  1. 文件命名问题
  2. 文件名过长问题
  3. 文件保存路径
  4. 重名问题
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException; @Controller
public class FileUploadController { @RequestMapping(value="/fileupload")
public String fileupload(MultipartFile file, MultipartFile file1,
MultipartFile file2, HttpServletRequest request) throws IOException {
// MultipartFile参数中封装了上传的文件的相关信息
// System.out.println(file.getSize());
// System.out.println(file.getBytes().length);
// System.out.println(file.getContentType());
// System.out.println(file.getName());
// System.out.println(file.getOriginalFilename());
// System.out.println(file.isEmpty()); // 首先判断是否是空文件,也就是存储空间占用为0的文件
if(!file.isEmpty()) {
// 如果大小在范围要求内则正常处理;否则抛出自定义异常告知用户(未实现)
// 获取原始上传的文件名,可以作为当前文件的真实名称保存到数据库中备用
String fileName = file.getOriginalFilename();
// 设置保存的路径
String realPath = request.getServletContext().getRealPath("/images");
// 保存文件的方法,指定保存的位置和文件名即可,通常文件名使用随机生成策略产生,避免文件名冲突问题
// String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase(); // UUID 随机数
file.transferTo(new File(realPath, file.getOriginalFilename()));
}
// 测试一次性上传多个文件
if(!file1.isEmpty()) {
String fileName = file1.getOriginalFilename();
//可以根据需要,对不同种类的文件做不同的存储路径的区分,修改对应的保存位置即可
String realPath = request.getServletContext().getRealPath("/images");
file1.transferTo(new File(realPath, file1.getOriginalFilename()));
}
if(!file2.isEmpty()) {
String fileName = file2.getOriginalFilename();
String realPath = request.getServletContext().getRealPath("/images");
file2.transferTo(new File(realPath, file2.getOriginalFilename()));
}
return "page.jsp";
}
}

Restful

Restful 简介

Rest(REpresentational State Transfer)是一种网络资源的访问风格,定义了网络资源的访问方式。

而 Restful 则是按照 Rest 风格访问网络资源。

优点:

  • 隐藏资源的访问行为,通过地址无法得知做的是何种操作。
  • 书写简化。

Rest 行为常用约定方式

注意:上述行为是约定方式,约定不是规范,可以打破,所以称 Rest 风格,而不是 Rest 规范。

Restful开发入门

  1. 页面表单:
    <!-- 切换请求路径为restful风格 -->
<!-- GET请求通过地址栏可以发送,也可以通过设置form的请求方式提交 -->
<!-- POST请求必须通过form的请求方式提交 -->
<form action="/user/1" method="post">
<!-- 当添加了 name 为 _method 的隐藏域时,可以通过设置该隐藏域的值,修改请求的提交方式,切换为 PUT 请求或 DELETE 请求,但是 form 表单的提交方式 method 属性必须填写 post -->
<!-- 该配置需要配合 HiddenHttpMethodFilter 过滤器使用,单独使用无效,请注意检查 web.xml 中是否配置了对应过滤器 -->
<!-- 使用隐藏域提交请求类型,参数名称固定为"_method",必须配合提交类型 method=post 使用 -->
<input type="hidden" name="_method" value="PUT"/> <!-- value或="DELETE" -->
<input type="submit"/>
</form>
  1. 开启 SpringMVC 对 Restful 风格的访问支持过滤器,即可通过页面表单提交 PUT 与 DELETE 请求:
    <!-- 配置拦截器,解析请求中的参数_method,否则无法发起PUT请求与DELETE请求,配合页面表单使用 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<servlet-name>DispatcherServlet</servlet-name>
</filter-mapping> <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*:spring-mvc.xml</param-value>
</init-param>
</servlet>

表单校验框架

表单校验框架介绍

表单校验分类

  • 校验位置:

    • 客户端校验
    • 服务端校验
  • 校验内容与对应方式:
    • 格式校验

      • 客户端:使用 JS 技术,利用正则表达式校验
      • 服务端:使用校验框架
    • 逻辑校验
      • 客户端:使用 ajax 发送要校验的数据,在服务端完成逻辑校验,返回校验结果
      • 服务端:接收到完整的请求后,在执行业务操作前,完成逻辑校验

表单校验规则

  • 长度:例如用户名长度,评论字符数量
  • 非法字符:例如用户名组成
  • 数据格式:例如 Email 格式、IP 地址格式
  • 边界值:例如转账金额上限,年龄上下限
  • 重复性:例如用户名是否重复

表单校验框架

  • JSR(Java Specification Requests):Java 规范提案

    • JSR 303:提供 bean 属性相关校验规则
  • Hibernate 框架中包含一套独立的校验框架 hibernate-validator

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>

注意:

  • tomcat7:搭配 hibernate-validator 版本 5...Final
  • tomcat8.5 及以上:搭配 hibernate-validator 版本 6...Final

快速入门

  1. 页面表单:
<form action="/addemployee" method="post">
员工姓名:<input type="text" name="name"><span style="color:red">${name}</span><br/>
员工年龄:<input type="text" name="age"><span style="color:red">${age}</span><br/>
<input type="submit" value="提交">
</form>
  1. 设置校验规则:

    • 名称:@NotNull
    • 类型:属性注解 等
    • 位置:实体类属性上方
    • 作用:设定当前属性校验规则
    • 范例:

      每个校验规则所携带的参数不同,根据校验规则进行相应的调整

      具体的校验规则查看对应的校验框架进行获取
import javax.validation.constraints.NotBlank;

public class Employee {

    @NotBlank(message="姓名不能为空")
private String name; // 员工姓名
private Integer age; // 员工年龄 public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} @Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
  1. 开启校验,并获取校验错误信息:

    • 名称:@Valid、@Validated
    • 类型:形参注解
    • 位置:处理器类中的实体类类型的方法形参前方
    • 作用:设定对当前实体类类型参数进行校验
    • 范例:
import com.bean.Employee;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.RequestMapping; import javax.validation.Valid; @Controller
public class EmployeeController { // 使用 @Valid 开启校验(使用 @Validated 也可以开启校验)
// Errors 对象用于封装校验结果,如果不满足校验规则,对应的校验结果封装到该对象中,包含校验的属性名和校验不通过返回的消息
@RequestMapping(value="/addemployee")
public String addEmployee(@Valid Employee employee, Errors errors, Model model) {
System.out.println(employee);
// 判定Errors对象中是否存在未通过校验的字段
if(errors.hasErrors()){
// 获取所有未通过校验规则的信息
for(FieldError error : errors.getFieldErrors()){
// 将校验结果信息添加到Model对象中,用于页面显示
// 实际开发中无需这样设定,返回json数据即可
model.addAttribute(error.getField(), error.getDefaultMessage());
}
// 当出现未通过校验的字段时,跳转页面到原始页面,进行数据回显
return "employee.jsp";
}
return "success.jsp";
} }
  1. 示例效果:提交表单并返回校验结果

多规则校验

  • 同一个属性可以添加多个校验器:
@NotNull(message = "请输入您的年龄")
@Max(value = 60, message = "年龄最大值不允许超过60岁")
@Min(value = 18, message = "年龄最小值不允许低于18岁")
private Integer age; // 员工年龄
  • 3 种判定空校验器的区别:

嵌套校验

  • 名称:@Valid
  • 类型:属性注解
  • 位置:实体类中的引用类型属性上方
  • 作用:设定当前应用类型属性中的属性开启校验
  • 范例:
public class Employee {
// 实体类中的引用类型通过标注 @Valid 注解,设定开启当前引用类型字段中的属性参与校验
@Valid
private Address address;
}
  • 注意:开启嵌套校验后,被校验对象内部需要添加对应的校验规则。

分组校验

同一个模块,根据执行的业务不同,需要校验的属性也会有不同,如新增用户和修改用户时的校验规则不同。

因此,需要对不同种类的属性进行分组,在校验时可以指定参与校验的字段所属的组类别:

// 定义组(通用)
public interface GroupOne {
}
// 为属性设置所属组,可以设置多个
@NotEmpty(message = "姓名不能为空", groups = {GroupOne.class})
private String name; // 员工姓名
// 开启组校验
public String addEmployee(@Validated({GroupOne.class}) Employee employee){
}

综合案例

  • 页面表单:employee.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/addemployee" method="post">
员工姓名:<input type="text" name="name"><span style="color:red">${name}</span><br/>
员工年龄:<input type="text" name="age"><span style="color:red">${age}</span><br/>
<!-- 注意,引用类型的校验未通过信息不是通过对象进行封装的,而是直接使用"对象名.属性名"的格式作为整体属性字符串进行保存的,因此需要使用以下获取方法。
这和使用者的属性传递方式有关,不具有通用性,仅适用于本案例 -->
省名:<input type="text" name="address.provinceName"><span style="color:red">${requestScope['address.provinceName']}</span><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
  • 实体类:Employee.java
package com.bean;

import com.group.GroupA;

import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; public class Employee { // 设定校验器,设置校验不通过对应的消息,设定所参与的校验组
@NotBlank(message="姓名不能为空", groups = {GroupA.class})
private String name; // 员工姓名 // 一个属性可以添加多个校验器
@NotNull(message = "请输入您的年龄", groups = {GroupA.class})
@Max(value = 60, message = "年龄最大值不允许超过60岁")
@Min(value = 18, message = "年龄最小值不允许低于18岁")
private Integer age; // 员工年龄 // 实体类中的引用类型通过标注 @Valid 注解,设定开启当前引用类型字段中的属性参与校验
@Valid
private Address address; public Address getAddress() {
return address;
} public void setAddress(Address address) {
this.address = address;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} @Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
}
  • 实体类:Address.java
package com.bean;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size; // 嵌套校验的实体中,对每个属性正常添加校验规则即可
public class Address { @NotBlank(message = "请输入省份名称")
private String provinceName; // 省份名称 @NotBlank(message = "请输入城市名称")
private String cityName; // 城市名称 @NotBlank(message = "请输入详细地址")
private String detail; // 详细住址 @NotBlank(message = "请输入邮政编码")
@Size(max = 6, min = 6, message = "邮政编码由6位组成")
private String zipCode; // 邮政编码 public String getProvinceName() {
return provinceName;
} public void setProvinceName(String provinceName) {
this.provinceName = provinceName;
} public String getCityName() {
return cityName;
} public void setCityName(String cityName) {
this.cityName = cityName;
} public String getDetail() {
return detail;
} public void setDetail(String detail) {
this.detail = detail;
} public String getZipCode() {
return zipCode;
} public void setZipCode(String zipCode) {
this.zipCode = zipCode;
} @Override
public String toString() {
return "Address{" +
"provinceName='" + provinceName + '\'' +
", cityName='" + cityName + '\'' +
", detail='" + detail + '\'' +
", zipCode='" + zipCode + '\'' +
'}';
}
}
  • 分组接口:GroupA.java
package com.group;

public interface GroupA {
}
  • 控制层:EmployeeController.java
package com.controller;

import com.bean.Employee;
import com.group.GroupA;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping; import javax.validation.Valid;
import java.util.List; @Controller
public class EmployeeController { // 应用GroupA的分组校验规则
@RequestMapping(value="/addemployee")
// 使用@Valid开启校验,使用@Validated也可以开启校验
// Errors对象用于封装校验结果,如果不满足校验规则,对应的校验结果封装到该对象中,包含校验的属性名和校验不通过返回的消息
public String addEmployee(@Validated({GroupA.class}) Employee employee, Errors errors, Model model) {
System.out.println(employee);
// 判定Errors对象中是否存在未通过校验的字段
if(errors.hasErrors()){
// 获取所有未通过校验规则的信息
List<FieldError> fieldErrors = errors.getFieldErrors();
System.out.println(fieldErrors.size());
for(FieldError error : fieldErrors){
System.out.println(error.getField());
System.out.println(error.getDefaultMessage());
//将校验结果信息添加到Model对象中,用于页面显示,后期实际开发中无需这样设定,返回json数据即可
model.addAttribute(error.getField(),error.getDefaultMessage());
}
// 当出现未通过校验的字段时,跳转页面到原始页面,进行数据回显
return "employee.jsp";
}
return "success.jsp";
} // 不区分校验分组,即全部规则均校验
@RequestMapping(value="/addemployee2")
public String addEmployee2(@Valid Employee employee, Errors errors, Model model) {
System.out.println(employee);
if(errors.hasErrors()){
for(FieldError error : errors.getFieldErrors()){
model.addAttribute(error.getField(), error.getDefaultMessage());
}
return "employee.jsp";
}
return "success.jsp";
}
}

实用校验范例

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Date; // 实用的校验范例,仅供参考
public class Employee implements Serializable { private String id; // 员工ID private String code; // 员工编号 @NotBlank(message = "员工名称不能为空")
private String name; // 员工姓名 @NotNull(message = "员工年龄不能为空")
@Max(value = 60,message = "员工年龄不能超过60岁")
@Min(value = 18,message = "员工年里不能小于18岁")
private Integer age; // 员工年龄 @NotNull(message = "员工生日不能为空")
@Past(message = "员工生日要求必须是在当前日期之前")
private Date birthday; // 员工生日 @NotBlank(message = "请选择员工性别")
private String gender; // 员工性别 @NotEmpty(message = "请输入员工邮箱")
@Email(regexp = "@", message = "邮箱必须包含@符号")
private String email; // 员工邮箱 @NotBlank(message = "请输入员工电话")
@Pattern(regexp = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$", message = "手机号不正确")
private String telephone; // 员工电话 @NotBlank(message = "请选择员工类别")
private String type; // 员工类型:正式工为1,临时工为2 @Valid // 表示需要嵌套验证
private Address address; // 员工住址 // 省略各 getter、setter
}

Spring MVC 文件上传、Restful、表单校验框架的更多相关文章

  1. Spring MVC 笔记 —— Spring MVC 文件上传

    文件上传 配置MultipartResolver <bean id="multipartResolver" class="org.springframework.w ...

  2. Spring mvc文件上传实现

    Spring mvc文件上传实现 jsp页面客户端表单编写 三个要素: 1.表单项type="file" 2.表单的提交方式:post 3.表单的enctype属性是多部分表单形式 ...

  3. ajax方式提交带文件上传的表单,上传后不跳转

    ajax方式提交带文件上传的表单 一般的表单都是通过ajax方式提交,所以碰到带文件上传的表单就比较麻烦.基本原理就是在页面增加一个隐藏iframe,然后通过ajax提交除文件之外的表单数据,在表单数 ...

  4. Spring MVC文件上传教程 commons-io/commons-uploadfile

    Spring MVC文件上传教程 commons-io/commons-uploadfile 用到的依赖jar包: commons-fileupload 1.3.1 commons-io 2.4 基于 ...

  5. 【Java Web开发学习】Spring MVC文件上传

    [Java Web开发学习]Spring MVC文件上传 转载:https://www.cnblogs.com/yangchongxing/p/9290489.html 文件上传有两种实现方式,都比较 ...

  6. spring mvc 文件上传 ajax 异步上传

    异常代码: 1.the request doesn't contain a multipart/form-data or multipart/mixed stream, content type he ...

  7. Spring mvc 文件上传到文件夹(转载+心得)

    spring mvc(注解)上传文件的简单例子,这有几个需要注意的地方1.form的enctype=”multipart/form-data” 这个是上传文件必须的2.applicationConte ...

  8. Spring MVC 文件上传 & 文件下载

    索引: 开源Spring解决方案--lm.solution 参看代码 GitHub: pom.xml WebConfig.java index.jsp upload.jsp FileUploadCon ...

  9. spring mvc文件上传(单个文件上传|多个文件上传)

    单个文件上传spring mvc 实现文件上传需要引入两个必须的jar包    1.所需jar包:                commons-fileupload-1.3.1.jar       ...

随机推荐

  1. Hive(一)【基本概念、安装】

    目录 一. Hive基本概念 1.1 Hive是什么 1.2 Hive的优缺点 1.3 Hive的架构 1.4 Hive和数据库的区别 二. Hive安装 2.1 安装地址 2.2 Mysql的安装 ...

  2. DBeaver客户端工具连接Hive

    目录 介绍 下载安装 相关配置 1.填写主机名 2.配置驱动 简单使用 主题设置 字体背景色 介绍 在hive命令行beeline中写一些很长的查询语句不是很方便,急需一个hive的客户端界面工具 D ...

  3. 大数据学习day34---spark14------1 redis的事务(pipeline)测试 ,2. 利用redis的pipeline实现数据统计的exactlyonce ,3 SparkStreaming中数据写入Hbase实现ExactlyOnce, 4.Spark StandAlone的执行模式,5 spark on yarn

    1 redis的事务(pipeline)测试 Redis本身对数据进行操作,单条命令是原子性的,但事务不保证原子性,且没有回滚.事务中任何命令执行失败,其余的命令仍会被执行,将Redis的多个操作放到 ...

  4. 商业爬虫学习笔记day4

    一.获取登录后页面信息的两种方法 1.第一种方法: 人为把有效cookies加到请求头中,代码如下 import urllib.request # 确定url url = "https:// ...

  5. 关于C语言中不同类型数据进行计算 有符号和无符号数进行计算

    float是8个有效位, 做个试验: 输出如下: 上面说明了什么: 1, 18/2.2 是除不尽的, 因为是define,所以没有给ratio变量赋值类型,但是从sizeof输出的结果是8,所以系统默 ...

  6. 【AWS】【TroubleShooting】EC2实例无法使用SSH远程登陆(EC2 failure for SSH connection)

    1. Login AWS web console and check the EC2 instance.

  7. 开源低代码开发平台entfrm2.1.0更新

    开源低代码开发平台entfrm2.1.0更新 新功能 代码生成支持主子表,支持预览: 新增多应用顶部菜单与左侧菜单联动: element-ui升级到2.15.1: 新增表单管理,集成avue-from ...

  8. 【编程思想】【设计模式】【行为模式Behavioral】Specification

    Python版 https://github.com/faif/python-patterns/blob/master/behavioral/specification.py #!/usr/bin/e ...

  9. 实现将rsyslog将日志记录与MySQL中

    准备两个节点 node3:  rsyslog node2:   数据库 准备相应的包 [root@node3 php-fpm.d]#yum install rsyslog-mysql 将数据拷贝到数据 ...

  10. Socket通信和多线程的总结

    1.ServerSocket进行多线程接收 package com.yh.chat; import java.io.IOException; import java.net.ServerSocket; ...