Spring 开发准备
Spring 开发准备
1. 开发规范
1.1 REST 风格
在前后端分离的开发模式中,前后端开发人员都需要根据提前定义好的接口文档,来进行前后端功能的开发。
后端开发人员:必须严格遵守提供的接口文档进行后端功能开发(保障开发的功能可以和前端对接)
而在前后端进行交互的时候,我们需要基于当前主流的 REST 风格的 API 接口进行交互。
- REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格。
传统 URL 风格如下:
http://localhost:8080/user/getById?id=1 GET:查询id为1的用户
http://localhost:8080/user/saveUser POST:新增用户
http://localhost:8080/user/updateUser POST:修改用户
http://localhost:8080/user/deleteUser?id=1 GET:删除id为1的用户
原始的传统URL,定义比较复杂,而且将资源的访问行为对外暴露出来了。
基于 REST 风格 URL 如下:
http://localhost:8080/users/1 GET:查询id为1的用户
http://localhost:8080/users POST:新增用户
http://localhost:8080/users PUT:修改用户
http://localhost:8080/users/1 DELETE:删除id为1的用户
通过 URL 定位要操作的资源,通过 HTTP 动词(请求方式)来描述具体的操作。
在 REST 风格的 URL 中,通过四种请求方式,来操作数据的增删改查。
GET
: 查询POST
:新增PUT
:修改DELETE
:删除
我们看到如果是基于 REST 风格,定义的 URL 将会更加简洁、更加规范、更加优雅。
注意事项:
- REST 是风格,是约定方式,约定不是规定,可以打破
- 描述模块的功能通常使用复数,也就是加 's' 的格式来描述,表示此类资源,而非单个资源。如:users、emps、books…
1.2 统一响应结果
前后端工程在进行交互时,使用统一响应结果 Result。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应信息 描述字符串
private Object data; //返回的数据
//增删改 成功响应
public static Result success(){
return new Result(1, "success", null);
}
//查询 成功响应
public static Result success(Object data){
return new Result(1, "success", data);
}
//失败响应
public static Result error(String msg){
return new Result(0, msg, null);
}
}
1.3 开发流程
在进行功能开发时,都是根据如下流程进行:

