1. SpringMVC:为什么它是Java Web开发的“交通枢纽”?
如果你刚开始接触Java Web开发,可能会被各种框架搞得眼花缭乱。我刚开始学的时候,也觉得SpringMVC这个名字听起来很“高大上”,好像是什么深奥的架构。但后来用多了才发现,它其实就像一个城市里的“交通指挥中心”。想象一下,一个城市每天有成千上万的车辆(HTTP请求)从四面八方涌来,如果没有一个高效的交通指挥系统(SpringMVC),整个城市就会陷入瘫痪和混乱。SpringMVC就是这个指挥中心,它负责接收所有进城的车辆(请求),然后根据车辆的目的地(URL),快速、准确地把它们引导到对应的处理站(Controller方法),处理完后再把结果(响应)送回去。
那么,SpringMVC到底是什么呢?简单说,它是Spring家族中专门用来构建Web应用程序的模块。在经典的SSM(SpringMVC + Spring + MyBatis)组合里,它就扮演着“表现层”的角色,负责和用户的浏览器打交道。你可能会问,Servlet也能干这个活,为什么还要用框架?我刚开始也有这个疑问。后来一个项目里,我用纯Servlet写了个小功能,光是解析请求参数、处理响应、跳转页面就写了一堆重复代码,维护起来特别头疼。而SpringMVC把这些繁琐的“脏活累活”都封装好了,提供了清晰的分工和流程,让我们开发者只需要关注最核心的业务逻辑:收到什么数据,要做什么处理,最后返回什么结果。
它的核心设计思想源于经典的MVC模式,但做了更精细的组件化拆分。MVC大家应该不陌生,就是把一个应用分成三块:Model(模型,装数据)、View(视图,展示界面)、Controller(控制器,处理逻辑)。SpringMVC在这个基础上,把“控制器”这个角色又拆成了好几个专业组件,比如专门找路的HandlerMapping、专门干活的HandlerAdapter、专门解析视图的ViewResolver。这种“专业的人干专业的事”的设计,让整个请求处理流程像一条高效的流水线,每个环节职责单一,不仅性能好,而且特别容易扩展和维护。接下来,我们就钻进这个“指挥中心”内部,看看它的核心部件到底是怎么运转的。
2. 深入核心组件:拆解SpringMVC的“五脏六腑”
要理解SpringMVC怎么工作,光知道它是个“指挥中心”还不够,你得认识里面的关键“官员”和“部门”。这些组件各司其职,协同合作,构成了框架的骨架。我刚开始看源码的时候也是一头雾水,后来我把它们想象成一个公司里的不同岗位,一下子就清晰了。
2.1 总指挥:DispatcherServlet
这是整个框架的绝对核心,所有请求的入口和出口。它本质上就是一个Servlet,配置在web.xml里,会拦截你指定的所有请求(通常是/)。你可以把它理解为公司的“前台总机”或者“调度中心”。它自己不处理具体业务,但所有事情都得经过它。它的主要工作就三件:接收请求、协调调度、返回响应。当一个HTTP请求到达Tomcat服务器后,Tomcat会根据web.xml的配置,把匹配的请求统统交给DispatcherServlet来处理。我踩过的一个坑是,在web.xml里配置它的<url-pattern>时,新手容易写成/*.do,但实际上更常见的做法是配置成/,表示拦截所有请求(静态资源除外,这个后面会说),这样才能支持更灵活的URL风格,特别是RESTful API。
2.2 导航员:HandlerMapping
请求来了,DispatcherServlet第一个问题就是:“这个活该找谁干?” 这时候就需要HandlerMapping(处理器映射器)出场了。它的职责就是根据当前请求的URL,找到对应的处理器(Handler)。这个过程就像你用地图APP导航:输入目的地(URL),APP(HandlerMapping)帮你找到最佳的路线和终点(具体的Controller和方法)。SpringMVC提供了多种映射策略,比如基于注解的RequestMappingHandlerMapping(现在最常用),基于Bean名称的BeanNameUrlHandlerMapping,还有以前基于XML配置的SimpleUrlHandlerMapping。当你在一个Controller类的方法上写@RequestMapping("/user/login")时,RequestMappingHandlerMapping就会把这个注解信息注册到自己的“地图”里,建立URL和方法的对应关系。
2.3 执行者与适配器:Handler与HandlerAdapter
找到该谁干活(Handler)之后,下一个问题是怎么让他干。这里的Handler通常就是我们写的Controller中的某个方法。但是,Controller的方法千变万化,参数类型、返回值类型都可能不同。DispatcherServlet需要一个统一的方式来调用这些各异的方法。这就是HandlerAdapter(处理器适配器)存在的意义。适配器模式在这里得到了完美体现。HandlerAdapter就像一个“万能接口转换器”,它知道如何与特定类型的Handler打交道。比如,最常用的RequestMappingHandlerAdapter就知道如何去执行那些带有@RequestMapping注解的方法,它会负责解析方法参数(把HTTP请求里的参数自动绑定到方法的String、int、甚至自定义对象参数上),调用方法,再把方法的返回值(可能是个字符串"success",也可能是个ModelAndView对象)封装成一个统一的ModelAndView结构,交给后面的流程。
我遇到过一个问题,自定义了一个处理器,实现了Controller接口,但请求总是报404。排查了半天才发现,光有Handler还不行,还必须有一个能适配它的HandlerAdapter。默认情况下,开启注解驱动(<mvc:annotation-driven/>)会注册多个适配器,其中就包括能处理Controller接口的SimpleControllerHandlerAdapter。如果你用了很冷门的处理器类型,可能就需要自己配置对应的适配器了。
2.4 视图渲染师:ViewResolver与View
业务逻辑处理完了,生成了数据和视图名,接下来就要把结果展示给用户看了。ViewResolver(视图解析器)的任务是把控制器返回的逻辑视图名(比如"userInfo")解析成具体的视图对象(View)。这个过程可以理解为,经理说“把那份报告给我”,秘书(ViewResolver)需要知道报告是放在哪个文件夹、是什么格式的(是PDF还是Word),然后找到它。通常我们会配置一个InternalResourceViewResolver,并设置前缀(prefix)和后缀(suffix),例如前缀是/WEB-INF/views/,后缀是.jsp。那么当控制器返回"userInfo"时,ViewResolver就会把它解析成/WEB-INF/views/userInfo.jsp这个具体的JSP文件路径,并封装成一个JstlView对象。
最后,View接口负责真正的渲染工作。它拿到Model(其实就是一堆存在request域里的数据)和解析好的物理视图,进行合成。对于JSP视图,就是执行JSP页面,把Model中的数据用EL表达式${}展示出来;对于JSON视图,则是把数据序列化成JSON字符串写入响应体。SpringMVC支持多种视图技术,除了JSP,还有Thymeleaf、FreeMarker、PDF、Excel等,非常灵活。
3. 一次请求的完整旅程:图解SpringMVC工作流程
知道了每个部件是干嘛的,现在我们把他们串起来,看看一个HTTP请求从浏览器发出,到收到响应,在SpringMVC内部到底经历了怎样的“奇幻漂流”。这个过程是面试中的常客,也是理解框架精髓的关键。我画过无数遍这个流程图,每次都有新体会。
第一步:用户发起请求。 用户在浏览器输入http://yourdomain.com/app/user/login,点击提交。这个请求经过网络,到达我们的Web服务器(如Tomcat)。
第二步:DispatcherServlet拦截请求。 Tomcat根据web.xml的配置,发现所有以/app/开头的请求都应该由名为dispatcher的Servlet处理。于是,请求被转交给了DispatcherServlet的service()方法,最终会调用到doDispatch()这个核心调度方法。
第三步:寻找合适的处理器(Handler)。 DispatcherServlet拿着请求的URL(这里是/user/login),去问所有注册的HandlerMapping:“嘿,这个地址你们谁负责?” 通常,RequestMappingHandlerMapping会翻看自己维护的注解映射表,发现@RequestMapping("/user/login")对应的是UserController类的login方法。于是它返回一个HandlerExecutionChain对象,这个对象里不仅包含了找到的Handler(即login方法对应的可执行对象),还可能包含一系列配置好的Interceptor(拦截器)。
第四步:获取对应的处理器适配器(HandlerAdapter)。 DispatcherServlet拿到Handler后,它自己还是不知道该怎么调用这个方法。于是它又去问所有注册的HandlerAdapter:“你们谁擅长跟这种类型的Handler打交道?” RequestMappingHandlerAdapter举手说:“我擅长,这种带@RequestMapping注解的方法归我管。” 于是DispatcherServlet就拿到了合适的适配器。
第五步:执行处理器,处理业务逻辑。 这是最核心的一步。RequestMappingHandlerAdapter开始工作:
- 它会解析
login方法的签名,发现方法需要两个参数:一个String username,一个String password。 - 它从HTTP请求中(可能是URL参数,也可能是表单POST体)提取出
username和password的值。 - 它可能会进行类型转换(比如把字符串
"123"转成int类型),数据校验(如果参数有@Valid注解)。 - 然后,它调用
UserController的实例,执行login(username, password)这个方法。 - 方法内部可能会调用Service层、访问数据库,最后返回一个结果。假设我们返回一个字符串
"success"。
第六步:处理返回结果,封装ModelAndView。 HandlerAdapter把方法返回的"success"字符串,以及方法执行过程中可能放入Model的数据(比如model.addAttribute("user", userObj)),一起封装成一个ModelAndView对象。这个对象包含了模型数据和逻辑视图名。
第七步:解析视图。 DispatcherServlet拿着ModelAndView中的逻辑视图名"success",去找ViewResolver。InternalResourceViewResolver根据配置(前缀/WEB-INF/views/,后缀.jsp),把"success"解析成具体的视图对象:/WEB-INF/views/success.jsp对应的JstlView。
第八步:渲染视图。 DispatcherServlet把上一步得到的View对象,以及ModelAndView中的模型数据,交给View去渲染。JstlView会将模型数据(那个user对象)设置到HttpServletRequest的属性中,然后请求转发(RequestDispatcher.forward)到/WEB-INF/views/success.jsp页面。
第九步:生成响应。 JSP页面被执行,生成HTML。HTML内容作为响应体,通过一系列的过滤器、Servlet,最终由Tomcat发送回用户的浏览器。用户就看到登录成功的页面了。
整个流程中,DispatcherServlet就像一个经验丰富的项目经理,它自己不写代码,但知道每个环节该找谁,确保流程顺畅。而HandlerMapping、HandlerAdapter、ViewResolver就是它手下的几个得力干将。理解了这个流程,你再去看SpringMVC的源码,或者配置一些高级功能,就会觉得有章可循,不再是黑盒了。
4. 从零搭建:手把手实现一个SpringMVC入门案例
理论讲得再多,不如亲手搭一个。下面我就带你从零开始,创建一个最简单的SpringMVC项目。我会把每一步的细节和可能遇到的坑都讲清楚,你可以跟着一起操作。
4.1 环境准备与项目创建
首先,你需要一个Java开发环境(JDK 8或以上)和一个IDE,比如IntelliJ IDEA。我们创建一个Maven Web项目。
- 新建项目:在IDEA里选择
New Project->Maven,勾选Create from archetype,然后选择maven-archetype-webapp。这个骨架会帮我们生成一个标准的Web目录结构。 - 配置pom.xml:项目创建好后,打开
pom.xml,添加SpringMVC的依赖。核心就三个:spring-webmvc(它本身会依赖spring-context和spring-web),以及Servlet API。注意Servlet API的scope要设为provided,因为Tomcat服务器本身会提供这个库。
<properties>
<spring.version>5.3.23</spring.version> <!-- 使用一个稳定的版本 -->
</properties>
<dependencies>
<!-- Spring MVC 核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- JSP API (如果用JSP做视图) -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 补全目录结构:Maven骨架生成的
webapp目录下可能缺少WEB-INF和web.xml。你需要手动创建src/main/webapp/WEB-INF目录,并在其下创建web.xml文件。同时,建议在src/main下创建java和resources目录,分别存放Java源代码和配置文件。
4.2 配置核心控制器与Spring容器
这是让SpringMVC跑起来最关键的两步配置。
- 配置DispatcherServlet (web.xml):在
web.xml中,我们需要声明并配置DispatcherServlet。同时,告诉它Spring配置文件的位置,这样它启动时就会去加载这个配置,初始化Spring的ApplicationContext(IoC容器)。
<?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">
<!-- 1. 配置SpringMVC的前端控制器:DispatcherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定SpringMVC配置文件的位置和名称 -->
<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>
<!-- 2. 配置DispatcherServlet的映射,拦截所有请求 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 3. 配置字符编码过滤器,解决POST请求中文乱码 -->
<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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
这里有个细节:<url-pattern>/</url-pattern>表示拦截所有请求,但不会拦截像.jsp这样的请求。对于静态资源(.js, .css, .jpg),需要额外配置,否则也会被DispatcherServlet拦截导致404。我们稍后在Spring配置里解决。
- 编写SpringMVC核心配置文件 (spring-mvc.xml):这个文件放在
src/main/resources下。它的作用是开启注解驱动、自动扫描组件、配置视图解析器等。
<?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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 1. 开启注解扫描,让Spring管理Controller等组件 -->
<context:component-scan base-package="com.yourpackage.controller"/>
<!-- 2. 开启MVC注解驱动。
这一行配置至关重要!它相当于一次性注册了:
- RequestMappingHandlerMapping (处理器映射器)
- RequestMappingHandlerAdapter (处理器适配器,支持@RequestBody, @ResponseBody等)
- ExceptionHandlerExceptionResolver (异常处理器)
等一系列默认组件。 -->
<mvc:annotation-driven/>
<!-- 3. 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/> <!-- 视图文件的前缀 -->
<property name="suffix" value=".jsp"/> <!-- 视图文件的后缀 -->
</bean>
<!-- 4. 处理静态资源:告诉DispatcherServlet,对于`/resources/**`这样的路径,直接去`/resources/`目录下找,别走MVC流程 -->
<mvc:resources mapping="/resources/**" location="/resources/"/>
<!-- 或者使用更简单的方式:将静态资源请求交由默认的Servlet处理 -->
<mvc:default-servlet-handler/>
</beans>
4.3 编写第一个Controller与视图
环境配好了,我们来写点真正的代码。
- 创建Controller:在
com.yourpackage.controller包下创建HelloController。
package com.yourpackage.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller // 这个注解标记该类是一个SpringMVC的控制器,会被组件扫描到
public class HelloController {
@RequestMapping("/hello") // 建立URL `/hello` 与下面方法的映射关系
public String sayHello() {
System.out.println("HelloController的sayHello方法被调用了!");
// 返回一个逻辑视图名,视图解析器会将其解析为 `/WEB-INF/views/welcome.jsp`
return "welcome";
}
}
- 创建JSP视图:在
webapp/WEB-INF/views/目录下(如果没有就创建),新建一个welcome.jsp文件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Welcome</title>
</head>
<body>
<h1>恭喜你!第一个SpringMVC程序运行成功!</h1>
<p>当前时间:<%= new java.util.Date() %></p>
</body>
</html>
- 配置与运行:将项目部署到Tomcat(IDEA里配置Tomcat运行环境,选择
war exploded部署方式)。启动Tomcat,在浏览器访问http://localhost:8080/你的项目名/hello。如果一切顺利,你应该能看到“恭喜你!”的页面,并且控制台打印了那句输出。
这个简单的案例虽然只涉及一个URL映射,但它已经完整地走通了SpringMVC的核心流程:请求被DispatcherServlet拦截,HandlerMapping根据/hello找到sayHello方法,HandlerAdapter执行该方法,返回视图名"welcome",ViewResolver将其解析为/WEB-INF/views/welcome.jsp,最终渲染JSP页面返回给浏览器。通过这个动手过程,前面讲的那些抽象组件是不是变得具体多了?
5. 核心注解实战:让你的Controller更强大
会用@RequestMapping写个Hello World只是开始。SpringMVC提供了一整套强大的注解,能让你优雅地处理各种复杂的Web场景。这些注解就像是给你的Controller方法配备的“瑞士军刀”,用好了能极大提升开发效率和代码可读性。下面我挑几个最常用、也最容易出错的详细讲讲。
5.1 @RequestMapping:路由映射的基石
这是你最早接触也是使用最频繁的注解。它的作用就是建立请求URL与处理方法的映射关系。除了最基本的value(或path)指定URL,它还有几个非常实用的属性。
-
method:指定请求的HTTP方法。这是实现RESTful风格API的基础。例如:@RequestMapping(value = "/users", method = RequestMethod.GET) public List<User> getUsers() { ... } // 获取用户列表 @RequestMapping(value = "/users", method = RequestMethod.POST) public User createUser(@RequestBody User user) { ... } // 创建新用户为了更简洁,Spring还提供了衍生注解:
@GetMapping,@PostMapping,@PutMapping,@DeleteMapping,@PatchMapping。上面两个例子可以写成:@GetMapping("/users") @PostMapping("/users") -
params:要求请求必须包含某些特定参数。可以用来做简单的请求过滤。比如:@RequestMapping(value = "/user", params = "type=admin") public String getAdminUser() { ... }只有像
/user?type=admin这样的请求才会匹配到这个方法。 -
headers:要求请求必须包含某些特定的HTTP头。常用于区分客户端类型或版本。例如:@RequestMapping(value = "/api/data", headers = "X-API-Version=1") public String getDataV1() { ... }
一个常见的坑:@RequestMapping可以放在类级别和方法级别。类级别的注解定义了一个通用的路径前缀。这在进行模块化开发时非常有用。但要注意,路径拼接时不要重复/。例如:
@Controller
@RequestMapping("/order") // 类级别
public class OrderController {
@GetMapping("/list") // 方法级别,最终路径是 /order/list
public String list() { ... }
}
如果类级别是/order/,方法级别是/list,最终路径会是/order//list,虽然大多数容器能处理,但最好保持风格一致,避免多余的斜杠。
5.2 请求参数绑定:@RequestParam, @PathVariable, @RequestBody
如何方便地获取请求中的参数,是Web开发的基础。SpringMVC提供了多种方式。
-
@RequestParam:用于获取查询参数或表单数据。它最常用的场景是处理key=value这种简单参数。@GetMapping("/user") public String getUser(@RequestParam("id") Long userId, @RequestParam(value = "name", required = false, defaultValue = "Guest") String userName) { // 访问 /user?id=123&name=张三 // userId = 123, userName = "张三" // 如果请求中没有name参数,则userName使用默认值"Guest" }required默认为true,如果请求中没有这个参数会抛出异常。defaultValue提供了默认值,设置它时required会自动变为false。 -
@PathVariable:用于获取URL路径模板中的变量。这是RESTful风格URL的标配。@GetMapping("/users/{userId}/orders/{orderId}") public String getOrder(@PathVariable Long userId, @PathVariable("orderId") Long orderNo) { // 访问 /users/100/orders/2001 // userId = 100, orderNo = 2001 }注意URL模板中的变量名
{userId}需要与方法参数名userId一致,如果不一致,需要在@PathVariable注解中指定,如@PathVariable("orderId")。 -
@RequestBody:用于获取请求体(Request Body)的内容,通常用于接收JSON或XML格式的复杂数据。这是开发前后端分离的API时必用的注解。@PostMapping("/users") public ResponseEntity<User> createUser(@RequestBody User user) { // 前端发送一个JSON对象:{"username":"john", "email":"john@example.com"} // SpringMVC会使用HttpMessageConverter(如Jackson)自动将JSON反序列化为User对象 User savedUser = userService.save(user); return ResponseEntity.ok(savedUser); }这里有个关键点:要使用
@RequestBody,必须在配置中开启<mvc:annotation-driven/>或者使用@EnableWebMvc,这样才会注册能够处理JSON的HttpMessageConverter(比如MappingJackson2HttpMessageConverter)。
参数绑定的本质:SpringMVC的参数绑定是通过一系列HandlerMethodArgumentResolver(处理器方法参数解析器)来完成的。@RequestParam有对应的解析器,@PathVariable也有,@RequestBody也有。当HandlerAdapter执行方法前,会遍历这些解析器,看哪个能处理当前参数,然后由它负责从请求中提取数据并转换成方法所需的类型。理解这一点,对于后面处理复杂参数或自定义参数解析非常有帮助。
5.3 模型与视图:@ModelAttribute 与 @SessionAttribute
这两个注解用于在请求之间共享数据,它们操作的是不同的作用域。
-
@ModelAttribute:这个注解功能比较灵活,有两种主要用法。- 用在方法上:标注的方法会在本控制器每个处理方法执行前被调用。通常用于准备一些公用的模型数据。例如,一个编辑用户信息的页面,需要先加载用户数据。
@ModelAttribute public void populateModel(@RequestParam("id") Long id, Model model) { User user = userService.findById(id); model.addAttribute("user", user); // 将user对象放入模型 } @GetMapping("/edit") public String editForm() { // 进入这个方法时,model里已经有一个名为"user"的属性了 return "user/editForm"; } - 用在方法参数上:将模型中的属性绑定到方法参数上,或者将请求参数绑定到命令对象上并同时放入模型。
这种用法在表单回显和更新时非常方便。@PostMapping("/update") public String updateUser(@ModelAttribute("user") User user) { // 这里的user对象,会从模型属性中获取(如果已有), // 同时也会用请求参数来更新这个对象的字段。 userService.update(user); return "redirect:/user/list"; }
- 用在方法上:标注的方法会在本控制器每个处理方法执行前被调用。通常用于准备一些公用的模型数据。例如,一个编辑用户信息的页面,需要先加载用户数据。
-
@SessionAttribute:用于在多个请求之间共享数据,数据存储在HttpSession中。这个注解是声明在类级别的,用来指定哪些模型属性需要存入Session。@Controller @SessionAttributes("shoppingCart") // 声明名为"shoppingCart"的模型属性要存入session public class OrderController { @GetMapping("/cart/add") public String addToCart(@RequestParam Long productId, Model model) { ShoppingCart cart = ... // 获取或创建购物车 cart.addItem(productId); model.addAttribute("shoppingCart", cart); // 放入model,同时会自动存入session return "cart/view"; } // 在后续的请求中,可以直接从session中获取 @GetMapping("/cart/checkout") public String checkout(SessionStatus status, @SessionAttribute("shoppingCart") ShoppingCart cart) { // 处理结账逻辑... status.setComplete(); // 清除通过@SessionAttributes存入session的数据 return "order/confirm"; } }需要注意的是,
@SessionAttributes声明的属性只在当前控制器的处理流程中有效,并且需要通过SessionStatus.setComplete()来清理,或者等待Session过期。它和直接使用HttpSession的setAttribute方法有所不同,后者作用域更广,生命周期更长。
掌握这些注解,你的Controller就能游刃有余地处理各种输入输出、数据共享和视图跳转的需求了。在实际项目中,灵活组合使用它们,是写出清晰、健壮Web层代码的关键。
436



被折叠的 条评论
为什么被折叠?



