本教程以實戰項目為主線,系統講解 Spring MVC 核心配置、常用注解、參數綁定、數據校驗、文件上傳下載、RESTful API、異常處理、攔截器、國際化、與 Spring IOC 整合等企業級開發全棧技能。
核心 Jar:spring-webmvc、spring-web、spring-context、spring-beans、spring-core、commons-logging、Servlet API、Jackson(JSON 支持)。
<!-- DispatcherServlet:前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern> <!-- 攔截所有請求 -->
</servlet-mapping>
<!-- Spring IOC 容器啟動(可選,整合時必需) -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:context="https://www.springframework.org/schema/context"
xmlns:mvc="https://www.springframework.org/schema/mvc"
xsi:schemaLocation="...">
<!-- 掃描 Controller 包 -->
<context:component-scan base-package="com.example.controller" />
<!-- 靜態資源默認處理 -->
<mvc:default-servlet-handler />
<!-- 註解驅動:參數綁定、格式化、驗證、MessageConverter 自動裝配 -->
<mvc:annotation-driven />
<!-- 視圖解析器:JSP 前綴/後綴 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
@Controller
@RequestMapping("/mvc")
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello"; // 視圖名 -> /WEB-INF/views/hello.jsp
}
}
src/main/webapp/WEB-INF/views/hello.jsp:<body>Hello World</body>
啟動服務器,訪問 https://localhost:8080/項目名/mvc/hello,顯示 "Hello World"。
| 注解 | 作用 | 關鍵屬性 |
|---|---|---|
@Controller | 標識控制器 Bean,掃描註冊到 Spring 容器 | - |
@RequestMapping | 映射請求 URL、方法、參數、Header | value, method, params, headers, consumes, produces |
@RequestBody | 讀取請求體,通過 HttpMessageConverter 反序列化為對象 | required |
@ResponseBody | 將返回對象序列化寫入響應體 | - |
@ModelAttribute | 方法級:預執行綁定模型;參數級:從模型獲取並綁定請求參數 | value (模型鍵名) |
@RequestParam | 綁定請求參數到方法參數 | value, required, defaultValue |
@PathVariable | 綁定 URL 佔位符到方法參數 | value, required |
@ExceptionHandler | 標識異常處理方法 | value (異常類數組) |
@ControllerAdvice | 全局異常處理、數據綁定、模型增強 | basePackages, annotations |
@InitBinder | 註冊自定義屬性編輯器(如日期格式化) | value (命令對象名) |
@Valid / @Validated | 觸發 JSR-303 Bean Validation 驗證 | - |
@SessionAttributes | 將模型屬性存入 Session 共享 | value (屬性名數組) |
// 請求參數 name=jayjay&age=20 自動綁定
@RequestMapping("/person")
public String toPerson(String name, double age) { ... }
// POJO 自動裝箱:請求參數自動填充屬性
@RequestMapping("/person1")
public String toPerson(Person p) { ... } // Person 需有無參構造器、setter
@InitBinder
public void initBinder(WebDataBinder binder) {
// 註冊日期編輯器:yyyy-MM-dd 格式
binder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
@RequestMapping("/date")
public String date(Date date) { ... } // 請求參數 date=2023-10-01 自動轉換
// 方式1:Map/Model/ModelMap
@RequestMapping("/show")
public String showPerson(Map<String, Object> map) {
map.put("p", new Person("jayjay", 20));
return "show"; // JSP 中 ${p.name} 獲取
}
// 方式2:ModelAndView
@RequestMapping("/show2")
public ModelAndView showPerson2() {
ModelAndView mv = new ModelAndView("show");
mv.addObject("p", new Person("jayjay", 20));
return mv;
}
@Controller
@RequestMapping("/json")
public class JsonController {
@ResponseBody
@RequestMapping("/user")
public User getUser() {
User u = new User();
u.setId(1);
u.setName("jayjay");
u.setBirth(new Date());
return u; // 自動序列化為 JSON
}
}
需導入 Jackson 依賴:jackson-databind、jackson-core、jackson-annotations。<mvc:annotation-driven> 會自動裝配 MappingJackson2HttpMessageConverter。
$(function() {
$("#btn").click(function() {
$.post("mvc/getPerson", {name: $("#name").val()}, function(data) {
alert(data); // 接收純文本或 JSON
});
});
});
@RequestMapping("/redirect")
public String redirect() {
return "redirect:hello"; // 重定向到 /mvc/hello
// 或 redirect:/otherPath、redirect:https://example.com
}
依賴:commons-fileupload、commons-io
配置 MultipartResolver:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600" /> <!-- 100MB -->
</bean>
Controller 處理:
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String upload(HttpServletRequest req) throws Exception {
MultipartHttpServletRequest mreq = (MultipartHttpServletRequest) req;
MultipartFile file = mreq.getFile("file");
String fileName = file.getOriginalFilename();
// 生成唯一文件名:時間戳 + 原後綴
String newName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
+ fileName.substring(fileName.lastIndexOf('.'));
// 保存到項目部署路徑/upload/
FileOutputStream fos = new FileOutputStream(
req.getSession().getServletContext().getRealPath("/") + "upload/" + newName);
fos.write(file.getBytes());
fos.close();
return "hello";
}
前端表單:enctype="multipart/form-data",method="post",<input type="file" name="file">
@RequestMapping("/download")
public void download(HttpServletResponse response, String fileName) throws Exception {
// 設置響應頭
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 讀取文件寫入響應流
FileInputStream fis = new FileInputStream(realPath + "upload/" + fileName);
OutputStream os = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
fis.close();
os.flush();
os.close();
}
前端:window.open("mvc/download?fileName=" + encodeURIComponent(name)) 解決中文亂碼。
@Controller
@RequestMapping("/rest")
public class RestController {
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public String get(@PathVariable("id") Integer id) { ... }
@RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
public String post(@PathVariable("id") Integer id) { ... }
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public String put(@PathVariable("id") Integer id) { ... }
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public String delete(@PathVariable("id") Integer id) { ... }
}
web.xml 配置:
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
前端表單:隱藏字段 _method=PUT/DELETE,method="post",Filter 會轉換請求方法。
| 類型 | 實現方式 | 適用場景 |
|---|---|---|
| 局部異常 | Controller 內 @ExceptionHandler 方法返回 ModelAndView | 特定 Controller 業務異常 |
| 全局異常 (注解) | @ControllerAdvice 類 + @ExceptionHandler 方法 | 全應用統一異常處理、日志、友好頁面 |
| 全局異常 (配置) | SimpleMappingExceptionResolver Bean 映射異常類到視圖名 | 無侵入式配置、傳統項目 |
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
System.out.println("preHandle: 權限檢查、日志記錄");
return true; // false 終止後續執行
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView mv) {
System.out.println("postHandle: 視圖渲染前,可修改模型數據");
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
System.out.println("afterCompletion: 視圖渲染後,資源清理、耗時統計");
}
}
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/mvc/**" />
<bean class="com.example.interceptor.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
public class User {
private int id;
@NotEmpty(message = "{NotEmpty.user.name}")
private String name;
@Past(message = "{Past.user.birth}")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
// getter/setter/toString
}
<form:form action="form/add" method="post" modelAttribute="user">
id: <form:input path="id" /><form:errors path="id" /><br/>
name: <form:input path="name" /><form:errors path="name" /><br/>
birth: <form:input path="birth" /><form:errors path="birth" />
<input type="submit" value="submit" />
</form:form>
@Controller
@RequestMapping("/form")
public class FormController {
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String add(@Valid User u, BindingResult br) {
if (br.getErrorCount() > 0) {
return "addUser"; // 回顯表單與錯誤
}
return "showUser";
}
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String addForm(Map<String, Object> map) {
map.put("user", new User()); // 必須預置模型對象
return "addUser";
}
}
src/main/resources/locale.properties (默認) / locale_zh_CN.properties (中文)
# locale.properties NotEmpty.user.name=Name cannot be empty Past.user.birth=Birth must be a past date # locale_zh_CN.properties NotEmpty.user.name=姓名不能為空 Past.user.birth=出生日期必須是過去日期
配置 MessageSource:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="locale" />
</bean>
<%@ taglib prefix="fmt" uri="https://java.sun.com/jsp/jstl/fmt" %> <fmt:message key="username" /> <!-- 顯示賬號/Account --> <fmt:message key="password" /> <!-- 顯示密碼/Password -->
瀏覽器語言切換自動生效。
@Controller/@ControllerAdvice@Controller/@ControllerAdvice<context:component-scan base-package="com.example">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>
<context:component-scan base-package="com.example">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>
Controller 中 @Autowired 注入 Service,Service 標註 @Component。父容器創建 Service,子容器創建 Controller,子容器可引用父容器 Bean,實現依賴注入。
DispatcherServletHandlerMapping 尋找匹配的 Handler (Controller 方法)HandlerAdapter 適配調用 HandlerModelAndView/視圖名/對象ViewResolver 解析視圖afterCompletion 收尾掌握以上棧,你將具備構建健壯、可維護、高性能 Spring MVC 應用的全棧能力。