第一章:SpringMVC环境搭建与基础配置
1.1 Maven依赖配置
在Maven项目中,SpringMVC的依赖配置是开发的第一步。根据Spring官方推荐,以下是SpringMVC 5.3.x版本的Maven依赖配置:
<dependencies>
<!-- Spring MVC核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.25</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmv</artifactId>
<version>5.3.25</version>
</dependency>
<!-- Servlet API依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- JSP页面需要 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-jsp</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
<!-- 数据库连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- 其他常用依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.25</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
</dependencies>
注意:提供的依赖
表示这些依赖由Servlet容器(如Tomcat)提供,不需要打包到最终的WAR文件中。
1.2 web.xml配置
web.xml是Java Web应用的配置中心,需要配置SpringMVC的核心组件——DispatcherServlet :
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- 配置SpringMVC的前端控制器 -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 设置配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc-servlet.xml</param-value>
</init-param>
<!-- 服务器启动时加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置DispatcherServlet的映射路径 -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置ContextLoaderListener加载根应用上下文 -->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support安娜imationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
< listener >
< listener-class > org.springframework.web.context.ContextLoaderListener </ listener-class >
</ listener >
</web-app>
关键配置说明:
DispatcherServlet
是SpringMVC的前端控制器,负责接收所有HTTP请求并分发给对应的处理器contextConfigLocation
参数指定了SpringMVC的配置文件位置<url-pattern>/</url-pattern>
表示拦截所有请求ContextLoaderListener
用于加载根应用上下文(applicationContext.xml),通常包含业务逻辑和数据访问层的配置
1.3 SpringMVC核心配置文件
在/WEB-INF/
目录下创建spring-mvc-servlet.xml
配置文件,配置SpringMVC的核心组件:
<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">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.example.controller"/>
<!-- 开启注解驱动 -->
<mvc:annotation-driven/>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 处理静态资源 -->
<bean class="org.springframework.web.servlet.view资源处理器" id="resourceProcessor">
<property name="locations">
<list>
<value>/static/</value>
</list>
</property>
<property name="mappings">
<list>
<value>/js/**</value>
<value>/css/**</value>
<value>/images/**</value>
</list>
</property>
</bean>
<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="1048576"/>
</bean>
</beans>
关键配置说明:
component-scan
开启组件扫描,自动发现并注册带有@Controller
等注解的类annotation-driven
开启注解驱动,支持@RequestMapping
等注解InternalResourceViewResolver
配置JSP视图解析器,指定视图页面的路径前缀和后缀ResourceHandler
处理静态资源请求,避免DispatcherServlet拦截静态资源CommonsMultipartResolver
配置文件上传解析器,用于处理文件上传请求
1.4 第一个SpringMVC示例(HelloWorld)
创建一个简单的HelloWorld控制器,演示SpringMVC的基本工作流程:
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.util.Date;
// 标记为控制器
@Controller
public class HelloController {
// 处理GET请求,映射到/hello路径
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String helloGET() {
// 直接返回视图名称,视图解析器会自动解析为/WEB-INF/views/hello.jsp
return "hello";
}
// 返回JSON数据
@RequestMapping(value = "/hello/json", method = RequestMethod.GET)
@ResponseBody
public String helloJSON() {
return "{\"message\":\"Hello Spring MVC!\"}";
}
// 返回ModelAndView,可以携带模型数据
@RequestMapping("/hello/model")
public ModelAndView helloModel() {
// 创建ModelAndView对象
ModelAndView mav = new ModelAndView();
// 添加模型数据
mav.addObject("message", "Hello Spring MVC!");
mav.addObject("time", new Date());
// 设置视图名称
mav.setViewName("hello");
return mav;
}
}
在/WEB-INF/views/
目录下创建hello.jsp
页面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SpringMVC Hello World</title>
</head>
<body>
<h1><%= request.getAttribute("message") %></h1>
<p>Current Time: <%= request.getAttribute("time") %></p>
</body>
</html>
运行测试: 启动Tomcat服务器后,在浏览器中访问以下路径:
/hello
:显示JSP页面/hello/json
:返回JSON数据/hello/model
:显示携带模型数据的JSP页面
第二章:核心注解与请求映射
2.1 @Controller与@RequestMapping
@Controller注解是SpringMVC的核心注解,用于标记一个类为控制器:
@Controller
public class UserController {
// ...
}
@RequestMapping注解用于映射HTTP请求到控制器方法:
@Controller
public class UserController {
// 映射到/user路径的GET请求
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUser() {
return "user";
}
// 映射到/user的POST请求
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String createUser() {
return "createUser";
}
}
2.2 @GetMapping、@PostMapping等请求方法注解
SpringMVC 4.3+版本引入了更简洁的请求方法注解:
@RestController
public class UserController {
// 处理GET请求,等价于@RequestMapping(method = GET)
@GetMapping("/user")
public String getUser() {
return "GET User";
}
// 处理POST请求,等价于@RequestMapping(method = POST)
@PostMapping("/user")
public String createUser() {
return "POST User";
}
// 处理PUT请求
@PutMapping("/user/{id}")
public String updateUser(@PathVariable Long id) {
return "PUT User " + id;
}
// 处理DELETE请求
@DeleteMapping("/user/{id}")
public String.deleteUser(@PathVariable Long id) {
return "DELETE User " + id;
}
}
注解对比:
注解 | 对应的HTTP方法 | 使用场景 |
---|---|---|
@GetMapping | GET | 获取资源信息 |
@PostMapping | POST | 创建新资源 |
@PutMapping | PUT | 更新资源 |
@DeleteMapping | DELETE | 删除资源 |
@PatchMapping | PATCH | 部分更新资源 |
@RequestMapping | 任意方法 | 更灵活的请求映射 |
2.3 路径变量与请求参数绑定(@PathVariable、@RequestParam)
@PathVariable用于绑定URL中的路径变量:
@RestController
public class UserController {
// 映射到/user/123路径,将123绑定到id参数
@GetMapping("/user/{id}")
public String getUserById(@PathVariable Long id) {
return "User ID: " + id;
}
// 多个路径变量
@GetMapping("/products/{category}/{id}")
public String Product(@PathVariable String category, @PathVariable Long id) {
return "Category: " + category + ", ID: " + id;
}
// 名称匹配(参数名与路径变量名相同)
@GetMapping("/user/{userId}")
public String getUser(@PathVariable Long userId) {
return "User ID: " + userId;
}
// 显式指定名称
@GetMapping("/user/{userId}")
public String getUser(@PathVariable("userId") Long id) {
return "User ID: " + id;
}
}
@RequestParam用于绑定请求参数(查询参数或请求体参数):
@RestController
public class UserController {
// 获取查询参数,如?name=Alice
@GetMapping("/user/param")
public String getUserByParam(@RequestParam String name) {
return "Hello, " + name + "!";
}
// 指定参数名
@GetMapping("/user/named")
public String getUserByName(@RequestParam("name") String-username) {
return "Hello, " +-username + "!";
}
// 可选参数,默认值
@GetMapping("/user/optional")
public String getUserOptional(@RequestParam(required = false, defaultValue = "Guest") String name) {
return "Hello, " + name + "!";
}
// 处理多个参数
@GetMapping("/user/multiple")
public String getUserMultiple(
@RequestParam String name,
@RequestParam Integer age) {
return "Name: " + name + ", Age: " + age;
}
}
前端测试:
/user/param?name=Alice
:返回"Hello, Alice!"/user/multiple?name=Bob&age=25
:返回"Name: Bob, Age: 25"
2.4 请求头与Cookie绑定(@RequestHeader、@CookieValue)
@RequestHeader用于绑定HTTP请求头参数:
@RestController
public class HeaderController {
// 获取User-Agent请求头
@GetMapping("/user/agent")
public String getUserAgent(@RequestHeader("User-Agent") String agent) {
return "User-Agent: " + agent;
}
// 指定请求头参数名
@GetMapping("/userCustom")
public String getUserCustom(
@RequestHeader("X-Custom-Header") String customHeader) {
return "Custom Header: " + customHeader;
}
// 可选请求头,默认值
@GetMapping("/user/optional-header")
public String getUserOptionalHeader(
@RequestHeader(required = false, defaultValue = "default-value") String header) {
return "Header: " + header;
}
}
@CookieValue用于绑定Cookie参数:
@RestController
public class CookieController {
// 获取JSESSIONID Cookie
@GetMapping("/show-session-id")
public String showSessionId(@CookieValue("JSESSIONID") String-sessionId) {
return "Session ID from cookie: " +-sessionId;
}
// 获取自定义Cookie,可选
@GetMapping("/show-custom-cookie")
public String showCustomCookie(
@CookieValue(required = false, defaultValue = "not-set") String userCookie) {
return "User Cookie: " + userCookie;
}
}
前端测试:
- 访问
/show-session-id
:返回当前会话的JSESSIONID - 访问
/show-custom-cookie
:返回默认值"not-set"(若未设置该Cookie)
第三章:数据绑定与参数传递
3.1 基本类型参数传递
直接参数绑定:当请求参数名称与控制器方法参数名称相同时,SpringMVC可以自动绑定 :
@RestController
public class BasicParamController {
// GET请求:/basic?name=Alice&age=25
@GetMapping("/basic")
public String basicParams(String name, int age) {
return "Name: " + name + ", Age: " + age;
}
// POST请求:表单参数name=Alice&age=25
@PostMapping("/basic/post")
public String basicPostParams(String name, int age) {
return "POST Name: " + name + ", POST Age: " + age;
}
// 使用@RequestParam显式指定参数名
@GetMapping("/basic/named")
public String namedParams(
@RequestParam("name") String-username,
@RequestParam("age") int userAge) {
return "Username: " +-username + ", User Age: " + userAge;
}
}
3.2 对象参数绑定(POJO)
POJO参数绑定:当请求参数名称与POJO的属性名称相同时,SpringMVC可以自动将参数绑定到对象的属性上:
// 定义POJO类
@Data
public class User {
private String name;
private int age;
private String email;
// getter和setter方法
}
@RestController
public class PojoController {
// GET请求:/pojo?name=Alice&age=25&email=alice@example.com
@GetMapping("/pojo")
public String pojoGet(User user) {
return "Name: " + user.getName() + ", Age: " + user.getAge();
}
// POST请求:表单参数name=Alice&age=25&email=alice@example.com
@PostMapping("/pojo/post")
public String pojoPost(User user) {
return "POST Name: " + user.getName() + ", POST Age: " + user.getAge();
}
// 处理嵌套对象
@Data
public class Address {
private String city;
private String street;
}
@Data
public class UserWithAddress {
private User user;
private Address address;
}
// GET请求:/nested?user.name=Alice&user.age=25&address.city=Beijing
@GetMapping("/nested")
public String nestedPojo(UserWithAddress userWithAddress) {
return "Name: " + userWithAddress.getUser().getName() +
", City: " + userWithAddress().getCity();
}
}
前端测试:
/pojo?name=Alice&age=25
:返回"Name: Alice, Age: 25"/嵌套?user.name=Bob&user.age=30&address.city=Shanghai
:返回"Name: Bob, City: Shanghai"
3.3 数组与集合参数传递
数组参数绑定:通过重复参数名传递数组:
@RestController
public class ArrayController {
// GET请求:/array?ids=1&ids=2&ids=3
@GetMapping("/array")
public String arrayParams(@RequestParam int[] ids) {
return "IDs: " + Arrays.toString(ids);
}
// 使用Spring的数组格式(如:ids=1,2,3)
@GetMapping("/array/comma-separated")
public String commaSeparatedParams(@RequestParam String ids) {
return "IDs: " + ids;
}
}
集合参数绑定:使用List
或Set
接收参数:
@RestController
public class ListController {
// GET请求:/list?names=John&names=Jane&names=Jim
@GetMapping("/list")
public String listParams(@RequestParam List<String> names) {
return "Names: " + names;
}
// 使用Spring的集合格式(如:names=John,Jane,Jim)
@GetMapping("/list/comma-separated")
public String commaSeparatedList(@RequestParam String names) {
return "Names: " + names;
}
// 使用JSON传递集合
@PostMapping("/list/json")
public String jsonList(@RequestBody List<String> names) {
return "JSON Names: " + names;
}
}
前端测试:
/array?ids=1&ids=2&ids=3
:返回"IDs: [1, 2, 3]"/list?names=John&names=Jane
:返回"Names: [John, Jane]"- 使用Postman发送POST请求到
/list/json
,请求体为["Alice", "Bob"]
,返回"JSON Names: [Alice, Bob]"
3.4 文件上传参数绑定(@RequestParam MultipartFile)
文件上传配置:在web.xml中添加 CommonsMultipartResolver
:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="10485760"/河边>
<property name="maxInMemorySize" value="1048576"/>
</bean>
文件上传控制器 :
@RestController
public class FileUploadController {
// 单文件上传
@PostMapping("/upload")
public String uploadSingleFile(
@RequestParam("file") MultipartFile file) {
try {
// 保存文件到服务器
String path = "/tmp/uploads/" + file.getOriginalFilename();
file transferTo(new File(path));
return "File上传成功: " + file.getOriginalFilename();
} catch (Exception e) {
return "File上传失败: " + e.getMessage();
}
}
// 多文件上传
@PostMapping("/uploadMultiple")
public String uploadMultipleFiles(
@RequestParam("files") List<MultipartFile> files) {
try {
for (MultipartFile file : files) {
String path = "/tmp/uploads/" + file.getOriginalFilename();
file transferTo(new File(path));
}
return "多文件上传成功: " + files.size() + " files";
} catch (Exception e) {
return "多文件上传失败: " + e.getMessage();
}
}
// 带其他参数的文件上传
@PostMapping("/upload/with-params")
public String uploadWithParams(
@RequestParam("file") MultipartFile file,
@RequestParam("username") String username) {
try {
// 保存文件到服务器
String path = "/tmp/uploads/" + username + "-" + file.getOriginalFilename();
file transferTo(new File(path));
return "File上传成功: " + file.getOriginalFilename();
} catch (Exception e) {
return "File上传失败: " + e.getMessage();
}
}
}
HTML表单 :
<!-- 单文件上传 -->
<form action="/upload单车" method="POST"enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
<!-- 多文件上传 -->
<form action="/upload多" method="POST"enctype="multipart/form-data">
<input type="file" name="files" multiple>
<input type="submit" value="上传">
</form>
<!-- 带其他参数的文件上传 -->
<form action="/upload/with-params" method="POST"enctype="multipart/form-data">
<input type="text" name="username" placeholder="用户名">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
注意事项:
- 文件上传需要设置
enctype="multipart/form-data"
- 需要添加
commons-fileupload
和commons-io
依赖 :
<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.5</version>
</dependency>
第四章:视图解析与模板引擎集成
4.1 ViewResolver配置与视图返回
视图解析器负责将控制器返回的逻辑视图名解析为物理视图 :
// 配置JSP视图解析器
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/"); // 视图前缀
resolver.setSuffix(".jsp"); // 视图后缀
return resolver;
}
// 配置Thymeleaf视图解析器
@Bean
public ViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setOrder(1); // 优先级高于JSP
return resolver;
}
// 配置Thymeleaf模板引擎
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
// 配置模板解析器
@Bean
public TemplateResolver templateResolver() {
TemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/WEB-INF/templates/"); // 模板前缀
resolver.setSuffix(".html"); // 模板后缀
resolver.setTemplateMode("HTML5"); // 模板类型
resolver.setCharacterEncoding("UTF-8"); // 编码
return resolver;
}
4.2 JSP模板集成与Thymeleaf模板配置
JSP视图示例:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= request.getAttribute("title") %></title>
</head>
<body>
<h1><%= request.getAttribute("message") %></h1>
<p>Time: <%= request.getAttribute("time") %></p>
<div>
<ul>
<%
List<User> userList = (List<User>) request.getAttribute("userList");
for (User user : userList) {
%>
<li><%= user.getName() %></li>
<%
}
%>
</ul>
</div>
</body>
</html>
Thymeleaf视图示例:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${title}">默认标题</title>
</head>
<body>
<h1 th:text="${message}">默认消息</h1>
<p>Time: <span th:text="${#dates.format(time, 'yyyy-MM-dd HH:mm:ss')}">2025-08-04 12:00:00</span></p>
<div th:each="user : ${userList}">
<p th:text="${user.name}">用户姓名</p>
</div>
<form th:action="@{/user/create}" method="post">
<input type="text" name="name" th:value="${user.name}" required>
<input type="submit" value="提交">
</form>
</body>
</html>
Thymeleaf与JSP路径配置对比:
特性 | JSP | Thymeleaf |
---|---|---|
默认路径 | /WEB-INF/views/ |
/WEB-INF/templates/ 或 /templates/ |
文件扩展名 | .jsp |
.html |
编码方式 | 需要手动处理 | 自动处理 |
前端兼容性 | 需要Servlet容器支持 | 无需Servlet容器支持,可直接在浏览器中预览 |
安全性 | 需要手动处理XSS等安全问题 | 默认启用上下文敏感的转义功能,防止XSS |
4.3 模型数据传递(Model、ModelAndView)
使用Model传递数据:
@RestController
public class ModelController {
@GetMapping("/model")
public String modelExample(Model model) {
model.addAttribute("message", "Hello Model!");
model.addAttribute("time", new Date());
return "modelExample";
}
@GetMapping("/model-and-view")
public ModelAndView modelAndViewExample() {
ModelAndView mav = new ModelAndView();
mav.addObject("message", "HelloModelAndView!");
mav.addObject("time", new Date());
mav.setViewName("modelAndViewExample");
return mav;
}
}
使用Map传递数据:
@RestController
public class MapController {
@GetMapping("/map")
public String mapExample(@RequestParam String name, Map<String, Object> map) {
map.put("greeting", "Hello, " + name + "!");
map.put("time", new Date());
return "mapExample";
}
}
使用ModelAndView返回JSON数据 :
@RestController
public class JsonController {
@GetMapping("/json")
public String jsonExample() {
return "{\"message\":\"Hello JSON!\"}";
}
@GetMapping("/json/model")
public String jsonModelExample(Model model) {
model.addAttribute("message", "Hello JSON Model!");
return "jsonModelExample";
}
@GetMapping("/json/model-and-view")
public ModelAndView jsonModelAndViewExample() {
ModelAndView mav = newModelAndView();
mav.addObject("message", "Hello JSONModelAndView!");
mav.setViewName("jsonModelAndViewExample");
return mav;
}
}
第五章:表单处理与文件上传
5.1 表单提交与数据绑定
表单提交处理:使用@RequestParam
或POJO接收表单数据:
@RestController
public class FormController {
// 使用请求参数接收表单数据
@PostMapping("/form/params")
public String formParams(
@RequestParam("name") String name,
@RequestParam("age") int age,
@RequestParam("email") String email) {
return "提交成功: " + name + ", " + age + ", " + email;
}
// 使用POJO接收表单数据
@PostMapping("/form/pojo")
public String formPojo(@RequestBody User user) {
return "提交成功: " + user.getName() + ", " + user.getAge() + ", " + user plemail();
}
}
Thymeleaf表单绑定:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${'创建用户'}">创建用户</title>
</head>
<body>
<form th:action="@{/user/create}" method="post" th:object="${user}">
<div>
<label>姓名</label>
<input type="text" th:field="*{name}" required>
</div>
<div>
<label>年龄</label>
<input type="number" th:field="*{age}" required>
</div>
<div>
<label>邮箱</label>
<input type="email" th:field="*{email}" required>
</div>
<input type="submit" value="创建用户">
</form>
</body>
</html>
表单数据绑定说明:
th:object="${user}"
指定表单绑定的对象th:field="*{property}"
自动绑定对象的属性到表单字段- 支持表单验证和错误提示
5.2 文件上传功能实现(CommonsMultipartResolver)
文件上传控制器 :
@RestController
public class FileUploadController {
// 单文件上传
@PostMapping("/uploadSingle")
public String uploadSingleFile(
@RequestParam("file") MultipartFile file) {
try {
// 保存文件到服务器
String path = "/tmp/uploads/" + file.getOriginalFilename();
file transferTo(new File(path));
return "File上传成功: " + file.getOriginalFilename();
} catch (Exception e) {
return "File上传失败: " + e.getMessage();
}
}
// 多文件上传
@PostMapping("/uploadMultiple")
public String uploadMultipleFiles(
@RequestParam("files") List<MultipartFile> files) {
try {
for (MultipartFile file : files) {
String path = "/tmp/uploads/" + file.getOriginalFilename();
file transferTo(new File(path));
}
return "多文件上传成功: " + files.size() + " files";
} catch (Exception e) {
return "多文件上传失败: " + e.getMessage();
}
}
// 带其他参数的文件上传
@PostMapping("/upload/with-params")
public String uploadWithParams(
@RequestParam("file") MultipartFile file,
@RequestParam("username") String username) {
try {
// 保存文件到服务器
String path = "/tmp/uploads/" + username + "-" + file.getOriginalFilename();
file transferTo(new File(path));
return "File上传成功: " + file.getOriginalFilename();
} catch (Exception e) {
return "File上传失败: " + e.getMessage();
}
}
}
文件上传注意事项:
- 需要添加
commons-fileupload
和commons-io
依赖 - 需要设置文件上传的最大大小和内存限制
- 需要处理文件名重复和非法字符问题
- 建议使用相对路径或配置文件指定上传路径
5.3 表单验证与错误处理
表单验证:使用@Valid
和JSR-303验证注解 :
// 定义带验证的POJO
@Data
public class UserForm {
@NotNull(message = "姓名不能为空")
@Size(min = 2, max = 50, message = "姓名长度必须在2-50之间")
private String name;
@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄不能为负数")
@Max(value = 150, message = "年龄不能超过150")
private Integer age;
@NotNull(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
}
// 控制器方法
@RestController
public class ValidationController {
@PostMapping("/validate")
public String validateUser(@Valid @RequestBody UserForm userForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 获取所有错误信息
List<String> errors = bindingResult AllErrors().stream()
.map田ObjectError::getDefaultMessage)
.collect(Collectors.toList());
return "{\"status\":\"error\", \"messages\": " + errors + "}";
}
// 验证通过,处理业务逻辑
return "{\"status\":\"success\"}";
}
}
前端验证:使用JavaScript进行前端验证 :
<script>
function validateForm() {
const name = document.getElementById("name").value;
const age = document.getElementById("age").value;
const email = document.getElementById("email").value;
if (name.length === 0) {
alert("姓名不能为空");
return false;
}
if (age < 0 || age > 150) {
alert("年龄必须在0-150之间");
return false;
}
if (!验证邮箱格式(email)) {
alert("邮箱格式不正确");
return false;
}
return true;
}
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
</script>
第六章:RESTful API开发
6.1 RESTful URL设计(@PathVariable)
RESTful API设计原则:资源导向,使用HTTP方法表示操作 :
@RestController
@RequestMapping("/api/v1/employees")
public class EmployeeRestController {
// 查询所有员工
@GetMapping
public List<Employee> getEmployees() {
// 从数据库或服务获取员工列表
return employeeService的所有员工();
}
// 根据ID查询员工
@GetMapping("/employee/{id}")
public Employee getEmployeeById(@PathVariable Long id) {
Employee employee = employeeService.getEmployeeById(id);
if (employee == null) {
throw new EmployeeNotFoundException("员工ID不存在: " + id);
}
return employee;
}
// 创建员工
@PostMapping
public Employee createEmployee(@RequestBody Employee employee) {
// 验证员工信息
validateEmployee(employee);
// 调用服务层创建员工
return employeeService.createEmployee(employee);
}
// 更新员工
@PutMapping("/employee/{id}")
public Employee updateEmployee(
@PathVariable Long id,
@RequestBody Employee employee) {
// 验证员工信息
validateEmployee(employee);
// 调用服务层更新员工
Employee updatedEmployee = employeeService.updateEmployee(id, employee);
if (updatedEmployee == null) {
throw new EmployeeNotFoundException("员工ID不存在: " + id);
}
return updatedEmployee;
}
// 删除员工
@DeleteMapping("/employee/{id}")
public void deleteEmployee(@PathVariable Long id) {
if (!employeeService.deleteEmployee(id)) {
throw new EmployeeNotFoundException("员工ID不存在: " + id);
}
}
// 私有验证方法
private void validateEmployee(Employee employee) {
// 验证员工姓名
if (employee.getName() == null || employee.getName().trim().length() == 0) {
throw new IllegalArgumentException("员工姓名不能为空");
}
// 验证员工邮箱
if (employee plemail() != null && !验证邮箱格式(employee plemail())) {
throw new IllegalArgumentException("邮箱格式不正确");
}
}
// 验证邮箱格式
private boolean validateEmail(String email) {
return email != null && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
RESTful API设计最佳实践:
- 使用名词复数表示资源集合,如
/employees
- 使用HTTP方法表示操作,GET表示获取,POST表示创建,PUT表示更新,DELETE表示删除
- 使用路径变量表示资源标识,如
/employees/{id)
- 使用查询参数进行过滤和分页,如
/employees?name=John&age=25
- 使用JSON格式进行数据交换
6.2 JSON数据处理(@RequestBody、@ResponseBody)
使用@RequestBody处理JSON请求 :
@RestController
public class JsonController {
// 接收JSON请求体并转换为Java对象
@PostMapping("/json")
public String jsonPost(@RequestBody User user) {
// 处理用户信息
return "JSON数据接收成功: " + user plemail();
}
// 返回JSON响应
@GetMapping("/json")
public User jsonGet() {
User user = new User();
user.plemail("alice@example.com");
user.name("Alice");
user.age(25);
return user;
}
// 处理嵌套JSON对象
@PostMapping("/nestedjson")
public String nestedJsonPost(@RequestBody UserWithAddress userWithAddress) {
// 处理嵌套对象
return "嵌套JSON接收成功: " + userWithAddress.user().name();
}
// 返回嵌套JSON对象
@GetMapping("/nestedjson")
public UserWithAddress nestedJsonGet() {
User user = new User();
user.name("Bob");
user.age(30);
Address address = new Address();
address.city("北京");
address.street("中关村大街");
UserWithAddress userWithAddress = new UserWithAddress();
userWithAddress.user(user);
userWithAddress.address(address);
return userWithAddress;
}
// 处理JSON数组
@PostMapping("/jsonArrayPost")
public String jsonArrayPost(@RequestBody List<User> userList) {
// 处理用户列表
return "JSON数组接收成功: " + userList.size() + " users";
}
// 返回JSON数组
@GetMapping("/jsonArrayGet")
public List<User> jsonArrayGet() {
// 从数据库获取用户列表
return Arrays.asList(
new User("Alice", 25, "alice@example.com"),
new User("Bob", 30, "bob@example.com")
);
}
}
JSON数据处理注意事项:
- 确保添加了
jackson-databind
依赖 - 复杂对象需要提供无参构造方法和getter/setter
- 可以使用
@JsonInclude
和@JsonProperty
注解控制JSON序列化/反序列化 - 建议使用
@RestController
代替@Controller
和@ResponseBody
组合
6.3 使用@RestController开发API
使用@RestController简化API开发 :
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
// 查询所有用户
@GetMapping
public ResponseEntity<List<User>> allUsers() {
List<User> userList = userService.allUsers();
return ResponseEntity.ok(userList);
}
// 根据ID查询用户
@GetMapping("/user/{id}")
public ResponseEntity<User> userById(@PathVariable Long id) {
User user = userService.userById(id);
if (user == null) {
return ResponseEntity notFound().build();
}
return ResponseEntity.ok(user);
}
// 创建用户
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
// 验证用户信息
validateUser(user);
// 创建用户
User createdUser = userService.createUser(user);
return ResponseEntity created().build();
}
// 更新用户
@PutMapping("/user/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@RequestBody User user) {
// 验证用户信息
validateUser(user);
// 更新用户
User updatedUser = userService.updateUser(id, user);
if (updatedUser == null) {
return ResponseEntity notFound().build();
}
return ResponseEntity.ok(updatedUser);
}
// 删除用户
@DeleteMapping("/user/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
boolean deleted = userService.delete用户(id);
if (!deleted) {
return ResponseEntity notFound().build();
}
return ResponseEntity noContent().build();
}
// 私有验证方法
private void validateUser(User user) {
// 验证用户名
if (user.name() == null || user.name().trim().length() == 0) {
throw new IllegalArgumentException("用户名不能为空");
}
// 验证邮箱
if (user.email() != null && !validateEmail(user.email())) {
throw new IllegalArgumentException("邮箱格式不正确");
}
}
// 验证邮箱格式
private boolean validateEmail(String email) {
return email != null && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
使用@RestController的优势:
- 自动将返回值转换为JSON格式,无需添加
@ResponseBody
注解 - 更适合开发RESTful API,返回数据而非视图
- 可以与
@ResponseStatus
、@ExceptionHandler
等注解结合使用
第七章:异常处理与拦截器配置
7.1 全局异常处理器(@ControllerAdvice)
全局异常处理器:使用@ControllerAdvice
处理整个应用的异常 :
@ControllerAdvice
public class GlobalExceptionHandler {
// 处理Employee Not Found异常
@ExceptionHandler(EmployeeNotFoundException.class)
@ResponseBody
public ResponseEntity<Error> handleEmployeeNotFound
(EmployeeNotFoundException ex) {
Error error = new Error();
error.code(404);
error.message(ex.getMessage());
error.timestamp(new Date());
return ResponseEntity notFound().body(error);
}
// 处理非法参数异常
@ExceptionHandler(legalStateException.class)
@ResponseBody
public ResponseEntity<Error> handleIllegalState
(IllegalArgumentException ex) {
Error error = new Error();
error.code(400);
error.message(ex.getMessage());
error.timestamp(new Date());
return ResponseEntity badRequest().body(error);
}
// 处理其他异常
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Error> handleGeneralError
(Exception ex) {
Error error = new Error();
error.code(500);
error.message("服务器内部错误");
error.timestamp(new Date());
error.details(ex.getMessage());
return ResponseEntity internal ServerError().body(error);
}
// 自定义错误对象
@Data
public class Error {
private int code;
private String message;
private Date timestamp;
private String details;
}
}
异常处理最佳实践:
- 使用
@ControllerAdvice
集中处理异常 - 不同异常类型返回不同的HTTP状态码
- 错误信息以JSON格式返回,包含状态码、消息、时间戳等信息
- 避免暴露敏感信息,如数据库错误、堆栈跟踪等
- 可以添加日志记录,记录异常详细信息
7.2 自定义拦截器(HandlerInterceptor)
自定义拦截器:实现HandlerInterceptor
接口创建拦截器 :
public class LoginInterceptor implements HandlerInterceptor {
// 在控制器方法执行前调用
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 获取当前请求路径
String path = request.getRequestURI();
// 忽略不需要登录的路径
if (path.equals("/login") || path.equals("/css/**") || path.equals("/js/**")) {
return true;
}
// 检查用户是否登录
User user = (User) request.getSession().getAttribute("LOGined_USER");
if (user == null) {
// 用户未登录,重定向到登录页面
response.sendRedirect("/login");
return false;
}
// 用户已登录,继续执行
return true;
}
// 在控制器方法执行后调用,视图渲染前
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView mav) throws Exception {
// 可以在此添加额外的处理
}
// 在整个请求处理完成后调用
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 可以在此添加清理工作
}
}
7.3 拦截器注册与配置
在SpringMVC配置文件中注册拦截器 :
<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">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.example"/>
<!-- 开启注解驱动 -->
<mvc:annotation-driven />
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 注册拦截器 -->
<bean id="loginInterceptor" class="com.example.interceptor.LoginInterceptor"/>
<!-- 配置拦截器映射 -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.config annimation WebMvcConfigurationSupport">
<property name="mapping" value="/**"/>
<property name="excludeMapping" value="/login, /css/**, /js/**"/>
</bean>
</mvc:interceptors>
</beans>
拦截器配置说明:
loginInterceptor
定义了拦截器的实现类mapping
指定需要拦截的路径模式excludeMapping
指定不需要拦截的路径- 拦截器按注册顺序执行preHandle方法,按相反顺序执行postHandle和afterCompletion方法