- 查看页面原型明确需求
- 根据页面原型和需求,进行表结构设计、编写接口文档(已提供)
- 阅读接口文档
- 思路分析
- 功能接口开发
- 就是开发后台的业务功能,一个业务功能,我们称为一个接口
- 功能接口测试
- 功能开发完毕后,先通过测试工具进行功能接口测试,测试通过后,再和前端进行联调测试
- 前后端联调测试
- 和前端开发人员开发好的前端工程一起测试
2. 文件上传
2.1 程序示例
文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程。
想要完成文件上传这个功能需要涉及到两个部分:
- 前端程序
- 服务端程序
示例如下:
前端程序中的代码:
<form action="/upload" method="post" enctype="multipart/form-data">
姓名: <input type="text" name="username"><br>
年龄: <input type="text" name="age"><br>
头像: <input type="file" name="image"><br>
<input type="submit" value="提交">
</form>
上传文件的原始form表单,要求表单必须具备以下三点(上传文件页面三要素):
表单必须有
file
域,用于选择要上传的文件<input type="file" name="image"/>
表单提交方式必须为
POST
通常上传的文件会比较大,所以需要使用 POST 提交方式
表单的编码类型 enctype 必须要设置为:
multipart/form-data
普通默认的编码格式是不适合传输大型的二进制数据的,所以在文件上传时,表单的编码格式必须设置为
multipart/form-data
后端程序实现:
首先在服务端先定义一个 Controller,用来进行文件上传,然后在 Controller 当中定义一个方法来处理
/upload
请求在定义的方法中接收提交过来的数据(方法中的形参名和请求参数的名字保持一致)
- 用户名:
String name
- 年龄:
Integer age
- 文件:
MultipartFile image
- 用户名:
如果表单项的名字和方法中形参名不一致,该怎么办?
public Result upload(String username,
Integer age,
MultipartFile file) //file形参名和请求参数名image不一致
解决方法:使用 @RequestParam
注解进行参数绑定
public Result upload(String username,
Integer age,
@RequestParam("image") MultipartFile file)
程序运行后,我们上传文件,会发现产生了一个临时文件,而当程序结束后,这个临时文件会被自动删除。
所以,我们需要将这个临时文件,转存到目标路径中,实现真正的文件上传。
2.2 本地存储
以本地存储为例,将上传的文件保存在服务器的本地磁盘上。
代码实现:
- 在服务器本地磁盘上创建 images 目录,用来存储上传的文件;
- 使用
MultipartFile
类提供的API方法,把临时文件转存到本地磁盘目录下。
MultipartFile 常见方法
String getOriginalFilename();
// 获取原始文件名void transferTo(File dest);
// 将接收的文件转存到磁盘文件中long getSize();
// 获取文件的大小,单位:字节byte[] getBytes();
// 获取文件内容的字节数组InputStream getInputStream();
// 获取接收到的文件内容的输入流
示例代码:
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws IOException {
log.info("文件上传:{}, {}, {}", username, age, image);
/**
* 获取原始文件名
* 但是使用原始文件名作为所上传文件的存储名字,当再次上传一个同名文件时,
* 会把之前已经上传成功的文件覆盖掉
* 所以需要保证每次上传文件时文件名都唯一的
*/
String originalFilename = image.getOriginalFilename();
// 将文件存储在服务器的磁盘目录
image.transferTo(new File("E:/images/" + originalFilename));
// 构建新的文件名=随机名+文件扩展名
// 保证每次上传文件时文件名唯一(使用UUID获取随机文件名)
String extname = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = UUID.randomUUID().toString()+extname;
// 将文件存储在服务器的磁盘目录
image.transferTo(new File("E:/images/" + newFileName));
return Result.success();
}
}
在 SpringBoot 中,文件上传时默认单个文件最大大小为 1M
。
如果需要上传大文件,可以在 application.properties 进行如下配置:
# 配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
# 配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB
但是如果直接存储在服务器的磁盘目录中,存在以下缺点:
- 不安全:磁盘如果损坏,所有的文件就会丢失
- 容量有限:如果存储大量的图片,磁盘空间有限(磁盘不可能无限制扩容)
- 无法直接访问
为了解决上述问题,通常有两种解决方案:
- 自己搭建存储服务器,如:fastDFS 、MinIO
- 使用现成的云服务,如:阿里云,腾讯云,华为云
3. 配置文件
3.1 参数配置化
在项目开发过程中,少不了会有一些配置信息,如果直接写死在 Java 代码中(硬编码),即每涉及到一个第三方技术服务,就将其参数硬编码,那么在 Java 程序中会存在两个问题:
如果这些参数发生变化了,就必须在源程序代码中改动这些参数,然后需要重新进行代码的编译,将 Java 代码编译成 class 字节码文件再重新运行程序。(比较繁琐)
在实际开发过程中,Java 类有很多,如果将这些参数分散的定义在各个 Java 类当中,我们要修改一个参数值,我们就需要在众多的 Java 代码当中来定位到对应的位置,再来修改参数,修改完毕之后再重新编译再运行。(参数配置过于分散,不方便集中的管理和维护)
为了解决以上分析的问题,可以将参数配置在配置文件中。
示例如下:
# 配置端口号
server.port=8080
# 配置数据库连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb
sing.datasource.username=root
spring.datasource.password=root
Spring 配置信息官网:https://docs.spring.io/spring-boot/appendix/application-properties/index.html#appendix.application-properties
那我们怎么读取配置呢?
因为 application.properties 是 springboot 项目默认的配置文件,所以 springboot 程序在启动时会默认读取 application.properties 配置文件,而我们可以使用一个现成的注解:@Value
,获取配置文件中的数据。
@Value
注解通常用于外部配置的属性注入,具体用法为: @Value("${配置文件中的key}")
,比如:
@Component
public class SpringUtils {
@Value("${server.port}")
private String serverPort;
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${sing.datasource.username}")
private String dbUsername;
@Value("${spring.datasource.password}")
private String dbPassword;
}
3.2 yml 配置文件
springboot 支持多种配置方式的,除了支持 properties 配置文件以外,还支持 yml 格式的配置文件。
由于 properties 配置是以 key-value 的形式配置的,从上面的配置文件来看,properties 配置中会有很多冗余的信息。
想要解决这个问题,就可以使用 yml 配置文件的格式化(实际工作中也是 yml 用的更多)
application.properties
server.port=8080 server.address=127.0.0.1
application.yml
server: port: 8080 address: 127.0.0.1
application.yaml
server: port: 8080 address: 127.0.0.1
yml 格式的配置文件,后缀名有两种:
- yml (推荐)
- yaml
yml 格式的数据有以下特点:
- 容易阅读
- 容易与脚本语言交互
- 以数据为核心,重数据轻格式
yml 配置文件的基本语法:
- 大小写敏感
- 数值前边必须有空格,作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用 Tab 键,只能用空格(Idea中会自动将 Tab 转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
#
表示注释,从这个字符一直到行尾,都会被解析器忽略

yml文件中常见的数据格式:
- 定义 对象 或 Map 集合
user:
name: zhangsan
age: 18
password: 123456
- 定义 数组、list 或 set 集合
hobby:
- java
- game
- sport
3.3 注解 @ConfigurationProperties
在 Spring 中提供了一种简化方式,可以直接将配置文件中配置项的值自动的注入到对象的属性中。
Spring 提供的简化方式套路:
需要创建一个实现类,且实体类中的属性名和配置文件当中 key 的名字必须要一致。
比如:配置文件当中叫 endpoints,实体类当中的属性也得叫 endpoints,另外实体类当中的属性还需要提供 getter / setter 方法
需要将实体类交给 Spring 的 IOC 容器管理,成为 IOC 容器当中的 bean 对象。
在实体类上添加
@ConfigurationProperties
注解,并通过prefix
属性来指定配置参数项的前缀。

在添加上注解后,还需要引入一个依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
当在 pom.xml 文件当中配置了这项依赖之后,重新启动服务,就会看到在 properties 或者是 yml 配置文件当中,就会提示阿里云 OSS 相关的配置项。
所以这项依赖它的作用就是会自动的识别被 @ConfigurationProperties
注解标识的 bean 对象。
@ConfigurationProperties 和 @Value 注解的区别:
相同点:都是用来注入外部配置的属性的。
不同点:
@Value
注解只能一个一个的进行外部属性的注入。@ConfigurationProperties
可以批量的将外部的属性配置注入到 bean 对象的属性中。
如果要注入的属性非常的多,并且还想做到复用,就可以定义这么一个 bean 对象,通过 @ConfigurationProperties
批量的将外部的属性配置直接注入到 bean 对象的属性当中。
在其他的类当中,若想获取到注入进来的属性,直接注入 bean 对象,然后调用 get
方法,就可以获取到对应的属性值了。
4. 常用注解
4.1 @PathVariable
在 Controller中接收请求路径中的路径参数。
4.2 @DeleteMapping
限定请求方式为 delete
。
类似还有 @GetMapping
,@PostMapping
。
4.3 @RequestBody
在 Controller 中接收 json 格式的请求参数。
4.4 @RequestMapping
指定请求路径。
注意:一个完整的请求路径,应该是类上 @RequestMapping 的 value 属性 + 方法上的 @RequestMapping 的 value 属性
4.5 @RequestParam
@RequestParam(defaultValue="默认值")
设置请求参数默认值。
4.6 @Value
@Value
注解通常用于外部配置的属性 单个 注入,具体用法为:
@Value("${配置文件中的key}")
4.7 @ConfigurationProperties
可以批量的将外部的属性配置 批量 注入到 bean 对象的属性中。
4.8 @WebFilter
@WebFilter
注解用于添加在 Filter 类上,指定属性 urlPatterns
指定过滤器要拦截哪些请求。
4.9 @ServletComponentScan
在启动类上面加上一个注解 @ServletComponentScan
,通过这个注解来开启 SpringBoot 项目对于 Servlet 组件的支持。
4.10 @RestControllerAdvice
定义一个类,在类上加上一个注解 @RestControllerAdvice
,代表定义了一个全局异常处理器。
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
处理异常的方法返回值会转换为 json 后再响应给前端
4.11 @ExceptionHandler
在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解 @ExceptionHandler
。
通过 @ExceptionHandler
注解当中的 value
属性来指定要捕获的是哪一类型的异常。
@ExceptionHandler(Exception.class) // 表示处理所有异常类型