SSM-JUnit、文件上传与拦截器
# 什么是SSM
SSM框架是三个核心组件的集成方案:
- Spring框架:提供IOC(控制反转)容器,统一管理所有对象的生命周期与依赖关系。
- Spring MVC:作为表现层框架,采用MVC架构模式,通过DispatcherServlet实现请求分发,有效隔离业务层、表现层与持久层。
- MyBatis:专注持久层的ORM框架,简化数据库操作,支持灵活SQL编写与结果映射。
# 组件通用套路
- 引入 pom 依赖,确保依赖之间没有版本冲突
- 第一步先确定我引入的组件的生效范围;然后是根据我们的想要的效果以及我们的需求去确定配置
- 确定是否遵循最佳实践?
# TestCase - Junit
在单元测试的实施过程中,我们首先需要在POM文件中添加相关依赖项,以确保测试框架的可用性。随后,着手编写测试类是关键步骤。
在测试类编写过程中,以下注解的合理使用至关重要:
@BeforeClass 与 @AfterClass 用于在测试类执行前后进行全局操作,如数据库初始化或资源加载。这些方法仅在测试类初始化和终止时各执行一次。
@Before 与 @After 应用于每个测试方法的执行前后,通常用于:
数据库连接的获取与释放
日志记录的预处理
测试环境的准备与清理
@Test 标记测试方法的核心注解,每个测试方法都需添加该注解以标识其测试性质。
- 测试执行完成后,我们不仅需要确认方法的正常运行,更重要的是验证结果是否符合预期。断言(Assert)机制在此阶段发挥重要作用,使用适当的断言类型(assertEquals, assertTrue等)
# pom file
<!--Junit单元测试框架-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- Spring-Test是Spring与Junit提供的整合包
通过这个组件可以在Junit测试时自动对IOC容器进行初始化
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- scope = test 代表只在单元测试时才会加载这个依赖 -->
<scope>test</scope>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
配置项 | 作用 |
---|---|
javax.servlet-api | 提供 Servlet 接口的类定义,使测试代码能编译和运行。 |
<scope>test</scope> | 确保依赖仅在测试阶段生效,避免与 Servlet 容器的实现冲突,同时减少生产包体积。 |
# Demo
import com.ssm.entity.Goods;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.text.ParseException;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class GoodsServiceTest {
//@BeforeClass代表在初始化测试用例类前执行的操作
//@BeforeClass必须是static静态方法
@BeforeClass
public static void init() {
System.out.println("正在导入Goods表数据");
}
@Resource
private GoodsService goodsService;
@Before
public void doBefore() {
System.out.println("获取数据库Connection对象");
}
@After
public void doAfter() {
System.out.println("释放数据库Connection对象");
}
@Test
//@Transactional默认在测试用例执行成功后,为了保证原始数据不被测试数据污染
//自动进行回滚,要解决这个问题需要设置@Rollback(false)关闭自动回滚
@Transactional
@Rollback(false)
public void initGoods() throws ParseException {
goodsService.initGoods();
}
/**
* 程序运行成功 绿色对勾
* 程序运行失败 红色叹号
* 程序运行逻辑错误 黄色叉号
*/
@Test
public void findById() {
Goods goods = goodsService.findById(9000);
//Assert 断言,对程序产生的结果进行判断
assertTrue("未找到Goods对象", goods!=null);
}
/*测试用例类执行完成后,执行的操作
通常用于清除结果*/
@AfterClass
public static void destory() {
System.out.println("清空Goods表数据");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File Upload
文件上传开发套路 1.Maven依赖commons-fileupload 2.applicationContext配置multipartResolver 3.表单设置Post请求与form-data 4.Controller使用@RequestParam MultipartFile接收文件 5.利用MultipartFile.transferTo()方法实现文件另存为
一句话总结: Spring文件上传就是从表单post传文件,后端用MultipartFile接住存起来的事儿。
# 基础步骤
添加依赖
Maven引入commons-fileupload
库,用于处理文件上传: 【注】Spring MVC 5.x及以上版本已内置文件上传支持,可根据项目需求选择是否引入该依赖<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
1
2
3
4
5配置
multipartResolver
在Spring的applicationContext.xml
中定义Bean,并设置参数(如文件大小限制): 此配置使Spring具备处理二进制文件上传的能力<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> <!-- 10MB --> <property name="defaultEncoding" value="UTF-8"/> </bean>
1
2
3
4FE 表单
在HTML表单中使用post请求,并设置编码类型: 表单需指定
method="post"
和enctype="multipart/form-data"
,并包含文件输入框:<form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="上传"> </form>
1
2
3
4Controller接收文件
使用@RequestParam
绑定上传文件到MultipartFile
对象:@PostMapping("/upload") public String uploadFile(@RequestParam("file") MultipartFile file) { // 处理文件 }
1
2
3
4- 确保参数名与表单文件域name属性一致
- MultipartFile接口提供了获取文件信息的多种方法
接收多个文件时,使用
MultipartFile[]
或多个@RequestParam
参数:@RequestParam("files") MultipartFile[] files
1保存文件
调用transferTo()
将文件保存到服务器指定路径:String savePath = "/uploads/"; File targetFile = new File(savePath + file.getOriginalFilename()); file.transferTo(targetFile);
1
2
3
# Spring MVC拦截器
Spring 拦截器的核心配置管理可分为两大维度:
1. 生命周期管控
- 依赖管理:优先确保组件正确注入与顺序加载
- 作用域控制:通过注册路径指定拦截的URL模式
- 行为定制:可编程设置预处理策略/响应修改规则
2. 执行阶段模型 基于 HandlerInterceptor 的三阶段工作流:
- 预处理阶段:Controller方法执行前进行身份验证/参数校验
- 后处理阶段:Controller返回后修改模型数据/响应头
- 完成回调:视图渲染结束后处理资源清理/日志记录
# 拦截器最佳使用
Q1:过滤器改变Request参数后,Interceptor能否获取到新值? A:可以。因为在同一个线程中,包装后的Request对象会被后续组件共享。
Q2:拦截器的postHandle与afterCompletion的异常处理差异?
try {
preHandle(); // 返回false则链终止
handlerMethod.invoke();
postHandle(); // 视图渲染前(若Controller抛出异常则不执行)
} catch (Exception ex) {
// 进入已配置的异常处理流程
} finally {
afterCompletion(); // 总是会执行(类似finally块)
}
2
3
4
5
6
7
8
9
# Demo
# applicationContext.xml
<mvc:interceptors>
<!--配置拦截器-->
<mvc:interceptor>
<!--拦截器拦截的地址,/**代表所有地址
也可以书写/xxx某个具体的地址
如果有多个URL需要拦截,可以写多个mvc:mapping标签-->
<mvc:mapping path="/**"/>
<mvc:mapping path="/login.html"/>
<mvc:mapping path="/reg"/>
<!--
exclude-mapping是排除地址,必须以/开头,后面增加匹配字符串即可
exclude-mapping通常是排除掉静态资源
-->
<mvc:exclude-mapping path="/**/*.ico"/>
<mvc:exclude-mapping path="/**/*.jpg"/>
<mvc:exclude-mapping path="/**/*.gif"/>
<bean class="com.springmvc.interceptor.SecurityInterceptor"/>
</mvc:interceptor>
<!--某个URL遇到多个拦截器时,会按照配置顺序依次执行,如果前面preHandle没有通过,则会全部中断-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.springmvc.interceptor.SecurityInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# SecurityInterceptor
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class SecurityInterceptor implements HandlerInterceptor {
public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getRequestURL() + "前置处理");
String token = request.getHeader("token");
if(token == null){
response.sendError(403,"Token Not Found!");
return false;
}
return true;
}
public void postHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(request.getRequestURL() + "处理完成");
}
public void afterCompletion(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(request.getRequestURL() + "已渲染输出");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 与Filter Chain 区别
- 过滤器:关注 协议层处理(如安全、编码、跨域),与业务逻辑无关。
- 拦截器:关注 业务层处理(如参数校验、日志记录),与控制器紧密耦合。
- 过滤器 和 拦截器 操作的是同一个
HttpServletRequest/Response
对象,但它们的 处理阶段、设计目的和职责范围 完全不同。 - 处理的东西不同
- Filter:在任何业务代码执行前处理
- Interceptor:处理的是装饰后的Request对象
HTTP请求 -->
[Filter1] -> [Filter2] ->
[DispatcherServlet] -->
[Interceptor1] -> [Interceptor2] ->
Controller
↓
[Interceptor2] postHandle <- [Interceptor1] postHandle
[DispatcherServlet]渲染视图
[Filter2] doFilter后续处理
[Filter1] doFilter后续处理
HTTP响应
2
3
4
5
6
7
8
9
10
11
Spring Security 是否完全替代了拦截器?:不是。Spring Security 负责安全基础设施,拦截器仍可用于业务逻辑(如记录操作日志)。两者可协同工作。
# 拓展:参数白化(Parameter Allow List)
参数白化(Parameter Allow List):预定义合法参数名/参数结构的集合,所有请求参数需与白名单精确匹配才能进入处理流程。这是零信任安全模型的重要组成部分。指在一个系统或应用程序中,明确规定允许使用的参数集合。只有在这个白名单中的参数才被认为是合法的、可接受的,系统会对其进行正常处理;而不在白名单内的参数则会被视为非法或不可信的,可能会被系统拒绝、过滤或采取其他安全措施进行处理。
白化策略
策略类型 | 防御粒度 | 实现成本 | 动态能力 | 典型案例 |
---|---|---|---|---|
静态接口白名单 | 接口级别 | 低 | 无 | 基础CMS系统 |
动态方法白名单 | 方法级别 | 中 | 部分 | 金融交易API |
JSON Schema验证 | 参数结构 | 高 | 强 | OpenAPI合规系统 |
参数名混淆 | 协议级别 | 很高 | 需客户端配合 | 移动端安全通信 |