Spring MVC 是 Spring Framework 的一个模块,专门用于开发 Web 应用程序。它实现了 MVC(Model-View-Controller)设计模式,使得开发 Web 应用变得简单高效。本文将详细介绍 Spring MVC 的核心概念、工作原理以及实际应用。

目录

  • [什么是 Spring MVC](#什么是-spring-mvc)

  • [Spring MVC 的核心组件](#spring-mvc-的核心组件)

  • [Spring MVC 的工作流程](#spring-mvc-的工作流程)

  • [Spring MVC 的配置方式](#spring-mvc-的配置方式)

  • [Spring MVC 的常用注解](#spring-mvc-的常用注解)

  • [Spring MVC 的数据绑定](#spring-mvc-的数据绑定)

  • [Spring MVC 的视图解析](#spring-mvc-的视图解析)

  • [Spring MVC 的异常处理](#spring-mvc-的异常处理)

  • [Spring MVC 的拦截器](#spring-mvc-的拦截器)

  • [Spring MVC 与 RESTful API](#spring-mvc-与-restful-api)

  • [Spring MVC 的文件上传](#spring-mvc-的文件上传)

  • [Spring MVC 的实际应用案例](#spring-mvc-的实际应用案例)

  • [总结与展望](#总结与展望)

什么是 Spring MVC

Spring MVC 是一个基于 Java 的实现了 MVC 设计模式的请求驱动类型的轻量级 Web 框架,通过把 Model、View、Controller 分离,将 Web 层进行职责解耦,把复杂的 Web 应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。

MVC 设计模式

  • Model(模型):负责存储数据和处理用户请求的业务逻辑

  • View(视图):负责展示数据给用户

  • Controller(控制器):负责接收用户请求并调用模型和视图完成用户请求

Spring MVC 的核心组件

Spring MVC 框架由以下几个核心组件组成:

1. DispatcherServlet:前端控制器,是整个 Spring MVC 的核心。负责接收请求、分发请求、处理响应。

2. HandlerMapping:处理器映射器,根据请求的 URL 找到对应的处理器(Handler)。

3. HandlerAdapter:处理器适配器,按照特定的规则去执行处理器。

4. Handler:处理器,也称为 Controller,负责具体的业务处理逻辑。

5. ViewResolver:视图解析器,负责将处理结果生成 View 视图。

6. View:视图,负责将模型数据通过页面展示给用户。

Spring MVC 的工作流程

Spring MVC 的工作流程可以用以下步骤来描述:

1. 用户发送请求至前端控制器 DispatcherServlet

2. DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器

3. 处理器映射器根据请求 URL 找到具体的处理器,生成处理器对象及处理器拦截器(如果有),一并返回给 DispatcherServlet

4. DispatcherServlet 通过 HandlerAdapter 处理器适配器调用处理器

5. 执行处理器(Controller,也叫后端控制器)

6. Controller 执行完成返回 ModelAndView

7. HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet

8. DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器

9. ViewResolver 解析后返回具体 View

10. DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)

11. DispatcherServlet 响应用户

![Spring MVC 工作流程图](https://cdn.jsdelivr.net/gh/doocs/advanced-java@main/images/spring-mvc-process.png)

Spring MVC 的配置方式

Spring MVC 提供了两种配置方式:XML 配置和 Java 配置。

XML 配置方式

<!-- web.xml 配置 DispatcherServlet -->

<servlet>

    <servlet-name>dispatcher</servlet-name>

    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <init-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>classpath:spring-mvc.xml</param-value>

    </init-param>

    <load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

    <servlet-name>dispatcher</servlet-name>

    <url-pattern>/</url-pattern>

</servlet-mapping>

<!-- spring-mvc.xml 配置视图解析器等 -->

<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"/>

    <!-- 开启 SpringMVC 注解驱动 -->

    <mvc:annotation-driven/>

    <!-- 配置视图解析器 -->

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <property name="prefix" value="/WEB-INF/views/"/>

        <property name="suffix" value=".jsp"/>

    </bean>

</beans>

Java 配置方式

// WebConfig.java

@Configuration
@EnableWebMvc

@ComponentScan("com.example.controller")

public class WebConfig implements WebMvcConfigurer {

    

    @Override

    public void configureViewResolvers(ViewResolverRegistry registry) {

        registry.jsp("/WEB-INF/views/", ".jsp");

    }

}

// WebAppInitializer.java

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    

    @Override

    protected Class<?>[] getRootConfigClasses() {

        return new Class[] { RootConfig.class };

    }

    

    @Override

    protected Class<?>[] getServletConfigClasses() {

        return new Class[] { WebConfig.class };

    }

    

    @Override

    protected String[] getServletMappings() {

        return new String[] { "/" };

    }

}

Spring MVC 的常用注解

Spring MVC 提供了丰富的注解,使得开发更加简便:

1. @Controller:标识一个类为控制器

2. @RequestMapping:映射请求路径

3. @GetMapping:映射 GET 请求

4. @PostMapping:映射 POST 请求

5. @PutMapping:映射 PUT 请求

6. @DeleteMapping:映射 DELETE 请求

7. @PathVariable:获取 URL 中的动态参数

8. @RequestParam:获取请求参数

9. @RequestBody:获取请求体内容

10. @ResponseBody:将返回值直接写入响应体

11. @RestController:@Controller 和 @ResponseBody 的组合注解

12. @ModelAttribute:绑定请求参数到模型对象

示例代码

@Controller

@RequestMapping("/user")

public class UserController {

    

    @GetMapping("/{id}")

    public String getUserById(@PathVariable("id") Long id, Model model) {

        User user = userService.findById(id);

        model.addAttribute("user", user);

        return "userDetail";

    }

    

    @PostMapping

    public String createUser(@RequestParam String username, @RequestParam String password) {

        userService.createUser(username, password);

        return "redirect:/user/list";

    }

    

    @GetMapping("/json/{id}")

    @ResponseBody

    public User getUserJson(@PathVariable("id") Long id) {

        return userService.findById(id);

    }

}

Spring MVC 的数据绑定

Spring MVC 提供了强大的数据绑定功能,可以将请求参数绑定到 Java 对象上。

简单类型绑定

@GetMapping("/hello")

public String hello(@RequestParam String name) {

    return "Hello, " + name;

}

对象绑定

@PostMapping("/register")

public String register(User user) {

    userService.register(user);

    return "redirect:/login";

}

集合类型绑定

@PostMapping("/batch")

public String batchUpdate(@RequestParam("ids") List<Long> ids) {

    userService.batchDelete(ids);

    return "redirect:/user/list";

}

日期类型绑定

@InitBinder

public void initBinder(WebDataBinder binder) {

    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    dateFormat.setLenient(false);

    binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));

}

@PostMapping("/event")

public String createEvent(@RequestParam("eventDate") Date eventDate, @RequestParam String name) {

    eventService.createEvent(name, eventDate);

    return "redirect:/event/list";

}

Spring MVC 的视图解析

Spring MVC 支持多种视图技术,包括 JSP、Thymeleaf、FreeMarker 等。

JSP 视图

@GetMapping("/hello")

public String hello(Model model) {

    model.addAttribute("message", "Hello, Spring MVC!");

    return "hello"; // 返回视图名称,会被解析为 /WEB-INF/views/hello.jsp

}

Thymeleaf 视图

// 配置 Thymeleaf 视图解析器

@Bean

public ViewResolver thymeleafViewResolver() {

    ThymeleafViewResolver resolver = new ThymeleafViewResolver();

    resolver.setTemplateEngine(templateEngine());

    resolver.setCharacterEncoding("UTF-8");

    return resolver;

}

@Bean

public SpringTemplateEngine templateEngine() {

    SpringTemplateEngine engine = new SpringTemplateEngine();

    engine.setTemplateResolver(templateResolver());

    return engine;

}

@Bean

public ITemplateResolver templateResolver() {

    SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();

    resolver.setPrefix("/WEB-INF/templates/");

    resolver.setSuffix(".html");

    resolver.setTemplateMode(TemplateMode.HTML);

    resolver.setCharacterEncoding("UTF-8");

    return resolver;

}

Spring MVC 的异常处理

Spring MVC 提供了多种异常处理机制:

@ExceptionHandler 注解

@Controller

public class UserController {

    

    @ExceptionHandler(UserNotFoundException.class)

    public ModelAndView handleUserNotFoundException(UserNotFoundException ex) {

        ModelAndView mav = new ModelAndView("error");

        mav.addObject("message", ex.getMessage());

        return mav;

    }

    

    @GetMapping("/user/{id}")

    public String getUser(@PathVariable Long id) {

        User user = userService.findById(id);

        if (user == null) {

            throw new UserNotFoundException("User not found with id: " + id);

        }

        return "userDetail";

    }

}

全局异常处理器

@ControllerAdvice

public class GlobalExceptionHandler {

    

    @ExceptionHandler(Exception.class)

    public ModelAndView handleException(Exception ex) {

        ModelAndView mav = new ModelAndView("error");

        mav.addObject("message", "An error occurred: " + ex.getMessage());

        return mav;

    }

    

    @ExceptionHandler(UserNotFoundException.class)

    public ModelAndView handleUserNotFoundException(UserNotFoundException ex) {

        ModelAndView mav = new ModelAndView("error");

        mav.addObject("message", ex.getMessage());

        return mav;

    }

}

SimpleMappingExceptionResolver

@Bean

public SimpleMappingExceptionResolver exceptionResolver() {

    SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();

    Properties mappings = new Properties();

    mappings.put("java.lang.Exception", "error");

    mappings.put("com.example.exception.UserNotFoundException", "user-not-found");

    resolver.setExceptionMappings(mappings);

    resolver.setDefaultErrorView("default-error");

    return resolver;

}

Spring MVC 的拦截器

Spring MVC 的拦截器(Interceptor)类似于 Servlet 的过滤器(Filter),用于在请求处理的前后执行一些操作。

定义拦截器

public class AuthInterceptor implements HandlerInterceptor {

    

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 在请求处理之前执行,返回 true 表示继续处理,返回 false 表示中断处理

        String token = request.getHeader("Authorization");

        if (token == null || !tokenService.isValid(token)) {

            response.sendRedirect("/login");

            return false;

        }

        return true;

    }

    

    @Override

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        // 在请求处理之后,视图渲染之前执行

    }

    

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        // 在整个请求处理完毕后执行

    }

}

注册拦截器

@Configuration

public class WebConfig implements WebMvcConfigurer {

    

    @Autowired

    private AuthInterceptor authInterceptor;

    

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(authInterceptor)

                .addPathPatterns("/admin/**")

                .excludePathPatterns("/admin/login");

    }

}

Spring MVC 与 RESTful API

Spring MVC 对 RESTful API 的开发提供了良好的支持。

RESTful 控制器

@RestController

@RequestMapping("/api/users")

public class UserRestController {

    

    @Autowired

    private UserService userService;

    

    @GetMapping

    public List<User> getAllUsers() {

        return userService.findAll();

    }

    

    @GetMapping("/{id}")

    public ResponseEntity<User> getUserById(@PathVariable Long id) {

        User user = userService.findById(id);

        if (user == null) {

            return ResponseEntity.notFound().build();

        }

        return ResponseEntity.ok(user);

    }

    

    @PostMapping

    public ResponseEntity<User> createUser(@RequestBody User user) {

        User savedUser = userService.save(user);

        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);

    }

    

    @PutMapping("/{id}")

    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {

        if (!userService.exists(id)) {

            return ResponseEntity.notFound().build();

        }

        user.setId(id);

        User updatedUser = userService.update(user);

        return ResponseEntity.ok(updatedUser);

    }

    

    @DeleteMapping("/{id}")

    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {

        if (!userService.exists(id)) {

            return ResponseEntity.notFound().build();

        }

        userService.delete(id);

        return ResponseEntity.noContent().build();

    }

}

Spring MVC 的文件上传

Spring MVC 提供了简单的文件上传功能。

配置 MultipartResolver

@Bean

public CommonsMultipartResolver multipartResolver() {

    CommonsMultipartResolver resolver = new CommonsMultipartResolver();

    resolver.setMaxUploadSize(5242880); // 5MB

    return resolver;

}

处理文件上传

@Controller

public class FileUploadController {

    

    @PostMapping("/upload")

    public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

        if (file.isEmpty()) {

            redirectAttributes.addFlashAttribute("message", "请选择一个文件上传");

            return "redirect:/uploadForm";

        }

        

        try {

            byte[] bytes = file.getBytes();

            Path path = Paths.get("uploads/" + file.getOriginalFilename());

            Files.write(path, bytes);

            

            redirectAttributes.addFlashAttribute("message", "文件上传成功: " + file.getOriginalFilename());

        } catch (IOException e) {

            e.printStackTrace();

            redirectAttributes.addFlashAttribute("message", "文件上传失败: " + e.getMessage());

        }

        

        return "redirect:/uploadForm";

    }

    

    @GetMapping("/uploadForm")

    public String showUploadForm() {

        return "uploadForm";

    }

}

Spring MVC 的实际应用案例

下面是一个简单的用户管理系统的示例:

实体类

public class User {

    private Long id;

    private String username;

    private String email;

    private String password;

    

    // 构造函数、getter 和 setter 方法

}

服务层

@Service

public class UserService {

    

    private Map<Long, User> users = new ConcurrentHashMap<>();

    private AtomicLong idGenerator = new AtomicLong();

    

    public List<User> findAll() {

        return new ArrayList<>(users.values());

    }

    

    public User findById(Long id) {

        return users.get(id);

    }

    

    public User save(User user) {

        Long id = idGenerator.incrementAndGet();

        user.setId(id);

        users.put(id, user);

        return user;

    }

    

    public User update(User user) {

        users.put(user.getId(), user);

        return user;

    }

    

    public void delete(Long id) {

        users.remove(id);

    }

    

    public boolean exists(Long id) {

        return users.containsKey(id);

    }

}

控制器

@Controller

@RequestMapping("/users")

public class UserController {

    

    @Autowired

    private UserService userService;

    

    @GetMapping

    public String listUsers(Model model) {

        List<User> users = userService.findAll();

        model.addAttribute("users", users);

        return "userList";

    }

    

    @GetMapping("/{id}")

    public String viewUser(@PathVariable Long id, Model model) {

        User user = userService.findById(id);

        if (user == null) {

            throw new RuntimeException("User not found with id: " + id);

        }

        model.addAttribute("user", user);

        return "userDetail";

    }

    

    @GetMapping("/new")

    public String newUserForm(Model model) {

        model.addAttribute("user", new User());

        return "userForm";

    }

    

    @PostMapping

    public String createUser(@ModelAttribute User user, BindingResult result) {

        if (result.hasErrors()) {

            return "userForm";

        }

        userService.save(user);

        return "redirect:/users";

    }

    

    @GetMapping("/{id}/edit")

    public String editUserForm(@PathVariable Long id, Model model) {

        User user = userService.findById(id);

        if (user == null) {

            throw new RuntimeException("User not found with id: " + id);

        }

        model.addAttribute("user", user);

        return "userForm";

    }

    

    @PostMapping("/{id}")

    public String updateUser(@PathVariable Long id, @ModelAttribute User user, BindingResult result) {

        if (result.hasErrors()) {

            return "userForm";

        }

        user.setId(id);

        userService.update(user);

        return "redirect:/users";

    }

    

    @GetMapping("/{id}/delete")

    public String deleteUser(@PathVariable Long id) {

        userService.delete(id);

        return "redirect:/users";

    }

}

视图模板(使用 JSP)

<!-- userList.jsp -->

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE html>

<html>

<head>

    <meta charset="UTF-8">

    <title>用户列表</title>

</head>

<body>

    <h1>用户列表</h1>

    <a href="<c:url value='/users/new'/>">添加用户</a>

    <table border="1">

        <tr>

            <th>ID</th>

            <th>用户名</th>

            <th>邮箱</th>

            <th>操作</th>

        </tr>

        <c:forEach items="${users}" var="user">

            <tr>

                <td>${user.id}</td>

                <td>${user.username}</td>

                <td>${user.email}</td>

                <td>

                    <a href="<c:url value='/users/${user.id}'/>">查看</a>

                    <a href="<c:url value='/users/${user.id}/edit'/>">编辑</a>

                    <a href="<c:url value='/users/${user.id}/delete'/>">删除</a>

                </td>

            </tr>

        </c:forEach>

    </table>

</body>

</html>

参考资料

1. Spring 官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html

2. Spring MVC 教程:https://www.baeldung.com/spring-mvc-tutorial

3. Spring Boot 与 Spring MVC:https://spring.io/guides/gs/serving-web-content/