SpringMVC 详解
1. SpringMVC 概述
SpringMVC 的基本概念及其在 Java Web 开发中的作用
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc
),但它通常被称为“Spring MVC”。
在控制层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点,是因为SpringMVC具备如下显著优势:
- Spring 家族原生产品,与IOC容器等基础设施无缝对接
- 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
- 代码清新简洁,大幅度提升开发效率
- 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
性能卓著,尤其适合现代大型、超大型互联网项目要求
MVC(Model-View-Controller)设计模式简介
MVC
是一种经典的软件架构模式,将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。模型负责数据处理和业务逻辑,视图负责用户界面展示,控制器作为中间层协调模型与视图的交互。该模式最早由Trygve Reenskaug在1979年提出,广泛应用于Web开发、桌面应用和移动应用领域。SpringMVC 与传统 Servlet 的对比
传统 Servlet代码写法
@WebServlet("/welcome")
public class WelcomeServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<form method='post'>");
out.println("<input type='text' name='name' placeholder='Enter your name'>");
out.println("<button type='submit'>Submit</button>");
out.println("</form>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("name");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<p>Hello, " + name + "!</p>");
}
}
Spring MVC 写法 调用业务即可
@Controller
@RequestMapping("/welcome")
public class WelcomeController {
@GetMapping
public String showForm() {
return "welcomeForm";
}
@PostMapping
public String handleSubmit(@RequestParam String name, Model model) {
model.addAttribute("message", "Hello, " + name + "!");
return "welcomeResult";
}
}
2. SpringMVC 核心组件
- DispatcherServlet:前端控制器的作用与配置
它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ] - HandlerMapping:请求映射处理器
它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书] - Controller:业务逻辑处理
执行具体业务逻辑,处理请求参数并生成响应(数据或视图)。需关注与领域模型的交互设计。 - ViewResolver:视图解析器
视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务] - ModelAndView:数据模型与视图绑定
封装模型数据(键值对集合)和目标视图信息。模型数据通过ModelMap传递至视图层渲染,视图可为逻辑名称(如"home")或直接视图对象。在RESTful服务中通常由@ResponseBody替代。[协调员]
3. SpringMVC 请求处理流程
- 请求从客户端到响应的完整流程
客户端请求 --> DispatcherServlet
|
v
HandlerMapping
|
v
HandlerAdapter
|
v
Controller
|
v
ModelAndView
|
v
ViewResolver
|
v
View (JSP, Thymeleaf, etc.)
|
v
响应内容 --> 客户端
- 涉及的核心组件及其交互方式
- DispatcherServlet:• 前端控制器,负责拦截所有请求,并协调其他组件完成请求处理。
- HandlerMapping:• 请求映射器,负责将请求映射到具体的处理器(Controller)。
- HandlerAdapter:• 处理器适配器,负责调用处理器(Controller)的方法。
- Controller:• 处理器,负责处理具体的业务逻辑,并返回 ModelAndView 对象。
- ModelAndView:• 包含视图名称和模型数据的对象,用于传递给视图解析器。
- ViewResolver:• 视图解析器,负责解析视图名称,找到具体的视图对象。
- View:• 视图对象,负责渲染最终的响应内容。
- 生命周期与关键执行点
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Controller
@RequestMapping("/demo")
public class LifecycleDemoController {
// 初始化阶段(Bean生命周期)
@PostConstruct
public void init() {
System.out.println("Bean初始化完成");
}
// 请求映射阶段
@GetMapping("/process")
@ResponseBody
public String handleRequest(
@RequestParam(required = false, defaultValue = "default") String param,
@ModelAttribute User user) {
System.out.println("接收到参数: " + param);
System.out.println("绑定模型属性: " + user);
// 业务处理
String result = executeBusinessLogic(param);
System.out.println("业务逻辑执行完成");
return result;
}
private String executeBusinessLogic(String input) {
return "处理结果: " + input.toUpperCase();
}
// 异常处理阶段
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public String handleError(RuntimeException ex) {
System.out.println("捕获到异常: " + ex.getMessage());
return "错误处理结果";
}
// 销毁阶段(Bean生命周期)
@PreDestroy
public void cleanup() {
System.out.println("Bean即将销毁");
}
}
// 模型类
class User {
private String name;
private int age;
// getters and setters
}
配置类
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 可添加拦截器、视图解析器等配置
}
关键执行点说明
初始化阶段
@PostConstruct标记的方法会在依赖注入完成后执行,用于资源初始化操作。此时ServletContext和ApplicationContext已可用。
请求处理阶段
参数绑定:@RequestParam处理查询参数,@ModelAttribute自动绑定表单数据到对象
业务方法执行:实际处理请求的核心逻辑
返回值处理:@ResponseBody将返回值直接写入HTTP响应体
异常处理阶段
@ExceptionHandler捕获控制器内抛出的指定异常,实现集中错误处理
销毁阶段
@PreDestroy标记的方法在Bean销毁前执行,用于释放资源
4. SpringMVC 注解详解
- @Controller / @RestController:控制器声明
RestController就相当于Controller+RequestBody
@Controller
public class HelloController {
//handlers
/**
* handler就是controller内部的具体方法
* @RequestMapping("/springmvc/hello") 就是用来向handlerMapping中注册的方法注解!
* @ResponseBody 代表向浏览器直接返回数据!
*/
@RequestMapping("/springmvc/hello")
@ResponseBody
public String hello(){
System.out.println("HelloController.hello");
return "hello springmvc!!";
}
}
- @RequestMapping / @GetMapping / @PostMapping:请求映射
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class ExampleController {
// 使用 @RequestMapping 处理多个HTTP方法
@RequestMapping(value = "/example", method = {RequestMethod.GET, RequestMethod.POST})
public String handleExample() {
return "This endpoint accepts both GET and POST requests";
}
// 使用 @GetMapping 只处理GET请求
@GetMapping("/getExample")
public String handleGetExample() {
return "This is a GET request example";
}
// 使用 @PostMapping 只处理POST请求
@PostMapping("/postExample")
public String handlePostExample(@RequestBody String requestBody) {
return "Received POST request with body: " + requestBody;
}
}
- @RequestParam / @PathVariable:参数绑定
@RequestParam:从URL查询字符串获取参数(如/path?param=value)
/**
* 前端请求: http://localhost:8080/param/data?name=xx&stuAge=18
*
* 使用@RequestParam注解标记handler方法的形参
* 指定形参对应的请求参数@RequestParam(请求参数名称)
*/
@GetMapping(value="/data")
@ResponseBody
public Object paramForm(@RequestParam("name") String name,
@RequestParam("stuAge") int age){
System.out.println("name = " + name + ", age = " + age);
return name+age;
}
@PathVariable:从URI模板中提取变量(如/path/{value})
/**
* 动态路径设计: /user/{动态部分}/{动态部分} 动态部分使用{}包含即可! {}内部动态标识!
* 形参列表取值: @PathVariable Long id 如果形参名 = {动态标识} 自动赋值!
* @PathVariable("动态标识") Long id 如果形参名 != {动态标识} 可以通过指定动态标识赋值!
*
* 访问测试: /param/user/1/root -> id = 1 uname = root
*/
@GetMapping("/user/{id}/{name}")
@ResponseBody
public String getUser(@PathVariable Long id,
@PathVariable("name") String uname) {
System.out.println("id = " + id + ", uname = " + uname);
return "user_detail";
}
5. Spring MVC响应数据
- handler 方法分析
理解handler方法的作用和组成
handler主要负责三个关键任务:
接收请求参数:通过多种方式获取前端传递的数据。
支持类型:查询参数(param)、JSON数据、路径变量(pathVariable)、共享域(如session或request属性)等。
调用业务逻辑:在方法体内调用业务层服务。
例如:service.xx(),其中xx()是业务方法。
响应前端数据:返回结果给客户端。
支持方式:直接返回JSON数据、页面(但模版页面跳转不在此讲解)、转发(forward)、重定向(redirect)等。
@GetMapping
public Object handler(简化请求参数接收){
调用业务方法
返回的结果 (页面跳转,返回数据(json))
return 简化响应前端数据;
}
页面跳转控制
导入maven依赖
<!-- jsp需要依赖! jstl-->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
创建JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!-- 可以获取共享域的数据,动态展示! jsp== 后台vue -->
${msg}
</body>
</html>
配置jsp视图解析器
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {
//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/views/",".jsp");
}
}
handler返回视图
/**
* 跳转到提交文件页面 /save/jump
*
* 如果要返回jsp页面!
* 1.方法返回值改成字符串类型
* 2.返回逻辑视图名即可
* <property name="prefix" value="/WEB-INF/views/"/>
* + 逻辑视图名 +
* <property name="suffix" value=".jsp"/>
*/
@GetMapping("jump")
public String jumpJsp(Model model){
System.out.println("FileController.jumpJsp");
model.addAttribute("msg","request data!!");
return "home";
}
转发和重定向
在 Spring MVC 中,Handler 方法返回值来实现快速转发,可以使用 redirect
或者 forward
关键字来实现重定向。
@RequestMapping("/redirect-demo")
public String redirectDemo() {
// 重定向到 /demo 路径
return "redirect:/demo";
}
@RequestMapping("/forward-demo")
public String forwardDemo() {
// 转发到 /demo 路径
return "forward:/demo";
}
返回静态资源处理
SpringMVC 配置配置类
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.nie.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {
//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/views/",".jsp");
}
//开启静态资源处理 <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
返回JSON数据
可以在方法上使用 @ResponseBody
注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。在前后端分离的项目中使用!
@GetMapping("/accounts/{id}")
@ResponseBody
public Object handle() {
// ...
return obj;
}
测试方法:
@RequestMapping(value = "/user/detail", method = RequestMethod.POST)
@ResponseBody
public User getUser(@RequestBody User userParam) {
System.out.println("userParam = " + userParam);
User user = new User();
user.setAge(18);
user.setName("John");
//返回的对象,会使用jackson的序列化工具,转成json返回给前端!
return user;
}
也可以直接在类上使用@ResponseBody responseBody可以添加到类上,代表默认类中的所有方法都生效!
@ResponseBody
@Controller
@RequestMapping("param")
public class ParamController {
@RestController
类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解。所以使用了 @RestController 注解就相当于给类中的每个方法都加了 @ResponseBody 注解。
RestController源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
6. RESTful API 设计
- RESTful 风格 API 的设计原则
- 每一个URI代表1种资源(URI 是名词);
- 客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
- 资源的表现形式是XML或者JSON;
- 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
- 使用 SpringMVC 实现 RESTful 接口
准备实体类
package com.nie.pojo;
public class User {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
准备controller
@RequestMapping("user")
@RestController
public class UserController {
/**
* 模拟分页查询业务接口
*/
@GetMapping
public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
@RequestParam(name = "size",required = false,defaultValue = "10")int size){
System.out.println("page = " + page + ", size = " + size);
System.out.println("分页查询业务!");
return "{'status':'ok'}";
}
/**
* 模拟用户保存业务接口
*/
@PostMapping
public Object saveUser(@RequestBody User user){
System.out.println("user = " + user);
System.out.println("用户保存业务!");
return "{'status':'ok'}";
}
/**
* 模拟用户详情业务接口
*/
@PostMapping("/{id}")
public Object detailUser(@PathVariable Integer id){
System.out.println("id = " + id);
System.out.println("用户详情业务!");
return "{'status':'ok'}";
}
/**
* 模拟用户更新业务接口
*/
@PutMapping
public Object updateUser(@RequestBody User user){
System.out.println("user = " + user);
System.out.println("用户更新业务!");
return "{'status':'ok'}";
}
/**
* 模拟条件分页查询业务接口
*/
@GetMapping("search")
public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
@RequestParam(name = "size",required = false,defaultValue = "10")int size,
@RequestParam(name = "keyword",required= false)String keyword){
System.out.println("page = " + page + ", size = " + size + ", keyword = " + keyword);
System.out.println("条件分页查询业务!");
return "{'status':'ok'}";
}
}
- HATEOAS 与超媒体支持
导入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
以下是一个基于 Spring HATEOAS 的 Java 代码示例,展示如何为 REST API 添加超媒体支持:
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public EntityModel<Product> getProduct(@PathVariable Long id) {
Product product = productService.findById(id); // 假设从服务层获取产品
// 创建包含产品和超链接的 EntityModel
EntityModel<Product> model = EntityModel.of(product);
// 添加自引用链接
model.add(WebMvcLinkBuilder.linkTo(
WebMvcLinkBuilder.methodOn(ProductController.class)
.getProduct(id)).withSelfRel());
// 添加相关资源链接
model.add(WebMvcLinkBuilder.linkTo(
WebMvcLinkBuilder.methodOn(ProductController.class)
.getAllProducts()).withRel("products"));
return model;
}
@GetMapping
public CollectionModel<EntityModel<Product>> getAllProducts() {
List<EntityModel<Product>> products = productService.findAll()
.stream()
.map(product -> EntityModel.of(product,
linkTo(methodOn(ProductController.class)
.getProduct(product.getId())).withSelfRel()))
.collect(Collectors.toList());
return CollectionModel.of(products,
linkTo(methodOn(ProductController.class)
.getAllProducts()).withSelfRel());
}
}
7.全局异常处理机智
异常处理的两种 方式
- 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
- 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如
@Throws
或@ExceptionHandler
),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。
基于注解异常声明异常类型
异常处理控制类,统一定义异常处理handler方法!
@RestControllerAdvice
public class GlobalExceptionHandler {
}
声明异常处理hander方法
/**
* 异常处理handler
* @ExceptionHandler(HttpMessageNotReadableException.class)
* 该注解标记异常处理Handler,并且指定发生异常调用该方法!
*
*
* @param e 获取异常对象!
* @return 返回handler处理结果!
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerJsonDateException(HttpMessageNotReadableException e){
return null;
}
/**
* 当发生空指针异常会触发此方法!
* @param e
* @return
*/
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){
return null;
}
/**
* 所有异常都会触发此方法!但是如果有具体的异常处理Handler!
* 具体异常处理Handler优先级更高!
* 例如: 发生NullPointerException异常!
* 会触发handlerNullException方法,不会触发handlerException方法!
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){
return null;
}
配置文件扫描控制器类配置
<!-- 扫描controller对应的包,将handler加入到ioc-->
@ComponentScan(basePackages = {"com.nie.controller",
"com.nie.exceptionhandler"})
8.拦截器
拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
以下是一个基于Java的简单拦截器实现代码,使用Spring框架的HandlerInterceptor接口。该拦截器可用于在请求处理前后执行特定逻辑(如日志记录、权限校验等)。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class CustomInterceptor implements HandlerInterceptor {
// 请求处理前执行
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("前置处理: " + request.getRequestURI());
// 此处可进行权限校验,返回false会中断请求
return true;
}
// 请求处理后、视图渲染前执行
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("后置处理: " + request.getRequestURI());
}
// 请求完成后执行(视图渲染完毕)
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("请求完成: " + request.getRequestURI());
}
}
在Spring Boot项目中,需通过配置类注册拦截器并指定拦截路径:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor())
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns("/api/public/**"); // 排除路径
}
}