SpringMVC框架
課程目標
理解SpringMVC核心框架流程
掌握SpringMVC框架配置方式
掌握SpringMVC的處理器方法參數與返回值的使用
掌握SpringMVC實現文件上傳功能
掌握SpringMVC攔截器的使用
了解RESTful互聯網軟件架構規(guī)范
掌握SSM框架集成的配置
課程重點
SpringMVC的配置的相關注解
SpringMVC框架處理器方法的參數及返回值的使用
SSM框架整合的集成配置
1.SpringMVC框架簡介
1.1.框架簡介
Spring MVC 是 Spring 提供給 Web 應用的框架設計。。
Spring MVC 角色劃分清晰,分工明細,并且和 Spring 框架無縫結合。作為當今業(yè)界最主流的 Web 開發(fā)框架,Spring MVC 已經成為當前javaWeb框架事實上的標準。
1.2.SpringMVC核心架構流程

springMVC核心架構的具體流程步驟如下:
- 首先用戶發(fā)送請求——>DispatcherServlet(前端控制器),前端控制器收到請求后自己不進行處理,而是委托給其他的解析器進行處理,作為統(tǒng)一訪問點,進行全局的流程控制;
- DispatcherServlet——>HandlerMapping, HandlerMapping將會把請求映射為HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象、多個HandlerInterceptor攔截器)對象,通過這種策略模式,很容易添加新的映射策略;
- DispatcherServlet——>HandlerAdapter,HandlerAdapter將會把處理器包裝為適配器,從而支持多種類型的處理器,即適配器設計模式的應用,從而很容易支持很多類型的處理器;
- HandlerAdapter——>處理器功能處理方法的調用,HandlerAdapter將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理;并返回一個ModelAndView對象(包含模型數據、邏輯視圖名);
- ModelAndView的邏輯視圖名——> ViewResolver, ViewResolver將把邏輯視圖名解析為具體的View,通過這種策略模式,很容易更換其他視圖技術;
- View——>渲染,View會根據傳進來的Model模型數據進行渲染,此處的Model實際是一個Map數據結構,因此很容易支持其他視圖技術;
- 返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶,到此一個流程結束。
1.3.前后端分離架構下的SpringMVC
那么,在前后端分離模式下,視圖層要分離出去,成為一個獨立工程;或者說:視圖層不在由服務器端控制。 所以,在前后端分離模式下,SpringMVC的核心架構流程修改如下:
2.SpringMVC框架實例
2.1.創(chuàng)建工程 項目類型-Jakarta EE 項目名: springmvc
編輯部署設置
1.應用程序上下文: /springmvc
2.HTTP 端口: 3333
2.2.在pom.xml文件中添加依賴 在resources下新建logback.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version> <!-- 建議使用最新穩(wěn)定版 -->
</dependency>
<!-- Logback 實現 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<!-- Logback 核心 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.4.11</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定義日志輸出格式 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [springmvc] %-5level - %msg%n" />
<!-- 控制臺輸出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 設置日志級別 -->
<logger name="com.neuedu.springmvc" level="INFO" />
<logger name="org.springframework" level="INFO" />
<!-- 根日志配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
2.3.在config包下創(chuàng)建MvcConfig.java類
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
@ComponentScan("com.neuedu.springmvc")
@EnableWebMvc //使用EnableWebMvc開啟springmvc注解方式配置
public class MvcConfig implements WebMvcConfigurer {
/**
* 跨域設置
*/
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedOrigins("*");
}
/**
* 添加攔截器(可以添加多個)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//registry.addInterceptor(null).addPathPatterns("/**");
}
/**
* 配置文件上傳解析器
*/
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setDefaultCharset(Charset.forName("UTF-8"));
List<MediaType> list = new ArrayList<MediaType>();
list.add( MediaType.APPLICATION_JSON);
converter.setSupportedMediaTypes(list);
converters.add(converter);
converters.add( new MappingJackson2HttpMessageConverter() );
}
}
2.4.在config包下創(chuàng)建MvcInit.java類
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import jakarta.servlet.Filter;
/**
* 1、創(chuàng)建Mvc初始化類,需要繼承AbstractAnnotationConfigDispatcherServletInitializer類
* springmvc容器的父容器spring配置類
* 實際工作中我們的項目比較復雜,可以將controller層放在springmvc容器中
* 其他層,如service層、dao層放在父容器了,bean管理起來更清晰一些
* 也可以沒有父容器,將所有bean都放在springmvc容器中
*/
public class MvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* 2、設置springmvc容器的spring配置類
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
/**
* 3、配置DispatcherServlet的url-pattern
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 4、注冊攔截器
*/
@Override
protected Filter[] getServletFilters() {
//添加攔截器,解決亂碼問題
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceRequestEncoding(true);
characterEncodingFilter.setForceResponseEncoding(true);
return new Filter[]{characterEncodingFilter};
}
/**
* 5、設置文件上傳相關配置
*/
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
//設置允許上傳的單個文件大小 5M
long maxFileSize = 5 * 1024 * 1024;
//設置允許上傳的總文件大小 20M
long maxRequestSize = 20 * 1024 * 1024;
//設置文件上傳閾值
int fileSizeThreshold =0;
registration.setMultipartConfig(
new MultipartConfigElement(null,maxFileSize,maxRequestSize,fileSizeThreshold));
}
}
2.5.創(chuàng)建Controller控制器
在controller包下創(chuàng)建 HelloController控制器。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String hello() throws Exception {
return "hello world!";
}
}
@Controller:此注解聲明在類上,表示此類是一個控制器類,并被納入到 Spring 容器中;
@RequestMapping:此注解明在類上,或者方法上;表示將一個請求url映射給控制器方法。
@ResponseBody:此注解聲明在類上,或者方法上;表示處理器方法直接返回數據。
2.6.測試
啟動Tomcat服務器,使用Apifox發(fā)送請求測試
http://localhost:3333/springmvc/hello
2.7.相關注解詳解
2.7.1.@RequestMapping注解
用于建立請求URL和控制器方法之間的對應關系。
- @RequestMapping 應用在處理器類上: 設置請求URL的第一級訪問目錄,使我們的URL可以按照模塊化管理
@RequestMapping("/user")
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String hello() throws Exception {
return "hello world";
}
}
上面處理器方法請求url應該這樣寫:http://localhost:3333/springmvc/user/hello
2.@RequestMapping注解中常用屬性有:
value:用于指定請求的URL。 method:用于指定請求的方式。
@RequestMapping(value="/hello",method=RequestMethod.POST)
public String hello() throws Exception {
return "hello world";
}
如果使用 get 方式訪問此方法時,就會出現異常。
2.7.2.@ResponseBody注解
@ResponseBody注解的核心作用是告訴 Spring MVC 框架:不要將方法的返回值解析為一個視圖(View)名稱去渲染頁面,而是直接將這個返回值作為 HTTP 響應體(Response Body)的內容。它會自動根據客戶端的請求頭(如 Accept)來決定使用哪個 HttpMessageConverter 將方法的返回值轉換成合適的格式(如 JSON、HTML、Text),并寫入到響應體中,同時設置相應的 Content-Type 頭。
- @ResponseBody 應用在處理器類上:此處理器類中的所有方法都直接返回數據。
- @ResponseBody 應用在處理器類的某個方法上:此處理器類中的某個方法直接返回數據。
2.7.3.@GetMapping與@PostMapping
@GetMapping是一個組合注解,是@RequestMapping(method = RequestMethod.GET)的縮寫。
@PostMapping是一個組合注解,是@RequestMapping(method = RequestMethod.POST)的縮寫。
3.控制器方法的參數與返回值
3.1.控制器方法參數
1.使用@RequestParam注解,將請求參數自動綁定到控制器方法參數
public String p1(@RequestParam("name") String name,
@RequestParam("age") int age) {}
@RequestParam("name")中的name,必須和請求參數名一致
@RequestParam注解可以根據控制器方法參數類型,自動完成類型轉換
2.控制器中方法可以使用對象接收多個請求參數數據
@Data
public class User {
private String name;
private Integer age;
}
public String p2(User user) {}
實體類屬性名必須和請求參數名一致
3.接收AJAX請求提交json數據
public String p3(@RequestBody User user) {}
@RequestBody 注解的主要作用是:將 HTTP 請求體(Body)中的 JSON/數據,自動綁定到后端的 Java 對象上。
實體類屬性名必須和JSON數據屬性名一致
3.2.控制器方法返回值
在前后端分離模式中,SpringMVC的控制器方法直接返回數據。
使用@ResponseBody注解
4.SpringMVC文件上傳
4.1.創(chuàng)建上傳文件控制器
import java.io.File;
import java.util.UUID;
import org.springframework.stereotype.Controller;
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;
@Controller
public class UploadController {
@ResponseBody
@RequestMapping("/upload")
// 必須要有MultipartFile類型參數
public String upload(@RequestParam("file") MultipartFile myFile) {
//上傳圖片存儲目錄
String path = "d:/upload";
//獲取文件名并使用UUID生成新文件名
String fileName = myFile.getOriginalFilename();
String newFileName = UUID.randomUUID() + fileName.substring(fileName.lastIndexOf("."));
//在指定上傳圖片存儲目錄中創(chuàng)建新文件
File targetFile = new File(path, newFileName);
//如果找不到指定目錄和文件,就新創(chuàng)建此目錄和文件
if (!targetFile.exists()) {
targetFile.mkdirs();
}
//將文件寫入硬盤(myFile在內存中)
try {
myFile.transferTo(targetFile);
} catch (Exception e) {
e.printStackTrace();
}
return "ok";
}
}
注意:
- 文件上傳;只有使用了multipart/form-data,才能上傳二進制數據;
- method:必須為post方式提交。
5.SpringMVC攔截器
什么是攔截器?
攔截器是 Spring MVC 框架提供的一種機制,允許你在請求到達 Controller 之前和響應返回客戶端之后插入自定義邏輯。它類似于 Servlet 中的 Filter,但更深度地集成到 Spring MVC 的核心流程中。
典型應用場景:
- 身份認證 (Authentication):檢查用戶是否登錄。
- 授權 (Authorization):檢查用戶是否有權限訪問某個資源。
- 日志記錄 (Logging):記錄請求信息、執(zhí)行時間等。
- 主題更改 (Theme Changing):根據用戶偏好設置主題。
- 性能監(jiān)控:計算 Controller 方法的執(zhí)行時間
實現步驟
- 實現一個攔截器需要三個步驟:
- 創(chuàng)建攔截器類:實現 HandlerInterceptor 接口
- 重寫關鍵方法:主要是 preHandle, postHandle, afterCompletion。
- preHandle:該方法在 controller 執(zhí)行前執(zhí)行,可以實現對數據的預處理。 如果方法返回 true ,則繼續(xù)調用下一個資源。否則不在繼續(xù)調用。
- postHandle:該方法在處理器執(zhí)行后,生成視圖前執(zhí)行。這里有機會修改視圖層數據。
- afterCompletion:最后執(zhí)行,通常用于記錄日志,釋放資源,處理異常。
- 注冊攔截器:通過配置類將攔截器添加到 Interceptor Registry 中。
示例:實現一個日志攔截器
第 1 步:創(chuàng)建攔截器類 LogInterceptor:記錄每個請求的開始和結束時間,并計算處理耗時
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日志攔截器
*/
public class LogInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
// 使用 ThreadLocal 來保存線程安全的變量(每個請求的開始時間)
private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 在 Controller 方法執(zhí)行之前調用
long startTime = System.currentTimeMillis();
startTimeThreadLocal.set(startTime);
logger.info("[preHandle] 請求 URL: '{}'", request.getRequestURL());
logger.info("[preHandle] 請求開始時間: {}", sdf.format(new Date()));
// 返回 true 表示繼續(xù)流程,調用下一個攔截器或 Controller
// 返回 false 則中斷流程,需要自己通過 response 產生響應
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 2. 在 Controller 方法執(zhí)行之后,但在視圖渲染之前調用
logger.info("[postHandle] 請求 URL: '{}' 控制器處理完畢", request.getRequestURL());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 3. 在整個請求結束之后(視圖渲染完畢)調用,常用于資源清理
long startTime = startTimeThreadLocal.get();
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
logger.info("[afterCompletion] 請求處理完畢時間: {}", sdf.format(new Date()));
logger.info("[afterCompletion] 請求 URL: '{}' 總耗時: {}ms", request.getRequestURL(), executeTime);
// 非常重要:清理 ThreadLocal,避免內存泄漏
startTimeThreadLocal.remove();
}
}
第 2 步:注冊攔截器(使用配置類)
修改 MvcConfig.java
@Override
public void addInterceptors(InterceptorRegistry registry) {
//registry.addInterceptor(null).addPathPatterns("/**");
// 注冊日志攔截器,攔截所有請求
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**"); // 攔截所有路徑
}
第 3 步:發(fā)送請求測試
6.RESTful互聯網軟件架構
6.1.什么是REST
什么是 RESTful 架構?
REST(Representational State Transfer,表述性狀態(tài)轉移)是一種軟件架構風格,而不是標準或協(xié)議。它由 Roy Fielding 博士在 2000 年提出,是一組設計原則和約束條件。
RESTful 是指遵循 REST 原則的 Web 服務或 API。
REST 的六個核心約束
客戶端-服務器架構 (Client-Server)
分離關注點:客戶端負責用戶界面和用戶體驗,服務器負責數據處理和存儲
優(yōu)勢:提高可移植性、簡化服務器組件、允許各部分獨立演進無狀態(tài) (Stateless)
每個請求必須包含處理該請求所需的所有信息
服務器不存儲客戶端上下文,會話狀態(tài)完全保存在客戶端
優(yōu)勢:提高可見性、可靠性、可擴展性可緩存 (Cacheable)
響應必須明確標識是否可緩存
減少客戶端-服務器交互,提高性能
優(yōu)勢:減少網絡延遲,提高效率統(tǒng)一接口 (Uniform Interface)
資源標識:每個資源都有唯一的標識符(URI)
通過表述操作資源:客戶端操作資源的表述而不是資源本身
自描述消息:每個消息包含足夠信息來描述如何處理分層系統(tǒng) (Layered System)
系統(tǒng)可以由多個層次組成,每個層次只知道相鄰層次
優(yōu)勢:提高可擴展性、簡化系統(tǒng)復雜度按需代碼 (Code-On-Demand,可選)
服務器可以臨時擴展客戶端功能
RESTful API 設計原則
- 資源導向設計
一切皆資源(名詞,而不是動詞)
使用 URI 標識資源:
/user - 用戶集合
/users/123 - 特定用戶
/users/123/orders - 用戶的訂單 - HTTP 方法的使用
- GET 查詢資源
- POST 創(chuàng)建新資源
- PUT 更新或創(chuàng)建資源
- DELETE 刪除資源
RESTful API 示例
用戶管理 API
獲取所有用戶
METHOD: GET
URL: /user
Content-Type: application/json
獲取特定用戶
METHOD: GET
URL: /user/123
Content-Type: application/json
創(chuàng)建新用戶
METHOD:POST
URL: /user
Content-Type: application/json
更新用戶
METHOD:PUT
URL: /user
Content-Type: application/json
刪除用戶
METHOD: DELETE
URL: /user/123
Content-Type: application/json
響應結果封裝類
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Result<T> {
private int code; // 狀態(tài)碼
private String msg; // 消息
private T data; // 數據
// 成功靜態(tài)方法
public static <T> Result<T> success(T data) {
return Result.<T>builder()
.code(200)
.msg("success")
.data(data)
.build();
}
// 失敗靜態(tài)方法
public static <T> Result<T> error(int code, String msg) {
return Result.<T>builder()
.code(code)
.msg(msg)
.build();
}
}
User類
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
}
UserController
import com.neuedu.springmvc.po.Result;
import com.neuedu.springmvc.po.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController//相當于@Controller+@ResponseBody
@RequestMapping("/user")//一級URL
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
//1. 查詢所有用戶
@GetMapping
public Result<List<User>> queryAllUsers() {
List<User> users = new ArrayList<>();
users.add(new User(1,"建宇",22,"23@11.com"));
users.add(new User(2,"卓航",22,"24@11.com"));
return Result.success(users);
}
// 2. 根據ID查詢用戶
@GetMapping("/{id}")
public Result<User> queryUserById(@PathVariable("id") int id) {
return Result.success(new User(id,"建宇",22,"23@11.com"));
}
// 3. 新增用戶
@PostMapping
public Result<String> addUser(@RequestBody User user) {
logger.info("[新增用戶信息]: '{}'",user);
return Result.success("新增成功");
}
// 4. 修改用戶
@PutMapping
public Result<String> updateUser(@RequestBody User user) {
logger.info("[修改用戶信息]: '{}'",user);
return Result.success("修改成功");
}
// 5. 刪除用戶
@DeleteMapping("/{id}")
public Result<String>deleteUser(@PathVariable("id") int id) {
logger.info("[修改用戶Id]: '{}'",id);
return Result.success("刪除用戶");
}
}