目录
(6)使用 @RequestHeader 注解来获取请求头中的信息
(7)使用 @CookieValue 注解来获取请求中的 Cookie 值
(8)直接在方法参数中声明 HttpServletRequest 对象来获取完整的请求信息
(2)StandardServletMultipartResolver解析
一、SpringMVC介绍
Spring MVC 是基于 Java 的 Web 框架,用于开发基于 Model-View-Controller(MVC)架构的 Web 应用程序。它提供了一种组织和管理 Web 应用程序的方式,将应用程序的逻辑分为模型(Model)、视图(View)和控制器(Controller),以提高代码的可维护性、可扩展性和可测试性。
1. 概念
把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)
- Model(模型):业务模型,负责在数据库中存取数据,对应项目只读service和dao
- View(视图):渲染数据,生成页面,是应用程序中处理数据显示的部分。对应项目中的Jsp
- Controller(控制器):控制MVC流程,接收用户在View层的输入,调用Model层获取数据,填充给View层。对应项目中的Servlet
通常控制器负责从视图读取用户输入的数据,然后从模型中请求数据,并将其交给视图
2. 特点
分离关注点(Separation of Concerns): Spring MVC 强调将应用程序的不同关注点分离,使代码更易于维护和扩展。模型、视图和控制器的分离使开发人员可以专注于各自的任务。
灵活的请求处理: Spring MVC 提供了强大的请求处理机制,支持 URL 映射、路径变量、请求参数绑定、表单验证等。这使开发人员能够更灵活地处理各种类型的请求。
松耦合和可测试性: Spring MVC 使用依赖注入和控制反转(IoC)容器,实现了松耦合,使应用程序组件可以方便地替换、测试和重用。这样的架构使单元测试和集成测试更容易实现。
可定制性: Spring MVC 允许开发人员自定义处理器拦截器、视图解析器、数据绑定规则等,以适应不同的项目需求。这样可以灵活地根据项目要求定制处理流程。
视图技术的支持: Spring MVC 支持多种视图技术,如 JSP、FreeMarker、Thymeleaf 等,使开发人员可以选择适合项目的视图模板,实现灵活的界面设计。
RESTful 支持: Spring MVC 提供了对 RESTful 风格的支持,可以轻松创建 RESTful Web 服务,从而满足现代 Web 应用的需求。
国际化和本地化支持: Spring MVC 提供了易于使用的国际化和本地化支持,使得应用程序可以轻松地实现多语言和多区域的功能。
- 客户端发送请求request,前端控制器(DispatcherServlet)接收请求
- 前端控制器请求处理器映射器(HandlerMapping)查找Handler,依据注解和xml配置查找
- 处理器映射器返回查找到的Handler给前端控制器
- 前端控制器请求处理器适配器(HandlerAdapter)执行Handler(通常称为Controller)
- 处理器适配器执行Handler处理器
- Handler处理器执行完毕以后返回给处理器适配器一个ModelAndView对象。(包含数据模型和视图名称)
- 处理器适配器把这把ModelAndView返回给前端控制器
- 前端控制器接收到ModelAndView后请求视图解析器(ViewResolver),对视图解析解析
- 视图解析器通过View的信息匹配到相应的视图结果,返回给前端控制器
- 前端控制器接收视图结果,并进行视图渲染,就是把Model模型数据填充到View视图的request作用域中, 生成最终的视图
- 最后把视图返回给客户端
二、SpringMVC的使用
开发流程
先新建web项目,并部署tomcat:SpringMVC项目新建
1. 搭建web项目
补全项目结构后如下:
2. 引入相关依赖
<properties>
<spring-version>6.0.11</spring-version>
</properties>
<dependencies>
<!-- 单元测试junit4 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- spring集成单元测试 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- springmvc核心包 -->
<!-- 包含了spring-context和spring-web等包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- mvc中响应数据JSON处理 -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- servlet依赖-->
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<!-- https://mvnrepository.com/artifact/jakarta.servlet.jsp/jakarta.servlet.jsp-api -->
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
注意
- servlet相关依赖需要注意当前tomcat的版本号,具体如下:
- tomcat也需要注意当前使用的jdk版本,具体如下:
3. 编写springmvc配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动注册组件 -->
<mvc:annotation-driven />
<!--扫描器-->
<context:component-scan base-package="com.jay.controller"/>
<!--配置处理静态资源(如样式表、JavaScript 文件、图片等)的映射规则-->
<mvc:resources location="/static/" mapping="/static/**"/>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="template/"/> <!-- 设置视图文件的前缀 -->
<property name="suffix" value=".jsp"/> <!-- 设置视图文件的后缀 -->
</bean>
<!-- 配置消息转换器,处理响应数据,一个处理json,一个处理普通文本,也可以只使用json,将普 通文本也用json格式返回,具体使用按需配置-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<!-- 配置json格式的响应,需要导入jackson-databind依赖-->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!-- <value>text/html;charset=UTF-8</value> -->
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
<!-- 配置普通文本的响应,当响应数据有中文乱码情况时,可以解决其问题-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<!-- 指定字符串的默认字符编码。在没有指定特定媒体类型的情况下,会使用这个默认字符编码。-->
<property name="defaultCharset" value="UTF-8"/>
<!-- 通过设置 supportedMediaTypes 属性来指定特定的媒体类型及其对应的字符编码。-->
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<!-- <value>application/json;charset=UTF-8</value> -->
<!-- <value>*/*;charset=UTF-8</value> -->
</list>
</property>
<!-- 用于避免响应头过大 -->
<property name="writeAcceptCharset" value="false"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
4. 编写web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<!--配置全局字符编码过滤filter-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置springMVC的前端控制器-->
<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 的初始化顺序-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
5. 创建jsp页面
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2023/8/18 0018
Time: 19:54
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>这是一个测试</h1>
</body>
</html>
6. 编写控制类
package com.jay.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Author jay_mosu
* @Date 2023/8/18 0018 11:32
* @PackageName com.jay.controller
* @ClassName TUserController
* @Description
*/
@Controller
public class TUserController {
@RequestMapping("/home")
public String home(){ return "test";}
}
6. 测试结果
三、SpringMVC的请求和响应
1. 请求参数获取方式
(1)通过方法参数直接接收
@RequestMapping("/test1")
public String test1(String name){
System.out.println(name);
return "test";
}
(2)使用 @RequestParam
注解来获取请求参数
// 使用@RequestParam注解,其中value表示指定接收的参数名称;required表示是否可以为空,默认true;defaultValue表示为空时的默认值
@RequestMapping("/test2")
public String test2(@RequestParam(value = "name",required = false,defaultValue = "张三") String name){
System.out.println(name);
return "test";
}
(3)使用@PathVariable注解获取参数
// 使用@PathVariable注解,传输参数时可以不通过url?aa=aa&bb=bb的方式,而是通过url/aa/bb的方式进行参数传输
@RequestMapping("/test3/{name}")
public String test3(@PathVariable String name){
System.out.println(name);
return "test";
}
(4)使用一个pojo对象封装请求参数
// 当请求是以表单参数或URL参数的方式传递数据,而不是将数据包含在请求体中的JSON或XML格式,
// SpringMVC会根据参数名称和请求参数名称的匹配自动将请求参数映射到对象的属性上。
@RequestMapping("/test4")
public String test4(Student student){
System.out.println(student);
return "test";
}
(5)使用RequestBody注解获取自定义对象
// 使用@RequestBody注解,可以将请求体中的数据反序列化成您指定的对象类型,适用于传输数据为一个自定义的对象
@RequestMapping("/test5")
public String test5(@RequestBody Map<String,String> map){
System.out.println(map);
return "test";
}
(6)使用 @RequestHeader
注解来获取请求头中的信息
// 使用@RequestHeader,可以将请求中的指定头部信息
@RequestMapping("/test6")
public String test6(@RequestHeader("User-Agent") String userAgent){
System.out.println(userAgent);
return "test";
}
(7)使用 @CookieValue
注解来获取请求中的 Cookie 值
// 使用@CookieValue获取cookie中的值
@RequestMapping("/test7")
public String test7(@CookieValue("name") String name) {
System.out.println(name);
// 使用 username 处理业务逻辑
return "test";
}
(8)直接在方法参数中声明 HttpServletRequest
对象来获取完整的请求信息
// 直接在方法参数中声明HttpServletRequest,再通过request获取其中的其他值
@RequestMapping("/test8")
public String test8(HttpServletRequest request) {
System.out.println(request.getParameter("name"));
// 使用 username 处理业务逻辑
return "test";
}
2. 响应
(1)返回视图名称字符串
// 返回视图名称字符串
@RequestMapping("/res1")
public String res1(){
// 因为视图解析器进行类配置,所以最终会走/template/test.jsp这个页面
return "test";
}
(2)返回视图对象(View Object)
// 返回视图对象
// 跳过视图解析器的过程,这在需要更精确的控制视图的情况下很有用。
@RequestMapping("/res2")
public View res2(){
return new InternalResourceView("/template/test.jsp"); // 返回视图对象
}
(3)返回ModelAndView对象
// 返回ModelAndView对象
// 返回一个ModelAndView对象,除了View的视图以外还可以添加数据
@RequestMapping("/res3")
public ModelAndView res3(){
// 创建一个ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
// 添加一个值,类似于Map的添加方式,在前端可以通过el表达式进行获取
modelAndView.addObject("name","小米");
// 设置跳转的视图名称,这将走视图解析器,因此类似于第一种只需要视图的名称就行
modelAndView.setViewName("test");
return modelAndView;
}
(4)重定向
// 重定向,可以跳过视图解析器
@RequestMapping("/res4")
public String res4(){
return "redirect:/template/test.jsp";
}
(5)请求转发
// 请求转发,可以跳过视图解析器
@RequestMapping("/res5")
public String res5(){
return "forward:/template/test.jsp";
}
(6)无返回值
// 无返回值
// 在没有返回值时,需要加上@ResponseBody,否则会默认将请求路径当做视图,最终会返回一个页面/template/res6.jsp
@ResponseBody
@RequestMapping("/res6")
public void res6(){
System.out.println("你好");
}
(7)返回一个任意的对象
// 返回一个任意的对象
// 返回一个对象时,需要加上@ResponseBody注解,否则同上
// 当返回类型为String类型时,如果有一个@ResponseBody注解,那么只是单纯将字符串进行了返回,是一个text/html的格式
// 当返回类型为其他对象时,一般是以JSON格式进行返回,需要加上jackson-databind依赖
// 如果上述两种返回类型出现中文乱码的情况,需要配置消息转换器,去处理返回类型的编码格式
@ResponseBody
@RequestMapping("/res7")
public List<Map<String,String>> res7(){
HashMap<String, String> map = new HashMap<>();
map.put("id","1");
map.put("name","张三");
map.put("age","15");
HashMap<String, String> map1 = new HashMap<>();
map1.put("id","2");
map1.put("name","王五");
map1.put("age","18");
ArrayList<Map<String, String>> maps = new ArrayList<>();
maps.add(map);
maps.add(map1);
return maps;
}
(8)返回响应状态码和消息
// 返回响应状态码和消息
// @ResponseStatus中可以设置当前请求最终响应回去的状态码以及其对应的消息
@ResponseBody
@ResponseStatus(HttpStatus.NOT_EXTENDED)
@RequestMapping("/res8")
public String res8(){
return "这是一个测试方法";
}
四、SpringMVC注解详解
- @Controller: 标记一个类为控制器,处理用户请求并返回视图或数据。用于标识处理请求的控制器类。
- @RequestMapping: 映射请求 URL 到处理方法,用于指定请求路径和 HTTP 方法。用于将请求映射到具体的处理方法,可以用于类级别和方法级别。
- @GetMapping、@PostMapping、@PutMapping、@DeleteMapping: 分别映射 GET、POST、PUT、DELETE 请求。用于更明确地指定请求方法。
- @RequestParam: 从请求参数中获取值,可设置默认值和是否必需。获取请求参数,用于处理方法参数。
- @PathVariable: 从 URL 路径中获取值,用于动态路径参数。从 URL 中提取参数值,用于处理方法参数。
- @RequestBody: 将请求体中的数据反序列化为对象,用于处理 JSON 或 XML 数据。将请求体数据映射到方法参数。
- @ResponseBody: 将方法返回值序列化为响应体,常用于返回 JSON 或 XML 数据。将方法返回值直接作为响应体返回给客户端。
- @ModelAttribute: 用于绑定请求参数到模型对象。将请求参数绑定到模型对象,通常用于表单数据绑定。
- @Valid: 用于数据校验,配合 JSR-303 校验注解使用。用于在处理方法中进行数据校验。
- @InitBinder: 用于初始化数据绑定,自定义数据转换和验证规则。自定义数据绑定和验证规则。
- @SessionAttributes: 用于将模型属性暂存到会话中,多用于表单数据的临时存储。将模型属性存储在会话中,以供多个请求使用。
- @ModelAttribute: 在方法参数上使用,表示将会话中的属性绑定到方法参数上。在方法参数中获取会话中的属性。
- @ExceptionHandler: 用于处理控制器中的异常,定义全局或局部的异常处理方法。处理控制器中的异常情况。
- @RequestMapping、@GetMapping 等的 "produces" 和 "consumes" 属性: 用于指定请求的媒体类型(Content-Type)和响应的媒体类型(Accept)。更精确地控制请求和响应的媒体类型。
- @RequestMapping 的 "params" 和 "headers" 属性: 用于基于请求参数和头部信息进行请求映射。基于请求参数和头部信息进行请求映射。
五、SpringMVC中常用配置解析
1. 组件扫描
配置组件扫描的基础包路径,让 Spring MVC 能够自动扫描并注册控制器、服务等组件。
<!--扫描器-->
<context:component-scan base-package="com.jay.controller"/>
2. 视图解析器 (jsp)
配置视图解析器,将逻辑视图名解析成实际的视图模板路径。
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="template/"/> <!-- 设置视图文件的前缀 -->
<property name="suffix" value=".jsp"/> <!-- 设置视图文件的后缀 -->
</bean>
3. 处理器映射
配置处理器映射,将请求映射到相应的控制器方法。可以让您更精确地控制 URL 与处理器的映射关系,并且不依赖于在控制器类上使用注解。如果更倾向于使用注解进行请求映射,那么就不需要进行配置。
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<bean name="/example" class="com.jay.controller.ExampleController" />
<bean name="/another-example" class="com.jay.controller.AnotherExampleController" />
4. 资源处理器
配置处理静态资源,如 CSS、JavaScript、图片等
<!--配置处理静态资源(如样式表、JavaScript 文件、图片等)的映射规则-->
<mvc:resources location="/static/" mapping="/static/**"/>
5. 消息转换器
配置消息转换器,用于处理请求和响应的消息转换。
<!-- 配置消息转换器,处理响应数据-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<!-- 配置json格式的响应-->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!-- <value>text/html;charset=UTF-8</value> -->
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
<!-- 配置普通文本的响应-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<!-- 指定字符串的默认字符编码。在没有指定特定媒体类型的情况下,会使用这个默认字符编码。-->
<property name="defaultCharset" value="UTF-8"/>
<!-- 通过设置 supportedMediaTypes 属性来指定特定的媒体类型及其对应的字符编码。-->
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<!-- <value>application/json;charset=UTF-8</value> -->
<!-- <value>*/*;charset=UTF-8</value> -->
</list>
</property>
<!-- 用于避免响应头过大 -->
<property name="writeAcceptCharset" value="false"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
6. 异常处理
(1)通过配置XML文件进行全局处理
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 配置自定义异常映射,可以通过该属性配置不同异常类对应的视图名称。 -->
<property name="exceptionMappings">
<props>
<!-- 配置自定义异常映射 -->
<prop key="com.jay.exception.MyException">error/myError</prop>
</props>
</property>
<!-- 配置默认异常映射,抛出未映射的异常时,会使用该视图进行处理 -->
<property name="defaultErrorView" value="error/error"/>
<!-- 配置异常参数 -->
<property name="exceptionAttribute" value="ex"/>
<!-- 配置异常的日志记录类别,当异常被处理时,会在日志中输出警告消息,可以通过设置该属性来指定日志的类别 -->
<property name="warnLogCategory" value="com.jay.exception.MyException"/>
<!-- 配置异常状态码。可以为特定的异常设置响应的 HTTP 状态码 -->
<property name="statusCodes">
<props>
<prop key="com.jay.exception.MyException">110</prop>
</props>
</property>
<!-- 配置默认的 HTTP 状态码。当没有为异常配置特定的状态码时,会使用该默认状态码 -->
<property name="defaultStatusCode" value="500"/>
<!-- 配置处理器的顺序,多个异常处理器按照该顺序依次执行 -->
<property name="order" value="1"/>
</bean>
(2)通过自定义异常处理器
package com.jay.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* @Author jay_mosu
* @Date 2023/8/19 0019 16:12
* @PackageName com.jay.config
* @ClassName ExceptionResolver
* @Description 全局异常处理器
*/
public class ExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 创建一个模型视图对象
ModelAndView modelAndView = new ModelAndView();
// 添加值
modelAndView.addObject("ex",ex);
// 设置跳转视图名称
modelAndView.setViewName("error/error");
// if(ex instanceof 自定义异常){
// modelAndView.setViewName("自定义异常对应的视图名称");
// }
return modelAndView;
}
}
<!-- 注册自定义的异常处理器 -->
<bean class="com.jay.config.ExceptionResolver"/>
(3)通过注解处理异常
使用@ExceptionHandler 注解对某种异常进行处理。
package com.jay.controller;
import com.jay.exception.MyException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/**
* @Author jay_mosu
* @Date 2023/8/19 0019 16:42
* @PackageName com.jay.controller
* @ClassName BaseExceptionController
* @Description 异常处理基类
*/
@Controller
public class BaseExceptionController {
// 这个方法专门用来处理MyException的异常
@ExceptionHandler(MyException.class)
public ModelAndView modelAndView(Exception ex){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("ex",ex);
modelAndView.setViewName("error/error");
// 一些其他处理
// 返回
return modelAndView;
}
}
注意:使用注解时,这个异常处理的方法只能作用于当前的Controller类中,如果希望它可以在所有Controller中生效,可以写一个基类专门处理异常,其他Controller类继承自它。
7. 拦截器
配置拦截器,用于在请求处理前后进行预处理或后处理操作。
(1)编写自定义的拦截类
实现HandlerInterceptor接口,并重新相关方法,如下所示:
package com.jay.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* @Author jay_mosu
* @Date 2023/8/19 0019 16:53
* @PackageName com.jay.interceptor
* @ClassName MyInterceptor
* @Description 拦截器
*/
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求进入控制器方法之前执行的逻辑
return true; // 返回 true 表示继续执行后续的处理流程,返回 false 则中断请求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在控制器方法执行完毕,渲染视图之前执行的逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在视图渲染完毕之后执行的逻辑
}
}
(2)编写配置文件
<!-- 配置拦截器,用于在请求处理前后进行预处理或后处理操作 -->
<mvc:interceptors>
<!-- 配置一个拦截器 -->
<mvc:interceptor>
<!-- 拦截的路径,可以使用通配符,有多个用“,”隔开,拦截以/**开头的路径 -->
<mvc:mapping path="/**"/>
<!-- 放行的路径,同拦截路径一样进行编写 -->
<mvc:exclude-mapping path="/login"/>
<!-- 拦截处理的方法 -->
<bean class="com.jay.interceptor.MyInterceptor"/>
</mvc:interceptor>
<!-- 配置更多。。。。。。 -->
</mvc:interceptors>
8. 文件上传配置
如果需要支持文件上传,需要配置文件上传解析器。
(1)CommonsMultipartResolver解析
- 导入依赖
<!-- 文件上传 -->
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
- 编写配置文件
<!-- 支持所有Servlet版本,但是在Spring 6.x版本已经移除了org.springframework.web.multipart.commons包 -->
<!-- 需要导入相关的依赖commons-fileupload -->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
<!--默认的编码-->
<property name="defaultEncoding" value="UTF-8"/>
<!--上传的总文件大小-->
<property name="maxUploadSize" value="1048576"/>
<!--上传的单个文件大小-->
<property name="maxUploadSizePerFile" value="1048576"/>
<!--内存中最大的数据量,超过这个数据量,数据就要开始往硬盘中写了-->
<property name="maxInMemorySize" value="4096"/>
<!--临时目录,超过 maxInMemorySize 配置的大小后,数据开始往临时目录写,等全部上传完成后,再将数据合并到正式的文件上传目录-->
<property name="uploadTempDir" value="file:///E:\\tmp"/>
</bean>
(2)StandardServletMultipartResolver解析
- 编写spring-mvc.xml相关配置
<!-- 配置文件上传 -->
<!-- 支持Servlet 3.0+容器,不需要导入外部库 -->
<!-- 基于Servlet3.0+容器的多部分解析器,利用了Servlet容器内置的文件上传支持,因此与容器的配置密切相关,需要在web.xml中进行配置 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
- web.xml头部声明
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
默认搭建的maven项目中是2.3,它不支StandardServletMultipartResolver的相关配置信息,必须要3.0+的版本才行,所以这里是修改成了4.0版本,更多的头部空间命名可以看idea的设置,如下所示(如果需要更改默认的设置,请参考修改web.xml默认配置 ):
- 编写web.xml相关配置信息
<!--配置springMVC的前端控制器-->
<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 的初始化顺序-->
<load-on-startup>1</load-on-startup>
<!--文件上传方法中的参数配置-->
<multipart-config>
<!--文件保存的临时目录,这个目录系统不会主动创建-->
<!-- <location>D:\\temp</location>-->
<!--上传的单个文件大小-->
<max-file-size>1048576</max-file-size>
<!--上传的总文件大小-->
<max-request-size>1048576</max-request-size>
<!--这个就是内存中保存的文件最大大小-->
<file-size-threshold>4096</file-size-threshold>
</multipart-config>
</servlet>
(3)区别
- CommonsMultipartResolver
- 优势:支持各种 Servlet 容器,不仅限于支持 Servlet 3.0+ 的容器,支持各种 Servlet 容器,不仅限于支持 Servlet 3.0+ 的容器。
- 劣势:支持各种 Servlet 容器,不仅限于支持 Servlet 3.0+ 的容器。
- StandardServletMultipartResolver
- 优势:不需要额外的文件上传依赖,利用 Servlet 3.0+ 的容器特性,无需引入外部库。
- 劣势:仅在支持 Servlet 3.0+ 的容器中有效。
(4)小结
综合考虑,如果你的项目是在支持 Servlet 3.0+ 的容器中运行,并且不希望引入额外的依赖,那么使用 StandardServletMultipartResolver 是一个不错的选择。如果你希望在更早版本的容器中运行,或者对文件上传有更高级的需求,你可以选择 CommonsMultipartResolver。
9. 配置 Thymeleaf 视图解析器
如果您选择使用 Thymeleaf 作为视图技术,可以按照以下方式配置视图解析器。
(1)导入相关依赖
<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
(2)编写配置文件
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="characterEncoding" value="UTF-8" />
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
</bean>
<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/" /> <!-- Thymeleaf 模板的前缀路径 -->
<property name="suffix" value=".html" /> <!-- Thymeleaf 模板的后缀 -->
<property name="templateMode" value="HTML" />
</bean>
六、SpringMVC中的文件上传和下载
1. 文件上传相关配置
略(上面已经配过了,可以参考上面文件上传配置)。
2. 文件上传测试
(1)编写一个文件上传的工具类
package com.jay.utils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
public class FileUploadUtil {
private static final String PHOTO_UPLOAD_PATH = "/images";
private static final String FILE_UPLOAD_PATH = "/uploadFile";
public static String uploadFile(MultipartFile file, HttpServletRequest request) {
if (file.isEmpty()) {
throw new IllegalArgumentException("No file uploaded");
}
try {
// 获取文件类型
String fileType = getFileType(file.getOriginalFilename());
String fileBefore = null;
if(isPhotoFile(fileType)){
// 如果是照片,计算文件哈希值
fileBefore = calculateFileHash(file.getBytes());
}else{
//否则,用uuid生成随机的文件名
fileBefore = UUID.randomUUID().toString().replace("-","").substring(0,16);
}
// 生成文件名
String fileName = generateFileName(fileType, fileBefore);
// 获取当前存放静态资源的目录
String absolutePath = request.getServletContext().getRealPath("/static");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("/yyyy/MM/dd/"); //创建日期,并标注时间显示格式
String format = simpleDateFormat.format(new Date()); //获取今天的日期
// 检查文件类型
if (isPhotoFile(fileType)) {
// 图片文件,放在file目录下的image下,并通过日期进行文件分类
String folderUrl = FILE_UPLOAD_PATH + PHOTO_UPLOAD_PATH + format;
return getFilePath(fileName,absolutePath,folderUrl,file);
} else {
// 其他文件,放在file路径下,通过其类型进行分类,在类型文件夹中再通过日期进行分类
String folderUrl = FILE_UPLOAD_PATH + File.separator + fileType + format;
return getFilePath(fileName,absolutePath,folderUrl,file);
}
} catch (IOException e) {
throw new RuntimeException("Failed to upload file");
}
}
private static String getFilePath(String fileName, String absolutePath, String folderUrl, MultipartFile file) throws IOException {
File folder = new File(absolutePath + folderUrl);
// 检查当前目录是否存在,不存在则创建
if(!folder.exists()){
folder.mkdirs();
}
String fileUrl = absolutePath + folderUrl + fileName;
File destFile = new File(fileUrl);
if (destFile.exists()) {
System.out.println("文件已存在:" + destFile.getAbsolutePath());
return "/static" + folderUrl + fileName;
}
// 上传文件
file.transferTo(destFile);
System.out.println("文件上传成功:" + destFile.getAbsolutePath());
// 返回文件名
return "/static" + folderUrl + fileName;
}
private static String getFileType(String fileName) {
String extension = StringUtils.getFilenameExtension(fileName);
if (extension != null) {
return extension.toLowerCase();
}
throw new IllegalArgumentException("Invalid file type");
}
private static String calculateFileHash(byte[] fileBytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = md.digest(fileBytes);
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to calculate file hash");
}
}
private static String generateFileName(String fileType, String fileHash) {
// 返回文件名,包括文件哈希值和文件类型后缀
return fileHash + "." + fileType;
}
private static boolean isPhotoFile(String fileType) {
// 判断文件类型是否为照片文件,根据实际需求进行判断
return fileType.equals("jpg") || fileType.equals("jpeg") || fileType.equals("png");
}
}
(2)文件上传测试
package com.jay.controller;
import com.jay.utils.FileUploadUtil;
import com.jay.utils.Results;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.UUID;
/**
* @Author jay_mosu
* @Date 2023/8/19 0019 21:33
* @PackageName com.jay.controller
* @ClassName UploadCOntroller
* @Description 文件上传控制器
*/
@Controller
public class UploadController {
@ResponseBody
@RequestMapping("/upload1")
public Results upload1(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException {
String s = FileUploadUtil.uploadFile(file, request);
String url = request.getScheme()+ "://"+request.getServerName() +":"+request.getServerPort() + s;
return Results.success("文件上传成功",url);
}
}
(3)测试结果
3. 文件下载
(1)文件下载工具类
package com.jay.utils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import java.io.*;
/**
* @Author jay_mosu
* @Date 2023/8/19 0019 23:46
* @PackageName com.jay.utils
* @ClassName FileDownloadUtil
* @Description 文件下载
*/
public class FileDownloadUtil {
/**
* @param filePath: 文件的相对路径
* @param response:
* @return 无
* @author jay_mosu
* @date 2023/8/21 0021 14:32
* @description 传统文件下载,通过输出流和输入流
*/
public static void fileDownload(String filePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取文件的绝对路径
String absoluteFilePath = request.getServletContext().getRealPath(filePath);
// 获取文件的输入流
InputStream inputStream = new FileInputStream(absoluteFilePath);
// 获取原文件名
String fileName = getFileNameFromPath(filePath);
// 获取文件类型(MIME)
String contentType = request.getServletContext().getMimeType(absoluteFilePath);
if (contentType == null) {
contentType = "application/octet-stream"; // 默认二进制流类型
}
// 设置响应头
response.setContentType(contentType);
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
// 将文件内容写入响应流
OutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outputStream.close();
}
// 辅助方法:从文件路径中获取文件名
private static String getFileNameFromPath(String filePath) {
int lastSlashIndex = filePath.lastIndexOf("/");
if (lastSlashIndex != -1) {
return filePath.substring(lastSlashIndex + 1);
}
return filePath;
}
/**
* @param filePath: 文件相对路径
* @param request: 请求,用于获取服务器路径
* @return 响应一个下载的文件
* @author jay_mosu
* @date 2023/8/21 0021 14:33
* @description SpringMVC的文件下载方式,传入文件所在路径和请求进行下载,文件名默认为当前路径中文件名
*/
public static ResponseEntity<byte[]> fileDownload(String filePath, HttpServletRequest request) throws IOException {
return fileDownload(null,filePath,request);
}
/**
* @param fileName: 指定文件的名称
* @param filePath: 文件所在路径
* @param request: 请求,用于获取服务器路径
* @return 响应一个下载的文件
* @author jay_mosu
* @date 2023/8/21 0021 14:36
* @description SpringMVC的文件下载方式,传入文件所在路径和请求进行下载,文件名为传入的文件名,如果文件名为空,则默认为路径中文件名
*/
public static ResponseEntity<byte[]> fileDownload(String fileName,String filePath, HttpServletRequest request) throws IOException {
// 获取文件的绝对路径
String absoluteFilePath = request.getServletContext().getRealPath(filePath);
// 获取文件的输入流
InputStream inputStream = new FileInputStream(absoluteFilePath);
if(fileName == null || fileName.equals("")){
// 获取原文件名
fileName = getFileNameFromPath(filePath);
}
byte [] body = null;
body = new byte[inputStream.available()];
inputStream.read(body);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=" + fileName);
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(body, headers, statusCode);
return entity;
}
}
(2)文件下载测试
@ResponseBody
@GetMapping("/download")
public void downloadFile(String relativeFilePath,HttpServletRequest request, HttpServletResponse response) throws IOException {
FileDownloadUtil.FileDownload(relativeFilePath,request,response);
}
}
(3)测试结果
4. 扩展内容
(1)提交一个含文件属性的对象
- 创建对象
package com.jay.entity;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
/**
* @Author jay_mosu
* @Date 2023/8/20 0020 13:04
* @PackageName com.jay.entity
* @ClassName TUser
* @Description
*/
@Data
public class TUser {
private String userName;
private String userPhone;
private String email;
private MultipartFile file;
}
- 编写上传接口
// @ModelAttribute可加可不加,用于将传入的属性和当前对象中的属性进行映射
@ResponseBody
@RequestMapping("/upload2")
public Results upload2(@ModelAttribute TUser user, HttpServletRequest request) throws IOException {
String s = FileUploadUtil.uploadFile(user.getFile(), request);
System.out.println(user);
String url = request.getScheme()+ "://"+request.getServerName() +":"+request.getServerPort() + s;
return Results.success("文件上传成功",url);
}
(2)多文件上传
// @ModelAttribute可加可不加,用于将传入的属性和当前对象中的属性进行映射
@ResponseBody
@RequestMapping("/upload3")
public Results upload3(@ModelAttribute List<MultipartFile> multipartFiles, HttpServletRequest request) throws IOException {
ArrayList<String> strings = new ArrayList<>();
for (MultipartFile multipartFile : multipartFiles) {
String s = FileUploadUtil.uploadFile(multipartFile, request);
String url = request.getScheme()+ "://"+request.getServerName() +":"+request.getServerPort() + s;
strings.add(url);
}
return Results.success("文件上传成功",strings);
}
七、返回结果封装
package com.jay.utils;
import io.micrometer.common.util.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author jay_mosu
* @Date 2023/8/19 0019 21:34
* @PackageName com.jay.utils
* @ClassName Results
* @Description 响应结果封装
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Results {
private Integer status;
private String msg;
private Object object;
/**
* @param status: 自定义返回的状态码
* @param msg: 自定义返回的消息
* @param object: 返回的对象
* @return 返回最终封装的结果对象
* @author jay_mosu
* @date 2023/8/19 0019 21:42
* @description 请求成功,自定义返回的状态码、消息、数据
*/
public static Results success(Integer status, String msg, Object object) {
if(msg == null || msg.equals("")){
msg = "请求成功";
}
return new Results(status,msg,object);
}
/**
* @param msg: 需要返回的消息
* @param object: 需要返回的数据对象
* @return 返回最终封装的结果对象
* @author jay_mosu
* @date 2023/8/19 0019 21:37
* @description 请求成功,自定义返回的消息、对象
*/
public static Results success(String msg, Object object) {
return success(200,msg,object);
}
/**
* @param msg: 需要返回的消息
* @return 返回最终封装的结果对象
* @author jay_mosu
* @date 2023/8/19 0019 21:39
* @description 请求成功,自定义返回的消息
*/
public static Results success(String msg){
return success(msg,null);
}
/**
* @return 返回最终封装的结果对象
* @author jay_mosu
* @date 2023/8/19 0019 21:44
* @description 请求成功,使用默认的返回信息
*/
public static Results success(){
return success("请求成功");
}
/**
* @param status: 自定义失败的状态码
* @param msg: 需要返回的消息
* @return 返回最终封装的结果对象
* @author jay_mosu
* @date 2023/8/19 0019 21:47
* @description 请求失败,自定义返回的状态码、消息
*/
public static Results fail(Integer status, String msg){
return new Results(status,msg,null);
}
/**
* @param msg: 需要返回的消息
* @return 返回最终封装的结果对象
* @author jay_mosu
* @date 2023/8/19 0019 21:48
* @description 请求失败,自定义返回的消息
*/
public static Results fail(String msg){
return fail(400,msg);
}
/**
* @return 返回最终封装的结果对象
* @author jay_mosu
* @date 2023/8/19 0019 21:49
* @description 请求失败,返回默认的请求失败信息
*/
public static Results fail(){
return fail("请求失败");
}
}
八、总结
Spring MVC是基于Spring框架的Web模块,以MVC架构为基础,提供了注解驱动、灵活的URL映射、请求参数绑定、视图解析器、异常处理、拦截器等丰富特性,使开发人员能够轻松构建现代的Java Web应用程序,实现模块化、可维护的代码,同时提供良好的开发效率和灵活性
原文链接:https://blog.youkuaiyun.com/jay_musu/article/details/132376205