Spring MVC 框架学习笔记

Spring MVC 是Spring Framework中的一个模块,用于构建基于Servlet的Web应用程序。它遵循**Model-View-Controller (MVC)**设计模式,将应用程序的逻辑、数据和展示层分离,便于开发和维护。它提供了丰富的注解支持、数据绑定、验证、异常处理、国际化等功能,并且可以与其他Spring模块(如Spring Security、Spring Data等)无缝集成。通过Spring MVC,开发者可以快速构建高效、可维护的Web应用程序。

一、配置方式

XML配置: 可以通过XML文件配置Spring MVC的各个组件。适合传统项目,所有配置都在 XML 文件中完成。 • Java配置: 通过@Configuration和@EnableWebMvc注解进行Java配置。适合现代化项目,完全使用 Java 代码进行配置,更简洁。 • 自动配置: Spring Boot提供了自动配置,简化了Spring MVC的配置过程。

根据项目需求选择合适的配置方式,可以有效提高开发效率和代码可维护性。


以下给出基于 XML 的配置基于 Java 的配置混合配置(XML + Java)对应的配置示例。

1. 基于 XML 的配置示例

这是传统的配置方式,所有配置都在 XML 文件中完成。

1.1 web.xml 配置

<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_3_1.xsd"
         version="3.1">

    <!-- 配置 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>/WEB-INF/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 映射 DispatcherServlet 到所有请求 -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

1.2 spring-servlet.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">

    <!-- 启用注解驱动的控制器 -->
    <mvc:annotation-driven />

    <!-- 扫描控制器包 -->
    <context:component-scan base-package="com.example.controller" />

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

2. 基于 Java 的配置

这是现代化的配置方式,完全使用 Java 代码进行配置。

2.1 WebAppInitializer 配置

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 创建 Spring 应用上下文
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        // 创建并注册 DispatcherServlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

2.2 AppConfig 配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.controller")
public class AppConfig {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

3. 混合配置(XML + Java)

这种方式结合了 XML 和 Java 配置的优点,适用于需要兼容旧代码的项目。

3.1 web.xml 配置

<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_3_1.xsd"
         version="3.1">

    <!-- 配置 ContextLoaderListener -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 配置 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>/WEB-INF/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 映射 DispatcherServlet 到所有请求 -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3.2 applicationContext.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"
       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">

    <!-- 扫描服务层和 DAO 层 -->
    <context:component-scan base-package="com.example.service, com.example.dao" />
</beans>

3.3 spring-servlet.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">

    <!-- 启用注解驱动的控制器 -->
    <mvc:annotation-driven />

    <!-- 扫描控制器包 -->
    <context:component-scan base-package="com.example.controller" />

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

4. Spring Boot项目中的Spring MVC配置方式

Spring Boot 的自动配置和灵活的扩展机制,使得开发者可以快速构建功能强大的 Spring MVC 应用。在 Spring Boot 项目中,Spring MVC 的配置非常简单:

  1. 默认配置:Spring Boot 已经为 Spring MVC 提供了默认的自动配置。无需额外配置即可使用。
  2. 自定义配置: • 通过 application.propertiesapplication.yml 配置。 • 通过 @Configuration 类自定义行为。 • 使用 @EnableWebMvc 完全控制配置。

1. 默认配置

Spring Boot 自动配置了 Spring MVC,包括以下默认行为: • 自动注册 DispatcherServlet。 • 自动配置视图解析器(如 Thymeleaf、FreeMarker、JSP 等)。 • 自动配置静态资源处理(如 /static/public/resources 等目录)。 • 自动配置消息转换器(如 JSON、XML 等)。

开发者无需额外配置即可使用 Spring MVC。


2. 自定义配置

如果需要自定义 Spring MVC 的行为,可以通过以下方式实现:

2.1 使用 application.propertiesapplication.yml

Spring Boot 提供了许多与 Spring MVC 相关的配置属性,可以在 application.propertiesapplication.yml 中进行配置。

示例:application.properties

# 静态资源路径
spring.mvc.static-path-pattern=/static/**

# 视图解析器前缀和后缀
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

# 文件上传配置
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB

示例:application.yml

spring:
  mvc:
    static-path-pattern: /static/**
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 50MB
2.2 使用 @Configuration

通过编写配置类,可以更灵活地自定义 Spring MVC 的行为。

示例:自定义视图解析器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

示例:自定义静态资源处理

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }
}

示例:自定义拦截器

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("/**");
    }
}

二、核心组件及处理机制

DispatcherServlet: Spring MVC的核心控制器,负责接收HTTP请求并将其分发给相应的处理器(Controller)。 • HandlerMapping: 用于将请求映射到相应的处理器(Controller)。 • Controller: 处理请求并返回模型和视图。 • ViewResolver: 解析视图名称并渲染视图(如JSP、Thymeleaf等)。 • ModelAndView: 包含模型数据和视图信息的对象,用于在Controller和View之间传递数据。

请求处理流程

  1. 请求到达DispatcherServlet:客户端发送的HTTP请求首先被DispatcherServlet接收。
  2. HandlerMapping查找处理器:DispatcherServlet通过HandlerMapping找到处理该请求的Controller。
  3. Controller处理请求:Controller执行业务逻辑,并返回一个ModelAndView对象。
  4. ViewResolver解析视图:DispatcherServlet通过ViewResolver解析视图名称,找到对应的视图。
  5. 视图渲染:视图使用模型数据渲染响应,并将结果返回给客户端。
+-------------------+
|   HTTP Request    |
+-------------------+
          |
          v
+-------------------+
| DispatcherServlet |
+-------------------+
          |
          v
+-------------------+
| HandlerMapping    |
| (查找处理器)       |
+-------------------+
          |
          v
+-------------------+
| HandlerAdapter    |
| (调用处理器)       |
+-------------------+
          |
          v
+-------------------+
| Controller Method |
| (执行业务逻辑)     |
+-------------------+
          |
          v
+-------------------+
| 返回结果           |
| (ModelAndView,    |
|  JSON, String)    |
+-------------------+
          |
          v
+-------------------+
| ViewResolver      |
| (解析视图)         |
+-------------------+
          |
          v
+-------------------+
|  HTTP Response    |
+-------------------+

详细步骤

  1. 接收请求: • DispatcherServlet 接收所有到达应用程序的 HTTP 请求。
  2. 查找处理器: • 调用 HandlerMapping,根据请求的 URL 和其他信息,查找合适的处理器(通常是 Controller 中的方法)。
  3. 调用处理器: • 调用 HandlerAdapter,执行处理器方法,并将方法返回的结果(如 ModelAndView、JSON 数据等)封装为响应。
  4. 处理业务逻辑: • 处理器方法执行业务逻辑,并返回结果。
  5. 解析视图: • 如果返回的是视图名称(如 ModelAndView),则调用 ViewResolver 解析视图,并渲染页面。
  6. 返回响应: • 将最终生成的响应(如 HTML 页面、JSON 数据等)返回给客户端

前端控制器

DispatcherServlet 是 Spring MVC 框架的核心组件,它充当了前端控制器(Front Controller)的角色,负责接收所有的 HTTP 请求,并将这些请求分发给合适的处理器(Handler),最后将处理结果返回给客户端。它依赖于 HandlerMappingHandlerAdapterViewResolver 等组件来完成请求的处理和响应的生成。通过灵活的路由配置、强大的扩展性和丰富的特性,DispatcherServlet 使得 Spring MVC 能够轻松构建高效、可维护的 Web 应用程序。


1. DispatcherServlet 的作用

DispatcherServlet 是 Spring MVC 的入口点,它的主要职责包括:

  1. 接收请求: • 接收所有到达应用程序的 HTTP 请求。
  2. 分发请求: • 根据请求的 URL 和其他信息,将请求分发给合适的处理器(通常是 Controller 中的方法)。
  3. 协调处理流程: • 调用 HandlerMappingHandlerAdapterViewResolver 等组件,完成请求的处理和响应的生成。
  4. 返回响应: • 将处理结果(如 HTML 页面、JSON 数据等)返回给客户端。

2. DispatcherServlet 的配置

DispatcherServlet 的配置可以通过以下两种方式实现:

1. 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>/WEB-INF/spring-servlet.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>
2. Java 配置

通过 Java 类配置 DispatcherServlet

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[] { "/" };
    }
}

3. DispatcherServlet 的特性

  1. 灵活的路由: • 支持基于注解的路由配置(如 @RequestMapping),也支持传统的 XML 配置。
  2. 强大的扩展性: • 可以通过自定义 HandlerMappingHandlerAdapter 等组件扩展功能。
  3. 异常处理: • 支持全局异常处理(如 @ControllerAdvice)和局部异常处理(如 @ExceptionHandler)。
  4. 文件上传: • 内置对文件上传的支持,可以通过 MultipartResolver 轻松处理文件上传请求。
  5. 视图解析: • 支持多种视图技术(如 JSP、Thymeleaf、FreeMarker 等),并可以通过 ViewResolver 灵活配置。

4. DispatcherServlet 的常见问题

  1. URL 映射冲突: • 如果多个处理器映射到同一个 URL,可能会导致冲突。需要确保 URL 映射的唯一性。
  2. 性能问题: • DispatcherServlet 处理请求时会涉及多个组件的调用,可能会影响性能。可以通过缓存、异步处理等方式优化性能。
  3. 配置错误: • 如果 HandlerMappingHandlerAdapter 等组件配置错误,可能会导致请求无法正确处理。需要仔细检查配置。

其他组件

HandlerExceptionResolver

作用: 处理请求处理过程中抛出的异常,并生成错误响应。 • 常用实现: • DefaultHandlerExceptionResolver: 处理 Spring MVC 内置的异常。 • ExceptionHandlerExceptionResolver: 处理 @ExceptionHandler 注解标记的方法。

MultipartResolver

作用: 处理文件上传请求,解析 multipart/form-data 类型的请求。 • 常用实现: • CommonsMultipartResolver: 基于 Apache Commons FileUpload 实现。 • StandardServletMultipartResolver: 基于 Servlet 3.0 的 Part API 实现。

三、注解支持

在 Spring MVC 框架中,注解(Annotations)是简化开发的核心工具,用于配置控制器、请求映射、参数绑定、异常处理等。

Spring MVC 提供的控制器注解使用灵活,无需扩展基类或实现特定接口。

@Controller: 标识一个类为Spring MVC的Controller。 • @RequestMapping: 映射URL到Controller的方法。 • @RequestParam: 绑定请求参数到方法参数。 • @PathVariable: 绑定URL中的变量到方法参数。 • @ModelAttribute: 绑定表单数据到模型对象。 • @ResponseBody: 将方法返回值直接写入HTTP响应体,通常用于返回JSON或XML数据。

REST支持

• Spring MVC支持构建RESTful Web服务,可以通过@RestController注解简化REST API的开发。 • 支持内容协商(Content Negotiation),可以根据请求的Accept头返回不同的响应格式(如JSON、XML)。

详见处理请求映射中RESTful风格的路径、视图技术中返回 JSON/XML 数据的相关内容。


1. 注解列表

分组注解作用注解位置
控制器相关注解@Controller标记一个类为 Spring MVC 控制器,通常用于返回视图(如 JSP、Thymeleaf)。
@RestController标记一个类为 RESTful 控制器,默认返回 JSON 或 XML 数据。
请求映射相关注解@RequestMapping将 HTTP 请求映射到控制器方法,支持 URL、请求方法、请求头等条件。类或方法
@GetMapping将 GET 请求映射到控制器方法。方法
@PostMapping将 POST 请求映射到控制器方法。方法
@PutMapping将 PUT 请求映射到控制器方法。方法
@DeleteMapping将 DELETE 请求映射到控制器方法。方法
@PatchMapping将 PATCH 请求映射到控制器方法。方法
请求参数绑定注解@RequestParam从请求中提取查询参数或表单数据。方法参数
@PathVariable从 URL 路径中提取变量值。方法参数
@RequestBody将请求体(如 JSON 或 XML)绑定到方法参数。方法参数
@RequestHeader从请求头中提取指定字段的值。方法参数
@CookieValue从 Cookie 中提取指定字段的值。方法参数
@ModelAttribute将请求参数绑定到模型对象,通常用于表单提交。方法或方法参数
响应相关注解@ResponseBody将方法的返回值直接写入 HTTP 响应体,通常用于返回 JSON 或 XML 数据。方法
@ResponseStatus指定 HTTP 响应的状态码。方法或异常处理方法
@RestController结合了 @Controller@ResponseBody,默认返回 JSON 或 XML 数据。
视图相关注解@ModelAttribute将数据添加到模型中,供视图使用。方法或方法参数
@SessionAttributes将模型数据存储在会话中,供多个请求共享。
@CrossOrigin允许跨域请求,通常用于 RESTful API。类或方法
异常处理相关注解@ExceptionHandler在控制器中定义异常处理方法,处理特定类型的异常。方法
@ControllerAdvice全局异常处理类,处理所有控制器中抛出的异常。
@ResponseStatus在异常处理方法中指定 HTTP 响应的状态码。方法或异常处理方法
配置相关注解@EnableWebMvc启用 Spring MVC 的默认配置,通常用于 Java 配置类。
@Configuration标记一个类为配置类,用于定义 Spring Bean。
@ComponentScan扫描指定包下的组件(如控制器、服务等)。
其他常用注解@Valid对方法参数进行数据校验,通常与 @RequestBody@ModelAttribute 结合使用。方法参数
@InitBinder自定义数据绑定逻辑,通常用于日期格式化等场景。方法
@Profile指定配置或组件在特定环境下生效。类或方法

2. 注解使用场景

注解作用注解位置重要事项
@Controller标记控制器类类级别需配合 @ResponseBody 返回数据
@RestController标记 RESTful 控制器类级别默认返回 JSON/XML 数据
@RequestMapping映射 HTTP 请求类/方法级别支持多种 HTTP 方法
@GetMapping简化 @RequestMapping方法级别分别处理 GET、POST、PUT、DELETE 请求
@PathVariable绑定 URL 路径变量方法参数级别路径变量名称与参数名称一致时可省略
@RequestParam绑定请求参数方法参数级别支持默认值和必填校验
@RequestBody绑定请求体方法参数级别通常用于 POST/PUT 请求
@ResponseBody将返回值写入响应体方法级别通常与 @Controller 结合使用
@ExceptionHandler处理控制器中的异常方法级别只能处理当前控制器的异常
@ControllerAdvice全局处理异常类级别避免重复定义异常处理方法
@ModelAttribute绑定请求参数或添加模型属性方法/参数级别用于共享数据
@SessionAttributes将模型属性存储到会话中类级别适用于跨请求共享数据

基本控制器示例

@Controller用于标记一个类为Spring MVC控制器。通常与@RequestMapping、@GetMapping、@PostMapping等注解一起使用。

@GetMapping: 用于将HTTP GET请求映射到控制器的方法上。可以与@RequestParam、@PathVariable等注解一起使用。


示例 : 使用 @Controller@GetMapping
@Controller
public class HomeController {

    @GetMapping("/home")
    public String home(Model model) {
        model.addAttribute("message", "Welcome to the Home Page!");
        return "home"; // 返回视图名称
    }
}

请求: GET /home响应: 渲染 home.html 视图,并传递 message 属性。


基本控制器示例

@RequestMapping用于将HTTP请求映射到控制器的方法上,可以注解在类上或方法上,还可以指定请求的URL、HTTP方法(GET、POST等)、请求参数等信息。可以与@RequestParam、@PathVariable等注解一起使用。

@RequestMapping

映射 HTTP 请求到控制器方法,支持指定 URL、请求方法、请求参数等。

类级别或方法级别。

value:指定 URL 路径。 • method:指定 HTTP 请求方法(如 RequestMethod.GET)。 • produces:指定响应的媒体类型(如 application/json)。 • consumes:指定请求的媒体类型。

使用示例

@Controller
public class HomeController {
    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home() {
        return "home";
    }
}

重要事项

• 可以用 @GetMapping@PostMapping 等简化写法。 • 支持通配符(如 /user/*)和路径变量(如 /user/{id})。

示例 : 使用 @Controller@RequestMapping
@Controller
@RequestMapping("/api")
public class ApiController {
  @RequestMapping(value = "/hello", method = RequestMethod.GET)
  public String handle() {
      return "Hello World!";
  }
}

请求: GET /api/hello响应:


@ResponseBody

作用: 用于将控制器方法的返回值直接写入HTTP响应体,而不是视图。 • 位置: 方法级别。 • 配合使用: 通常与@Controller注解一起使用。 • 示例:

@Controller
public class HelloController {
    @GetMapping("/hello")
    @ResponseBody
    public String handle() {
        return "Hello World!";
    }
}

获取请求消息头和消息体

在Spring MVC中,可以通过以下方式获取请求的消息头和消息体:

  1. 获取请求头: • 使用@RequestHeader注解:可以将请求头直接注入到Controller方法的参数中。
@GetMapping("/example")
public String example(@RequestHeader("User-Agent") String userAgent) {
    return "User-Agent: " + userAgent;
}
  1. 获取请求体: • 使用@RequestBody注解:可以将请求体(通常是JSON或XML)直接绑定到方法的参数中。
@PostMapping("/example")
public String example(@RequestBody User user) {
    return "Received user: " + user.getName();
}
  1. 获取请求参数: • 使用@RequestParam注解:可以将请求参数(通常是URL中的查询参数或表单数据)直接注入到方法的参数中。
@GetMapping("/example")
public String example(@RequestParam("name") String name) {
    return "Hello, " + name;
}
  1. 获取整个请求对象: • 使用HttpServletRequest对象:可以通过注入HttpServletRequest对象来获取请求的所有信息,包括请求头、请求体、请求参数等。
@GetMapping("/example")
public String example(HttpServletRequest request) {
    String userAgent = request.getHeader("User-Agent");
    return "User-Agent: " + userAgent;
}

通过这些方式,Spring MVC可以灵活地处理不同类型的请求,并获取请求中的各种信息。


处理查询参数

** @RequestParam**将 HTTP 请求参数绑定到方法参数,注解在方法参数上。适用于 GET 请求的查询参数或 POST 请求的表单参数。

主要属性**

value:指定请求参数名称(默认为参数名称)。 • required:指定参数是否必须(默认为 true)。 • defaultValue:指定参数的默认值。

示例 3: 使用 @GetMapping@RequestParam
@Controller
public class SearchController {

    @GetMapping("/search")
    public String search(@RequestParam String query, Model model) {
        model.addAttribute("query", query);
        return "search"; // 返回搜索结果视图
    }
}

请求: GET /search?query=Spring响应: 渲染 search.html 视图,并传递 query 属性。

示例 10: 处理布尔参数,使用 @RequestParam 处理布尔类型
@Controller
public class FeatureController {

    @GetMapping("/feature")
    public String checkFeature(@RequestParam boolean enabled, Model model) {
        model.addAttribute("status", enabled ? "enabled" : "disabled");
        return "feature"; // 返回功能状态视图
    }
}

请求: GET /feature?enabled=true响应: 渲染 feature.html 视图,显示 Feature is enabled


示例 4: 处理多个查询参数,使用 @GetMapping 和多个 @RequestParam
@Controller
public class ProductController {

    @GetMapping("/products")
    public String getProducts(
            @RequestParam String category,
            @RequestParam double minPrice,
            @RequestParam double maxPrice,
            Model model) {
        model.addAttribute("category", category);
        model.addAttribute("minPrice", minPrice);
        model.addAttribute("maxPrice", maxPrice);
        return "products"; // 返回产品列表视图
    }
}

请求: GET /products?category=Electronics&minPrice=100&maxPrice=500响应: 渲染 products.html 视图,并传递 categoryminPricemaxPrice 属性。


示例 5: 处理可选参数,使用 @RequestParamrequired 属性
@Controller
public class GreetingController {

    @GetMapping("/greet")
    public String greet(@RequestParam(required = false, defaultValue = "Guest") String name, Model model) {
        model.addAttribute("message", "Hello, " + name + "!");
        return "greet"; // 返回问候视图
    }
}

请求: GET /greet响应: 渲染 greet.html 视图,显示 Hello, Guest!。 • 请求: GET /greet?name=John响应: 渲染 greet.html 视图,显示 Hello, John!

示例 7: 处理数组或列表参数,使用 @RequestParam 接收列表
@Controller
public class TagController {

    @GetMapping("/tags")
    public String getTags(@RequestParam List<String> tags, Model model) {
        model.addAttribute("tags", tags);
        return "tags"; // 返回标签视图
    }
}

请求: GET /tags?tags=Java&tags=Spring&tags=Hibernate响应: 渲染 tags.html 视图,并传递 tags 列表。


示例 8: 处理复杂查询参数,使用 @RequestParam 处理复杂查询
@Controller
public class OrderController {

    @GetMapping("/orders")
    public String getOrders(
            @RequestParam String status,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
            Model model) {
        model.addAttribute("status", status);
        model.addAttribute("startDate", startDate);
        model.addAttribute("endDate", endDate);
        return "orders"; // 返回订单视图
    }
}

请求: GET /orders?status=Shipped&startDate=2023-01-01&endDate=2023-12-31响应: 渲染 orders.html 视图,并传递 statusstartDateendDate 属性。


处理表单提交

@PostMapping用于将HTTP POST请求映射到控制器的方法上。可以与@RequestBody、@RequestParam等注解一起使用。

示例 2: 使用 @Controller@PostMapping
@Controller
public class FormController {

    @GetMapping("/form")
    public String showForm(Model model) {
        model.addAttribute("user", new User());
        return "form"; // 返回表单视图
    }

    @PostMapping("/submit")
    public String submitForm(@ModelAttribute User user, Model model) {
        model.addAttribute("message", "User " + user.getName() + " submitted successfully!");
        return "result"; // 返回结果视图
    }
}

请求: GET /form响应: 渲染 form.html 视图,显示表单。 • 请求: POST /submit (表单提交) • 响应: 渲染 result.html 视图,显示提交结果。

示例 6: 处理表单提交并验证参数,使用 @PostMapping@RequestParam
@Controller
public class LoginController {

    @GetMapping("/login")
    public String showLoginForm() {
        return "login"; // 返回登录表单视图
    }

    @PostMapping("/login")
    public String login(
            @RequestParam String username,
            @RequestParam String password,
            Model model) {
        if ("admin".equals(username) && "password".equals(password)) {
            model.addAttribute("message", "Login successful!");
        } else {
            model.addAttribute("message", "Invalid credentials!");
        }
        return "loginResult"; // 返回登录结果视图
    }
}

请求: GET /login响应: 渲染 login.html 视图,显示登录表单。 • 请求: POST /login (表单提交) • 响应: 渲染 loginResult.html 视图,显示登录结果。


**7. @RequestBody**将 HTTP 请求体(通常为 JSON/XML)绑定到方法参数。通常用于 POST 或 PUT 请求,传递复杂对象。

使用示例

@RestController
public class UserController {
    @PostMapping("/user")
    public User createUser(@RequestBody User user) {
        return user;
    }
}

示例 11: 结合 @ModelAttribute,使用 @PostMapping@ModelAttribute
@Controller
public class RegistrationController {

    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        model.addAttribute("user", new User());
        return "register"; // 返回注册表单视图
    }

    @PostMapping("/register")
    public String registerUser(@ModelAttribute User user, Model model) {
        model.addAttribute("message", "User " + user.getName() + " registered successfully!");
        return "registrationResult"; // 返回注册结果视图
    }
}

请求: GET /register响应: 渲染 register.html 视图,显示注册表单。 • 请求: POST /register (表单提交) • 响应: 渲染 registrationResult.html 视图,显示注册结果。

@ModelAttribute

作用: 用于将HTTP请求参数绑定到模型对象上,或者将模型对象添加到视图中。 • 位置: 方法级别或方法参数级别。 • 配合使用: 通常与@RequestMapping、@GetMapping等注解一起使用。 • 示例:

@Controller
public class UserController {
    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("message", "Hello World!");
    }

    @GetMapping("/hello")
    public String handle() {
        return "index";
    }
}

** @ModelAttribute**将请求参数绑定到模型对象,或向模型中添加属性。方法级别或方法参数级别。用于在多个请求方法之间共享数据。

主要属性

value:指定模型属性的名称。

使用示例

@Controller
public class UserController {
    @ModelAttribute("user")
    public User getUser() {
        return new User(1, "John Doe");
    }

    @GetMapping("/user")
    public String showUser(Model model) {
        return "user";
    }
}

**12. @SessionAttributes**将模型属性存储到会话中,供多个请求使用。类级别。适用于需要在多个请求之间共享数据的场景。

主要属性

value:指定要存储到会话中的模型属性名称。

使用示例

@Controller
@SessionAttributes("user")
public class UserController {
    @GetMapping("/user")
    public String showUser(Model model) {
        model.addAttribute("user", new User(1, "John Doe"));
        return "user";
    }
}

RESTfult风格相关注解


**@RestController**标记一个类为 RESTful 风格的控制器,该注解是 @Controller@ResponseBody 的组合注解,适用于构建 RESTful API。

所有方法的返回值直接写入 HTTP 响应体(通常为 JSON/XML)。

@RestController
public class UserController {
    @GetMapping("/user")
    public User getUser() {
        return new User(1, "John Doe");
    }
}
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String handle() {
        return "Hello World!";
    }
}
@RestController
@RequestMapping("/persons")
class PersonController {

	@GetMapping("/{id}")
	public Person getPerson(@PathVariable Long id) {
		// ...
	}
	
	@PostMapping
	@ResponseStatus(HttpStatus.CREATED)
	public void add(@RequestBody Person person) {
		// ...
	}

}

@RequestParam

作用: 用于将HTTP请求参数绑定到控制器方法的参数上。 • 位置: 方法参数级别。 • 配合使用: 通常与@RequestMapping、@GetMapping等注解一起使用。 • 示例:

@RestController
public class HelloController {
  @GetMapping("/hello")
  public String handle(@RequestParam String name) {
      return "Hello, " + name;
  }
}

@PathVariable

作用: 用于将URL中的路径变量绑定到控制器方法的参数上。 • 位置: 方法参数级别。 • 配合使用: 通常与@RequestMapping、@GetMapping等注解一起使用。 • 示例:

@RestController
public class UserController {
  @GetMapping("/users/{id}")
  public String getUser(@PathVariable Long id) {
      return "User ID: " + id;
  }
}

@RequestBody

作用: 用于将HTTP请求体绑定到控制器方法的参数上,通常用于处理JSON或XML数据。 • 位置: 方法参数级别。 • 配合使用: 通常与@PostMapping、@PutMapping等注解一起使用。 • 示例:

@RestController
public class UserController {
    @PostMapping("/users")
    public String createUser(@RequestBody User user) {
        return "Created user: " + user.getName();
    }
}
  1. 结合路径变量和查询参数
示例 9: 使用 @GetMapping@PathVariable@RequestParam
@Controller
public class UserController {

    @GetMapping("/users/{id}/details")
    public String getUserDetails(
            @PathVariable Long id,
            @RequestParam String format,
            Model model) {
        model.addAttribute("userId", id);
        model.addAttribute("format", format);
        return "userDetails"; // 返回用户详情视图
    }
}

请求: GET /users/123/details?format=json响应: 渲染 userDetails.html 视图,并传递 userIdformat 属性。


@PutMapping, @DeleteMapping, @PatchMapping

作用: 分别用于将HTTP PUT、DELETE、PATCH请求映射到控制器的方法上。 • 位置: 方法级别。 • 配合使用: 可以与@RequestBody、@PathVariable等注解一起使用。 • 示例:

@RestController
public class UserController {
  @PutMapping("/users/{id}")
  public String updateUser(@PathVariable Long id, @RequestBody User user) {
      return "Updated user with id: " + id;
  }
}


**@PathVariable**将 URL 路径中的变量绑定到方法参数。方法参数级别。路径变量名称与方法参数名称一致时,可省略 value 属性。

主要属性value:指定路径变量名称(默认为参数名称)。

使用示例

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") int userId) {
        return new User(userId, "John Doe");
    }
}

异常处理

@ExceptionHandler

作用: 用于处理控制器方法抛出的异常。 • 位置: 方法级别。 • 配合使用: 通常与@Controller、@RestController等注解一起使用。 • 示例:

@Controller
public class UserController {
  @ExceptionHandler(Exception.class)
  public String handleException(Exception ex) {
      return "Error: " + ex.getMessage();
  }
}
@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable int id) {
        if (id == 0) {
            throw new IllegalArgumentException("Invalid user ID");
        }
        return new User(id, "John Doe");
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handleIllegalArgumentException(IllegalArgumentException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }
}

@ControllerAdvice

作用: 用于全局处理控制器抛出的异常。 适用于全局异常处理,避免在每个控制器中重复定义异常处理方法。 • 位置: 类级别。 • 配合使用: 通常与@ExceptionHandler注解一起使用。 • 示例:

@ControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(Exception.class)
  public String handleException(Exception ex) {
      return "Global Error: " + ex.getMessage();
  }
}
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, String> handleException(Exception ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }
}

跨域请求

@CrossOrigin

作用: 用于允许跨域请求。 • 位置: 类级别或方法级别。 • 配合使用: 通常与@RestController、@Controller等注解一起使用。 • 示例:

@RestController
@CrossOrigin(origins = "http://example.com")
public class HelloController {
  @GetMapping("/hello")
  public String handle() {
      return "Hello World!";
  }
}

这些注解是Spring Web MVC框架中常用的核心注解,开发者可以根据具体需求灵活使用它们来构建Web应用程序。

四、处理请求映射

在Spring MVC框架中,处理请求映射的核心模块主要由以下几个接口和类组成:

1. 处理请求映射的核心模块

HandlerMapping 接口

作用: HandlerMapping 是Spring MVC中用于将请求映射到处理程序(Handler)的核心接口。它负责根据请求的URL、请求方法、请求头等信息,找到合适的处理程序(通常是Controller中的方法)。 • 实现类: Spring MVC提供了多个HandlerMapping的实现类,如: ◦ RequestMappingHandlerMapping: 用于处理基于注解的请求映射(如@RequestMapping)。 ◦ BeanNameUrlHandlerMapping: 根据Bean的名称进行URL映射。 ◦ SimpleUrlHandlerMapping: 通过配置URL模式与处理程序的映射关系来处理请求。

HandlerAdapter 接口

作用: HandlerAdapter 是用于执行处理程序的接口。它负责调用处理程序的方法,并将方法返回的结果转换为ModelAndView对象。 • 实现类: 常用的实现类有: ◦ RequestMappingHandlerAdapter: 用于处理基于注解的Controller方法。 ◦ SimpleControllerHandlerAdapter: 用于处理实现了Controller接口的类。

RequestMappingInfo

作用: RequestMappingInfo 是一个封装了请求映射信息的类,包含了URL模式、请求方法、请求头、参数等条件。它通常由RequestMappingHandlerMapping使用,用于匹配请求。

HandlerMethod

作用: HandlerMethod 是Spring MVC中用于封装Controller方法的类。它包含了方法的所有元数据,如方法对象、参数类型、返回类型等。

2. 路径映射处理机制

Spring MVC的路径映射处理机制主要通过RequestMappingHandlerMapping实现,具体步骤如下:

  1. 初始化阶段: • 在应用启动时,Spring MVC会扫描所有带有@Controller@RestController注解的类,并解析其中的@RequestMapping注解。 • 每个@RequestMapping注解会被转换为一个RequestMappingInfo对象,包含URL模式、请求方法、请求头、参数等条件。 • 这些RequestMappingInfo对象与对应的HandlerMethod会被注册到RequestMappingHandlerMapping中。

  2. 请求匹配阶段: • 当一个请求到达时,DispatcherServlet会调用HandlerMappinggetHandler方法。 • RequestMappingHandlerMapping会根据请求的URL、请求方法、请求头等信息,查找与之匹配的RequestMappingInfo。 • 如果找到匹配的RequestMappingInfo,则会返回对应的HandlerMethod

  3. 请求处理阶段: • DispatcherServlet会调用HandlerAdapterhandle方法,将请求交给HandlerMethod处理。 • RequestMappingHandlerAdapter会调用HandlerMethod对应的方法,并将方法返回的结果转换为ModelAndView对象。

以下是Spring MVC路径映射处理机制的流程图,展示了从请求到达服务器到最终调用Controller方法的完整过程:

+-------------------+
|   HTTP Request    |
+-------------------+
          |
          v
+-------------------+
| DispatcherServlet |
+-------------------+
          |
          v
+-------------------+       +-------------------+
| HandlerMapping    | <---->| RequestMappingInfo|
| (e.g., RequestMap |       | (URL, Method,     |
| pingHandlerMapping)|       | Headers, Params)  |
+-------------------+       +-------------------+
          |
          v
+-------------------+
| HandlerAdapter    | <---->| HandlerMethod     |
| (e.g., RequestMap |       | (Controller Method|
| pingHandlerAdapter)|       | with Metadata)    |
+-------------------+
          |
          v
+-------------------+
| Controller Method |
| (Business Logic)  |
+-------------------+
          |
          v
+-------------------+
|  Return Result    |
| (e.g., ModelAndView, |
|  JSON, String)     |
+-------------------+
          |
          v
+-------------------+
|  HTTP Response    |
+-------------------+

详细步骤说明:

  1. HTTP Request: • 客户端发送HTTP请求到服务器。

  2. DispatcherServlet: • DispatcherServlet是Spring MVC的核心组件,负责接收所有请求,并将其分发给合适的处理程序。

  3. HandlerMapping: • DispatcherServlet调用HandlerMapping(如RequestMappingHandlerMapping)来查找与请求匹配的处理程序。 • HandlerMapping根据请求的URL、请求方法、请求头等信息,查找对应的RequestMappingInfo对象。

  4. RequestMappingInfo: • RequestMappingInfo封装了请求的映射条件(如URL模式、请求方法、请求头、参数等)。 • 如果找到匹配的RequestMappingInfo,则返回对应的HandlerMethod

  5. HandlerAdapter: • DispatcherServlet调用HandlerAdapter(如RequestMappingHandlerAdapter)来执行处理程序。 • HandlerAdapter负责调用HandlerMethod对应的方法,并将方法返回的结果转换为ModelAndView对象或其他响应类型。

  6. HandlerMethod: • HandlerMethod封装了Controller方法的元数据(如方法对象、参数类型、返回类型等)。 • HandlerAdapter调用HandlerMethod对应的方法,执行业务逻辑。

  7. Controller Method: • Controller方法处理业务逻辑,并返回结果(如ModelAndView、JSON、字符串等)。

  8. Return Result: • Controller方法返回的结果会被HandlerAdapter处理,并最终由DispatcherServlet转换为HTTP响应。

  9. HTTP Response: • 服务器将HTTP响应返回给客户端。

关键点:

HandlerMapping负责将请求映射到处理程序。 • HandlerAdapter负责执行处理程序并处理结果。 • RequestMappingInfoHandlerMethod是路径映射和处理的核心数据结构。

3. URL映射(路径映射)的不同形式

在Spring MVC框架中,路径映射(URL映射)可以通过多种形式进行定义,支持灵活且强大的路径匹配规则。

Spring MVC的路径映射支持精确匹配、路径变量、通配符、正则表达式、多路径、查询参数等多种方式。通过结合HTTP请求方法、请求头、内容类型等条件,可以更精确地控制请求的匹配规则。

以下是Spring MVC中常见的路径形式:

1. 精确路径匹配

形式: 直接指定完整的路径。 • 示例:

 ```java
 @GetMapping("/user/info")
 public String getUserInfo() {
     return "user-info";
 }
 ````

说明: 只有当请求的URL完全匹配/user/info时,才会调用该方法。

2. 通配符路径

形式: 使用*匹配单层路径,使用**匹配多层路径。 • 示例:

 ```java
 @GetMapping("/user/*/info")
 public String getUserInfoByWildcard() {
     return "user-info-wildcard";
 }
 ```
 @GetMapping("/user/**")
 public String getAllUserPaths() {
     return "all-user-paths";
 } 

说明: ◦ /user/*/info匹配/user/123/info,但不匹配/user/123/456/info。 ◦ /user/**匹配/user/123/user/123/456等多层路径。

3. 多路径映射

形式: 在一个方法中映射多个路径。 • 示例:

 ```java
 @GetMapping({"/user/info", "/user/details"})
 public String getUserInfoOrDetails() {
     return "user-info-or-details";
 }
 ```

说明: 请求的URL可以是/user/info/user/details,都会调用该方法。


4. 路径前缀

形式: 在类级别使用@RequestMapping定义路径前缀,在方法级别定义具体路径。 • 示例:

 ```java
 @Controller
 @RequestMapping("/user")
 public class UserController {
     @GetMapping("/info")
     public String getUserInfo() {
         return "user-info";
     }
 }
 ```

说明: 请求的URL是/user/info

5. 默认路径

形式: 使用/或空字符串作为默认路径。 • 示例:

 ```java
 @GetMapping("/")
 public String getHomePage() {
     return "home";
 }
 ```

说明: 请求的URL是根路径/

6. 带请求头信息的路径

形式: 结合请求头信息定义路径。 • 示例:

@GetMapping(value = "/user", headers = "X-Custom-Header=123")
public String getUserWithHeader() {
    return "user-with-header";
}

说明: 只有请求头中包含X-Custom-Header=123时,才会调用该方法。

7. 带内容类型的路径

形式: 结合请求的内容类型(Content-Type)定义路径。 • 示例: java @PostMapping(value = "/user", consumes = "application/json") public String createUserWithJson(@RequestBody User user) { return "user-created-json"; } 说明: 只有请求的内容类型为application/json时,才会调用该方法。

8. 带响应类型的路径

形式: 结合响应的内容类型(Accept)定义路径。 • 示例: java @GetMapping(value = "/user", produces = "application/json") public User getUserAsJson() { return new User("John", 30); } 说明: 只有客户端接受application/json时,才会调用该方法。

9. 路径中包含查询参数

形式: 结合查询参数来定义路径。 • 示例:

 ```java

@GetMapping("/user") public String getUserByName(@RequestParam String name) { return "user-name: " + name; } ```

@GetMapping("/example")
public String example(@RequestParam("name") String name) {
    return "Hello, " + name;
}

说明: 请求的URL可以是/user?name=Johnname参数会被绑定到方法参数。

10. 路径变量(Path Variables)

形式: 使用{变量名}定义路径变量,并通过@PathVariable注解获取。 • 示例:

 ```java

@GetMapping("/user/{id}") public String getUserById(@PathVariable Long id) { return "user-id: " + id; } ```

说明: 请求的URL可以是/user/123,其中123会被绑定到id参数。

11. 正则表达式路径

形式: 在路径变量中使用正则表达式限制匹配规则。 • 示例:

 ```java

@GetMapping("/user/{id:\d+}") public String getUserByIdWithRegex(@PathVariable String id) { return "user-id-regex: " + id; } ```

说明: 只有id为数字时才会匹配,例如/user/123/user/abc不会匹配。


4. RESTful风格的路径

RESTful风格的路径是一种基于HTTP方法的URL设计规范,旨在通过URL和HTTP方法(如GET、POST、PUT、DELETE等)清晰地表达资源的操作。Spring MVC框架天然支持RESTful风格的路径设计。

RESTful风格的路径是通过资源、HTTP方法和路径变量的结合,构建出语义清晰、易于维护的API。

RESTful路径的典型设计
操作类型URL 格式HTTP 方法示例说明
获取资源/usersGETGET /users获取所有用户资源。
/users/{id}GETGET /users/1获取 ID 为 1 的用户资源。
创建资源/usersPOSTPOST /users创建新用户,请求体包含用户数据。
更新资源/users/{id}PUTPUT /users/1更新 ID 为 1 的用户资源,请求体包含更新数据。
删除资源/users/{id}DELETEDELETE /users/1删除 ID 为 1 的用户资源。
嵌套资源/users/{userId}/ordersGETGET /users/1/orders获取用户 ID 为 1 的所有订单。
/users/{userId}/orders/{orderId}GETGET /users/1/orders/100获取用户 ID 为 1 的订单 ID 为 100 的订单。

通过上表可以清晰地看到 RESTful 风格的路径设计规则,包括:

  • 资源为核心:URL 表示资源(如 /users/orders)。
  • HTTP 方法表示操作:使用 GETPOSTPUTDELETE 等 HTTP 方法表示对资源的操作。
  • 路径变量表示资源标识:使用 {id} 等路径变量表示特定资源。
  • 嵌套资源:通过 URL 层次化表示资源之间的关系(如 /users/{userId}/orders)。

这种设计方式使得 RESTful API 更加语义化、清晰且易于维护。


RESTful风格路径的设计原则
  1. 资源为核心: • URL表示资源,而不是操作。资源通常使用名词表示,例如/users/orders
  2. HTTP方法表示操作: • 使用HTTP方法(GET、POST、PUT、DELETE等)表示对资源的操作。 ◦ GET:获取资源。 ◦ POST:创建资源。 ◦ PUT:更新资源。 ◦ DELETE:删除资源。
  3. 路径变量表示资源标识: • 使用路径变量(如/users/{id})表示特定资源。
  4. URL层次化: • 使用URL的层次结构表示资源之间的关系,例如/users/{userId}/orders表示某个用户的订单。

注意事项

  1. 避免动词: • URL中应避免使用动词(如/getUsers),而应使用名词表示资源。
  2. 使用复数形式: • 资源名称通常使用复数形式(如/users而不是/user)。
  3. 版本控制: • 如果API需要版本控制,可以在URL或请求头中引入版本号,例如/v1/users
  4. 状态码: • 使用HTTP状态码(如200、201、404)表示操作结果,而不是在响应体中返回状态信息。

Spring MVC中的RESTful路径实现

在Spring MVC中,可以通过@RequestMapping及其衍生注解(如@GetMapping@PostMapping@PutMapping@DeleteMapping)来实现RESTful风格的路径。

示例代码

@RestController
@RequestMapping("/users")
public class UserController {

    // 获取所有用户
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    // 获取单个用户
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    // 创建用户
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }

    // 更新用户
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        return userService.updateUser(id, user);
    }

    // 删除用户
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

RESTful路径的优点
  1. 语义清晰: • URL和HTTP方法结合,明确表达操作意图。
  2. 易于扩展: • 通过URL层次化,可以轻松扩展资源关系。
  3. 标准化: • 符合RESTful设计规范,易于理解和维护。
  4. 与HTTP协议紧密结合: • 充分利用HTTP方法的语义,简化设计。

五、返回响应数据

在Spring MVC框架中,Model 是一个核心概念,用于在控制器(Controller)和视图(View)之间传递数据。

1. Model 的作用

Model 是一个接口,用于存储需要在视图中显示的数据。它的主要作用包括: • 传递数据:将控制器处理后的数据传递给视图。 • 动态渲染:视图可以根据 Model 中的数据动态生成内容。 • 解耦:控制器和视图之间通过 Model 进行数据交互,实现松耦合。

Model 是控制器和视图之间的桥梁,用于传递数据。 • Model 是一个键值对容器,可以存储任意类型的数据。 • Model 与视图技术(如 Thymeleaf、JSP)无缝集成,实现动态内容渲染。 • Model 的使用简化了控制器和视图之间的数据交互,提高了代码的可维护性和可读性。

在Spring MVC中,Model 的默认实现是 BindingAwareModelMap,它是一个 Map 的实现类,用于存储键值对。在控制器方法中,Spring 会自动将 Model 对象注入到方法参数中。

2. Model 和 Attribute 的关系

Model

Model 是一个接口,用于在控制器(Controller)和视图(View)之间传递数据。 • 它是一个数据容器,可以存储多个键值对(即多个属性)。 • 在控制器方法中,Model 通常作为参数传入,用于添加需要在视图中显示的数据。

Attribute

Attribute 是指存储在 Model 中的单个键值对。 • 每个 Attribute 包含一个名称(key)和一个值(value),值可以是任意类型的对象。 • AttributeModel 的组成部分,Model 可以包含多个 Attribute

Model 和 Attribute 的异同

特性ModelAttribute
定义一个数据容器,用于存储多个键值对。单个键值对,是 Model 的组成部分。
作用在控制器和视图之间传递数据。表示 Model 中的一个具体数据项。
作用域当前请求。作用域与 Model 一致,为当前请求。
生命周期与当前请求绑定,请求结束后销毁。Model 的生命周期一致。

Model 是一个容器,Attribute 是容器中的内容。 • 一个 Model 可以包含多个 Attribute,每个 Attribute 是一个键值对。 • Model 的作用是管理 Attribute,并将它们传递给视图。

3. Model 的核心方法

Model 接口提供了以下常用方法: • addAttribute(String name, Object value):向 Model 中添加一个属性,属性名称为 name,属性值为 value。 • addAttribute(Object value):向 Model 中添加一个属性,属性名称由对象的类名决定(首字母小写)。 • addAllAttributes(Map<String, ?> attributes):将 Map 中的所有键值对添加到 Model 中。 • mergeAttributes(Map<String, ?> attributes):将 Map 中的键值对合并到 Model 中,如果 Model 中已存在相同的键,则保留 Model 中的值。 • containsAttribute(String name):检查 Model 中是否包含指定名称的属性。

4. Model 的使用示例

示例 1: 使用 Model 传递数据

@Controller
public class HomeController {

    @GetMapping("/home")
    public String home(Model model) {
        // 向 Model 中添加 Attribute
        model.addAttribute("message", "Welcome to the Home Page!"); //传递简单数据
        model.addAttribute("user", new User("John", 30));	// 传递对象
        List<Product> products = Arrays.asList(
            new Product("Laptop", 1000),
            new Product("Phone", 500)
        );
        model.addAttribute("products", products);	// 传递集合

        return "home"; // 返回视图名称
    }
}

说明: • Model 是一个容器,存储了三个 Attributemessageuserproducts。 • 这些 Attribute 的作用域为当前请求。

示例 2: 在视图中访问Model中的 Attribute

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home Page</title>
</head>
<body>
    <h1 th:text="${message}">Default Message</h1>
    <p>User: <span th:text="${user.name}">Guest</span>, Age: <span th:text="${user.age}">0</span></p>
    <p>Products: <span th:text="${products['Laptop']}">Product</span>,
        <span th:text="${products.Phone}">Product</span></p>
</body>
</html>

说明: • 视图通过 ${message}${user.name}${products['Laptop']} 访问 Model 中的 Attribute

5. 作用域范围

Model 的作用域是当前请求,即它的生命周期与一次HTTP请求绑定。 • 当请求处理完成后,Model 中的数据会被销毁,不会在多个请求之间共享。 • 如果需要跨请求共享数据,可以使用 @SessionAttributes@SessionAttribute

除了 Model,Spring MVC 还支持其他作用域的数据传递方式: • @SessionAttributes:将 Model 中的数据存储到会话(Session)中,作用域为多个请求。 • @RequestAttribute:从请求中获取属性,作用域为当前请求。 • @SessionAttribute:从会话中获取属性,作用域为多个请求。

示例: 使用 @SessionAttributes

@Controller
@SessionAttributes("user") // 将 "user" 存储到 Session 中
public class UserController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("John", 30)); // 添加到 Model 和 Session
        return "user";
    }
}

说明: • user 属性不仅存储在 Model 中,还会存储在会话(Session)中,作用域为多个请求。

6. @ModelAttribute

@ModelAttribute:用于将方法返回值或请求参数绑定到 Model 中。

@Controller
public class UserController {

    @ModelAttribute("user")
    public User getUser() {
        return new User("Guest", 0);
    }

    @GetMapping("/user")
    public String showUser() {
        return "user"; // 返回视图名称
    }
}

说明: @ModelAttribute 方法会在每个请求处理之前执行,并将返回值添加到 Model 中。

7. **Model 与 ModelAndView **

Model:只是一个数据容器,用于存储需要在视图中显示的数据。 • ModelAndView:包含 Model 和视图名称(或视图对象),可以同时返回数据和视图。

示例:使用 ModelAndView

@Controller
public class HomeController {

    @GetMapping("/home")
    public ModelAndView home() {
        ModelAndView modelAndView = new ModelAndView("home"); // 设置视图名称
        modelAndView.addObject("message", "Welcome to the Home Page!"); // 添加数据
        return modelAndView;
    }
}

六、数据绑定与验证

数据绑定: Spring MVC自动将请求参数绑定到Java对象(如表单数据绑定到POJO)。 • 验证: 支持JSR-303/JSR-380 Bean Validation,可以通过注解对模型数据进行验证。

在Spring MVC中,数据绑定验证是两个非常重要的功能,它们帮助开发者处理用户输入的数据,并确保数据的有效性和一致性。以下是关于数据绑定和验证的详细说明:


1 数据绑定(Data Binding)

数据绑定是指将HTTP请求中的参数(如表单数据、URL参数、JSON等)自动绑定到Java对象(如POJO、DTO等)的过程。Spring MVC提供了强大的数据绑定机制,简化了开发者处理用户输入的工作。

1.1 数据绑定的实现

自动绑定:Spring MVC会自动将请求参数绑定到Controller方法的参数或模型对象中。 • 例如,表单中的<input name="username">会自动绑定到Java对象的username属性。 • 绑定注解: • @RequestParam:绑定单个请求参数到方法参数。 java public String getUser(@RequestParam("id") Long userId) { // ... } @PathVariable:绑定URL路径中的变量到方法参数。 java public String getUser(@PathVariable("id") Long userId) { // ... } @ModelAttribute:绑定表单数据到模型对象。 java public String saveUser(@ModelAttribute User user) { // ... } @RequestBody:绑定请求体(如JSON)到Java对象。 java public String saveUser(@RequestBody User user) { // ... }

1.2 数据绑定的流程

  1. 接收请求:DispatcherServlet接收HTTP请求。
  2. 参数解析:Spring MVC根据请求参数的类型和名称,调用适当的DataBinder进行绑定。
  3. 类型转换:如果请求参数的类型与目标类型不匹配,Spring会尝试进行类型转换(如将字符串转换为整数)。
  4. 绑定到对象:将解析后的参数绑定到目标对象(如POJO、DTO等)。

1.3 自定义数据绑定

自定义类型转换器:通过实现ConverterFormatter接口,可以自定义类型转换逻辑。

public class StringToDateConverter implements Converter<String, Date> {
  @Override
  public Date convert(String source) {
      // 自定义转换逻辑
  }
}

注册自定义转换器:在配置类中注册自定义转换器。

@Configuration
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addFormatters(FormatterRegistry registry) {
      registry.addConverter(new StringToDateConverter());
  }
}

2. 验证(Validation)

验证是确保用户输入的数据符合业务规则的过程。Spring MVC支持JSR-303/JSR-380 Bean Validation标准,可以通过注解对模型数据进行验证。

2.1 验证的实现

验证注解:在模型对象的属性上添加验证注解。

public class User {
    @NotNull
    private String username;

    @Size(min = 6, max = 20)
    private String password;

    @Email
    private String email;
}

常用注解包括: • @NotNull:字段不能为null。 • @NotEmpty:字段不能为空(适用于字符串、集合等)。 • @Size:字段的长度或大小必须在指定范围内。 • @Min@Max:字段的数值必须在指定范围内。 • @Pattern:字段必须匹配指定的正则表达式。 • @Email:字段必须是有效的电子邮件地址。

触发验证:在Controller方法中使用@Valid@Validated注解触发验证。

public String saveUser(@Valid @ModelAttribute User user, BindingResult result) {
  if (result.hasErrors()) {
      // 处理验证错误
  }
  // 保存用户
}

2.2 验证结果处理

BindingResult:验证结果会存储在BindingResult对象中,开发者可以通过它获取错误信息。

if (result.hasErrors()) {
  List<FieldError> errors = result.getFieldErrors();
  for (FieldError error : errors) {
      System.out.println(error.getField() + ": " + error.getDefaultMessage());
  }
}

2.3 自定义验证

自定义验证注解:通过实现ConstraintValidator接口,可以创建自定义验证注解。

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CustomValidator.class)
public @interface CustomValidation {
    String message() default "Invalid value";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class CustomValidator implements ConstraintValidator<CustomValidation, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 自定义验证逻辑
        return value != null && value.startsWith("custom");
    }
}

使用自定义注解:在模型对象中使用自定义注解。

public class User {
  @CustomValidation
  private String customField;
}

2.4 分组验证

分组验证:通过定义验证组,可以在不同场景下应用不同的验证规则。

public interface GroupA {}
public interface GroupB {}

public class User {
    @NotNull(groups = GroupA.class)
    private String username;

    @Size(min = 6, max = 20, groups = GroupB.class)
    private String password;
}

触发分组验证:在Controller方法中指定验证组。

public String saveUser(@Validated(GroupA.class) @ModelAttribute User user, BindingResult result) {
  // ...
}

3. 数据绑定与验证的结合

在Spring MVC中,数据绑定和验证通常是结合使用的:

  1. 绑定数据:将请求参数绑定到模型对象。
  2. 验证数据:对绑定后的模型对象进行验证。
  3. 处理结果:根据验证结果决定后续操作(如返回错误信息或保存数据)。

示例:

public String saveUser(@Valid @ModelAttribute User user, BindingResult result) {
    if (result.hasErrors()) {
        // 返回错误信息
        return "errorPage";
    }
    // 保存用户
    userService.save(user);
    return "successPage";
}


4. 自定义类型转换的适用场景

自定义类型转换器在Spring MVC中用于处理默认类型转换器无法满足需求的场景。以下是一些常见的需要使用自定义类型转换器的场景:

4.1 复杂类型转换

当需要将请求参数转换为复杂类型(如自定义对象、枚举、日期格式等)时,默认的类型转换器可能无法满足需求。

示例场景:

• 将字符串转换为自定义的Date格式:

public class StringToDateConverter implements Converter<String, Date> {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

    @Override
    public Date convert(String source) {
        try {
            return DATE_FORMAT.parse(source);
        } catch (ParseException e) {
            throw new IllegalArgumentException("Invalid date format. Expected format: yyyy-MM-dd");
        }
    }
}

4.2 自定义格式的请求参数

当请求参数的格式与默认格式不一致时,需要自定义转换逻辑。

示例场景:

• 将逗号分隔的字符串转换为列表:

public class StringToListConverter implements Converter<String, List<String>> {
  @Override
  public List<String> convert(String source) {
      return Arrays.asList(source.split(","));
  }
}

4.3 处理特殊字符或编码

当请求参数中包含特殊字符或需要特定编码处理时,默认转换器可能无法正确处理。

示例场景:

• 将URL编码的字符串解码:

public class UrlDecodeConverter implements Converter<String, String> {
  @Override
  public String convert(String source) {
      try {
          return URLDecoder.decode(source, StandardCharsets.UTF_8.toString());
      } catch (UnsupportedEncodingException e) {
          throw new IllegalArgumentException("Failed to decode URL parameter");
      }
  }
}

4.4 枚举类型的自定义映射

当需要将请求参数映射到枚举类型,但参数值与枚举名称不一致时,需要自定义转换逻辑。

示例场景:

• 将字符串映射到自定义枚举:

public enum UserRole {
    ADMIN("A"), USER("U"), GUEST("G");

    private final String code;

    UserRole(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

    public static UserRole fromCode(String code) {
        for (UserRole role : UserRole.values()) {
            if (role.getCode().equals(code)) {
                return role;
            }
        }
        throw new IllegalArgumentException("Invalid role code: " + code);
    }
}

public class StringToUserRoleConverter implements Converter<String, UserRole> {
    @Override
    public UserRole convert(String source) {
        return UserRole.fromCode(source);
    }
}

4.5 处理多语言或本地化数据

当请求参数需要根据用户的语言或地区进行特殊处理时,默认转换器可能无法满足需求。

示例场景:

• 将本地化的日期字符串转换为Date对象:

public class LocalizedDateConverter implements Converter<String, Date> {
    private final Locale locale;

    public LocalizedDateConverter(Locale locale) {
        this.locale = locale;
    }

    @Override
    public Date convert(String source) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy", locale);
        try {
            return dateFormat.parse(source);
        } catch (ParseException e) {
            throw new IllegalArgumentException("Invalid date format for locale: " + locale);
        }
    }
}

4.6 处理复杂对象

当请求参数需要映射到复杂的嵌套对象时,默认转换器无法处理。

示例场景:

• 将JSON字符串转换为嵌套对象:

public class JsonToUserConverter implements Converter<String, User> {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public User convert(String source) {
        try {
            return objectMapper.readValue(source, User.class);
        } catch (IOException e) {
            throw new IllegalArgumentException("Invalid JSON format");
        }
    }
}

4.7 处理默认转换器不支持的类型

当请求参数的类型是默认转换器不支持的类型时,需要自定义转换器。

示例场景:

• 将字符串转换为自定义的Money类型:

public class StringToMoneyConverter implements Converter<String, Money> {
  @Override
  public Money convert(String source) {
      String[] parts = source.split(" ");
      if (parts.length != 2) {
          throw new IllegalArgumentException("Invalid money format. Expected format: '100 USD'");
      }
      return new Money(Double.parseDouble(parts[0]), parts[1]);
  }
}

4.8 处理多值参数

当请求参数包含多个值,且需要将其转换为特定类型时,默认转换器可能无法处理。

示例场景:

• 将多个复选框的值转换为布尔列表:

public class StringToBooleanListConverter implements Converter<String[], List<Boolean>> {
  @Override
  public List<Boolean> convert(String[] source) {
      return Arrays.stream(source)
                   .map(Boolean::parseBoolean)
                   .collect(Collectors.toList());
  }
}

自定义类型转换器在以下场景中非常有用:

  1. 处理复杂类型或自定义格式的请求参数。
  2. 处理特殊字符、编码或本地化数据。
  3. 映射枚举类型或嵌套对象。
  4. 处理默认转换器不支持的类型或多值参数。

通过自定义类型转换器,开发者可以灵活地处理各种复杂的请求参数,确保数据绑定过程符合业务需求。

总结

数据绑定:Spring MVC自动将请求参数绑定到Java对象,支持自定义类型转换和绑定规则。 • 验证:通过JSR-303/JSR-380标准注解对模型数据进行验证,支持自定义验证和分组验证。 • 结合使用:数据绑定和验证通常结合使用,确保用户输入的数据有效且符合业务规则。

通过合理使用数据绑定和验证,开发者可以简化代码、提高开发效率,并确保应用程序的健壮性和安全性。

七、视图技术

• Spring MVC支持多种视图技术,包括JSP、Thymeleaf、FreeMarker、Velocity等。 • 通过配置ViewResolver,可以灵活选择和使用不同的视图技术。

配置视图的一般步骤:

  1. 在pom.xml中添加依赖。
  2. 在 Spring(或SpringBoot)配置文件中配置视图解析器。
  3. 放置视图文件,通常放在 /WEB-INF/views/ 目录下。
  4. 在控制器方法中返回视图名称,Spring MVC 会自动解析。

1. 各种视图技术一览

技术类型Spring MVC 支持Spring Boot 支持
Thymeleaf✅(默认支持,推荐)
FreeMarker✅(默认支持,适用复杂场景)
JSP❌(需手动配置,不推荐)
Velocity❌(需手动配置,不推荐)
Groovy✅(默认支持)
Mustache✅(默认支持)
JSON/XML✅(默认支持,适用API开发)

SpringBoot推荐视图技术:Thymeleaf

1. **与 Spring 集成紧密**:Thymeleaf 是 Spring 官方推荐的视图技术,天然支持 Spring 的标签和表达式。
2. **自然模板**:Thymeleaf 模板可以在浏览器中直接预览,无需启动应用。
3. **现代化**:支持 HTML5,语法简洁,适合现代 Web 开发。
4. **自动配置**:Spring Boot 默认支持 Thymeleaf,无需额外配置。
5. **社区活跃**:Thymeleaf 社区活跃,文档完善,易于学习和使用。

其他推荐技术

FreeMarker: • 适合需要复杂模板逻辑的场景。 • 语法比 Thymeleaf 更灵活,但学习曲线稍高。 • JSON/XML: • 适合 RESTful API 开发,无需前端模板引擎。

不推荐技术

JSP: • Spring Boot 对 JSP 支持较差,部署复杂,不推荐使用。 • Velocity: • 较老的模板引擎,语法简单,功能较弱,目前逐渐被 FreeMarker 和 Thymeleaf 取代。

其他技术

Groovy Markup Templates: • 基于 Groovy 的模板引擎,支持动态生成 HTML、XML 等。 • 适合熟悉 Groovy 的开发者。Spring Boot 默认支持,只需添加 spring-boot-starter-groovy-templates 依赖。

Mustache: • 轻量级模板引擎,语法简单,支持多种语言。 • 适合简单的模板渲染场景。Spring Boot 默认支持,只需添加 spring-boot-starter-mustache 依赖。

2. JSP 视图技术

传统的 Java Web 视图技术,适合简单的动态页面渲染。

需要在 WEB-INF 目录下放置 JSP 文件。

Spring Boot 默认不支持 JSP,但可以通过手动配置启用。不推荐在 Spring Boot 中使用 JSP。

XML 配置(Spring 项目)

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>

Java 配置(Spring 项目)

@Configuration
public class JspConfig {
    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

Spring Boot 项目配置

Spring Boot 默认不支持 JSP,但可以通过以下方式启用:

  1. application.properties 中配置:
    spring.mvc.view.prefix=/WEB-INF/views/
    spring.mvc.view.suffix=.jsp
    
  2. 确保 JSP 文件放在 src/main/webapp/WEB-INF/views/ 目录下。

3. Thymeleaf 视图技术

现代模板引擎,支持 HTML5,天然支持与 Spring 集成。 • 强调自然模板(Natural Templates),可以在浏览器中直接预览。

配置 ThymeleafViewResolverSpringTemplateEngine,将视图名称解析为 Thymeleaf 模板。

• Spring Boot 的默认推荐视图技术。 • 自动配置支持,只需添加 spring-boot-starter-thymeleaf 依赖。

XML 配置(Spring 项目)

<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".html" />
    <property name="templateMode" value="HTML" />
    <property name="characterEncoding" value="UTF-8" />
</bean>

<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver" />
</bean>

<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine" />
    <property name="characterEncoding" value="UTF-8" />
</bean>

Java 配置(Spring 项目)

@Configuration
public class ThymeleafConfig {
    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        return templateEngine;
    }

    @Bean
    public ThymeleafViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        resolver.setCharacterEncoding("UTF-8");
        return resolver;
    }

    private ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setCharacterEncoding("UTF-8");
        return resolver;
    }
}

Spring Boot 项目配置

Spring Boot 默认支持 Thymeleaf,只需添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

默认模板路径为 src/main/resources/templates/,可通过 application.properties 配置:

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

4. FreeMarker 视图技术

• 功能强大的模板引擎,支持动态生成 HTML、XML、JSON 等。配置 FreeMarkerViewResolverFreeMarkerConfigurer,将视图名称解析为 FreeMarker 模板。 • 语法简洁,适合复杂的模板渲染场景。• Spring Boot 默认支持,只需添加 spring-boot-starter-freemarker 依赖。

XML 配置(Spring 项目)

<bean id="freeMarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/views/" />
</bean>

<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="suffix" value=".ftl" />
    <property name="contentType" value="text/html;charset=UTF-8" />
</bean>

Java 配置(Spring 项目)

@Configuration
public class FreeMarkerConfig {
    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/WEB-INF/views/");
        return configurer;
    }

    @Bean
    public FreeMarkerViewResolver freeMarkerViewResolver() {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
        resolver.setSuffix(".ftl");
        resolver.setContentType("text/html;charset=UTF-8");
        return resolver;
    }
}

Spring Boot 项目配置

Spring Boot 默认支持 FreeMarker,只需添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

默认模板路径为 src/main/resources/templates/,可通过 application.properties 配置:

spring.freemarker.prefix=classpath:/templates/
spring.freemarker.suffix=.ftl

**5. JSON/XML **

• 通过 @Controller + @ResponseBody@RestController 直接返回 JSON 或 XML 数据。 • 常用于 RESTful API 开发。不需要视图文件,直接通过控制器方法返回对象。

Spring Boot 默认支持。

1. 返回 JSON/XML 数据的适用场景

RESTful API 项目: • 前后端分离架构,前端通过 API 与后端交互。 • 后端通常返回 JSON/XML 格式的数据,而不是 HTML 页面。 • 微服务架构: • 服务之间通过 API 通信,通常使用 JSON/XML 作为数据格式。 • 移动端或第三方集成: • 移动端应用或第三方系统通常需要 JSON/XML 格式的错误信息。

2. 实现方式

1. 使用 @RestController 返回 JSON/XML 数据

@RestController@Controller@ResponseBody 的组合注解,直接返回数据而不是视图。

示例 1:返回 JSON 数据

@RestController
public class UserController {
    @GetMapping("/user")
    public User getUser() {
        return new User(1, "John Doe");
    }
}
@RestController
public class UserController {
    @RequestMapping(value = "/user", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public User getUser() {
        return new User(1, "John Doe");
    }
}

示例 2:返回 XML 数据

@RestController
public class UserController {
    @GetMapping(value = "/user", produces = MediaType.APPLICATION_XML_VALUE)
    public User getUser() {
        return new User(1, "John Doe");
    }
}
@RestController
public class UserController {
    @RequestMapping(value = "/user", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
    public User getUser() {
        return new User(1, "John Doe");
    }
}
2. 使用 @Controller + @ResponseBody 返回 JSON/XML 数据

@Controller 用于定义控制器,@ResponseBody 用于将返回值直接写入 HTTP 响应体。

示例 1:返回 JSON 数据

@Controller
public class UserController {
    @GetMapping("/user")
    @ResponseBody
    public User getUser() {
        return new User(1, "John Doe");
    }
}

示例 2:返回 XML 数据

@Controller
public class UserController {
    @GetMapping(value = "/user", produces = MediaType.APPLICATION_XML_VALUE)
    @ResponseBody
    public User getUser() {
        return new User(1, "John Doe");
    }
}
3. 使用 @ResponseStatus 返回 JSON/XML 数据

@ResponseStatus 可以指定 HTTP 响应的状态码

示例 1:返回 JSON 数据

@RestController
public class UserController {
    @GetMapping("/user")
    @ResponseStatus(HttpStatus.OK)
    public User getUser() {
        return new User(1, "John Doe");
    }
}

示例 2:返回 XML 数据

@RestController
public class UserController {
    @GetMapping(value = "/user", produces = MediaType.APPLICATION_XML_VALUE)
    @ResponseStatus(HttpStatus.OK)
    public User getUser() {
        return new User(1, "John Doe");
    }
}

4. 使用 ResponseEntity 返回 JSON/XML 数据

ResponseEntity 可以灵活地控制 HTTP 响应的状态码、头部和内容

示例 1:返回 JSON 数据

@RestController
public class UserController {
    @GetMapping("/user")
    public ResponseEntity<User> getUser() {
        User user = new User(1, "John Doe");
        return ResponseEntity.ok(user); // 返回 200 状态码和 JSON 数据
    }
}

示例 2:返回 XML 数据

@RestController
public class UserController {
    @GetMapping(value = "/user", produces = MediaType.APPLICATION_XML_VALUE)
    public ResponseEntity<User> getUser() {
        User user = new User(1, "John Doe");
        return ResponseEntity.ok(user); // 返回 200 状态码和 XML 数据
    }
}

5. 使用 @ExceptionHandler 返回 JSON/XML 错误信息

@ExceptionHandler 用于处理控制器中的异常,并返回 JSON/XML 格式的错误信息。

示例 1:返回 JSON 错误信息

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable int id) {
        if (id == 0) {
            throw new IllegalArgumentException("Invalid user ID");
        }
        return new User(id, "John Doe");
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Map<String, String> handleIllegalArgumentException(IllegalArgumentException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }
}

示例 2:返回 XML 错误信息

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable int id) {
        if (id == 0) {
            throw new IllegalArgumentException("Invalid user ID");
        }
        return new User(id, "John Doe");
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorResponse handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ErrorResponse("BAD_REQUEST", ex.getMessage());
    }
}

6. 使用 @ControllerAdvice 全局返回 JSON/XML 错误信息

@ControllerAdvice 用于全局处理异常,并返回 JSON/XML 格式的错误信息。

示例 1:全局返回 JSON 错误信息

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Map<String, String> handleException(Exception ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }
}

示例 2:全局返回 XML 错误信息

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponse handleException(Exception ex) {
        return new ErrorResponse("INTERNAL_SERVER_ERROR", ex.getMessage());
    }
}

总结

在 Spring MVC 项目中,返回 JSON/XML 数据时,常用的注解和方式包括:

  1. @RestController:直接返回数据,适合 RESTful API。

    配合**@RequestMapping**:指定 URL 和返回的内容类型。

  2. @Controller + @ResponseBody:将返回值写入响应体。

  3. @ResponseStatus:指定 HTTP 状态码。

  4. ResponseEntity:灵活控制 HTTP 响应的状态码、头部和内容。

  5. @ExceptionHandler:处理控制器中的异常,返回错误信息。

  6. @ControllerAdvice:全局处理异常,返回错误信息。

通过这些注解,可以灵活地实现 JSON/XML 数据的返回和异常处理。

6. Spring 项目 vs Spring Boot 项目视图配置方式的不同

视图技术Spring 项目配置方式Spring Boot 项目配置方式
JSP需要手动配置 InternalResourceViewResolver默认不支持,需手动配置前缀和后缀
Thymeleaf需要手动配置 ThymeleafViewResolver默认支持,自动配置前缀和后缀
FreeMarker需要手动配置 FreeMarkerViewResolver默认支持,自动配置前缀和后缀
Velocity需要手动配置 VelocityViewResolver默认不支持,需手动配置前缀和后缀

Spring 项目:需要手动配置视图解析器,XML 或 Java 配置均可。 • Spring Boot 项目:对于 Thymeleaf 和 FreeMarker,默认支持并自动配置;对于 JSP 和 Velocity,需要手动配置。 • 推荐:在 Spring Boot 项目中优先使用 Thymeleaf 或 FreeMarker,减少配置工作量。

八、异常处理

Spring MVC 提供了强大的异常处理机制,允许开发者集中处理应用程序中的异常,从而避免在控制器中重复编写异常处理代码。通过统一的异常处理机制,可以提高代码的可维护性和可读性。

1. Spring MVC 异常处理机制

Spring MVC 的异常处理机制提供了多种方式来处理应用程序中的异常,包括局部异常处理、全局异常处理、自定义异常解析器等。通过合理使用这些机制,可以实现统一的异常处理逻辑,提高代码的可维护性和可读性。

Spring MVC 提供了以下几种方式来处理异常:

  1. @ExceptionHandler 注解: • 在控制器内部定义异常处理方法,用于处理该控制器中抛出的特定异常。 • 只能处理当前控制器中的异常。

  2. @ControllerAdvice 注解: • 定义一个全局的异常处理类,用于处理整个应用程序中的异常。 • 可以结合 @ExceptionHandler 注解实现全局异常处理。

  3. HandlerExceptionResolver 接口: • 实现自定义的异常解析器,用于处理特定类型的异常。 • 提供了更底层的控制,适合复杂的异常处理场景。

  4. @ResponseStatus 注解: • 将特定异常映射到 HTTP 状态码,直接返回给客户端。

  5. 默认异常处理: • 如果没有显式处理异常,Spring MVC 会使用默认的异常处理机制,返回 500 错误。


2. 异常处理的方式

1. 使用 @ExceptionHandler 处理局部异常

在控制器中定义异常处理方法,处理该控制器中抛出的异常。

示例代码:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        if (id == 0) {
            throw new IllegalArgumentException("Invalid user ID");
        }
        return new User(id, "John Doe");
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }
}
特点:

• 只能处理当前控制器中的异常。 • 方法可以返回 ResponseEntityModelAndView 或其他类型。


2. 使用 @ControllerAdvice 处理全局异常

定义一个全局异常处理类,处理整个应用程序中的异常。

示例代码:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return ResponseEntity.badRequest().body("Bad Request: " + ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGenericException(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal Server Error: " + ex.getMessage());
    }
}

特点:

• 可以处理所有控制器中的异常。 • 支持多个 @ExceptionHandler 方法,分别处理不同类型的异常。


3. 使用 HandlerExceptionResolver 实现自定义异常解析器

通过实现 HandlerExceptionResolver 接口,自定义异常处理逻辑。

示例代码:

@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (ex instanceof IllegalArgumentException) {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setStatus(HttpStatus.BAD_REQUEST);
            modelAndView.addObject("error", ex.getMessage());
            modelAndView.setViewName("error/badRequest");
            return modelAndView;
        }
        return null; // 返回 null 表示由其他解析器处理
    }
}

特点:

• 提供了更底层的控制,适合复杂的异常处理场景。 • 可以返回 ModelAndView 或直接修改 HttpServletResponse


4. 使用 @ResponseStatus 注解映射异常到 HTTP 状态码

将特定异常映射到 HTTP 状态码,直接返回给客户端。

示例代码:

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "User not found")
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

特点:

• 异常抛出时,Spring MVC 会自动返回指定的 HTTP 状态码和原因。 • 适合简单的异常处理场景。


5. 默认异常处理

如果没有显式处理异常,Spring MVC 会使用默认的异常处理机制,返回 500 错误。

3. 返回异常数据 or 异常页面?

返回 JSON/XML 异常数据

适用场景

RESTful API 项目: • 前后端分离架构,前端通过 API 与后端交互。 • 后端通常返回 JSON/XML 格式的数据,而不是 HTML 页面。 • 微服务架构: • 服务之间通过 API 通信,通常使用 JSON/XML 作为数据格式。 • 移动端或第三方集成: • 移动端应用或第三方系统通常需要 JSON/XML 格式的错误信息。

实现方式

• 使用 @RestController@Controller + @ResponseBody 返回 JSON/XML 数据。 • 使用 @ExceptionHandler@ControllerAdvice 捕获异常并返回 JSON/XML 格式的错误信息。

示例:返回 JSON 错误信息

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, String> handleException(Exception ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }
}

示例:返回 XML 错误信息

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleException(Exception ex) {
        return new ErrorResponse("INTERNAL_SERVER_ERROR", ex.getMessage());
    }
}

返回具体错误页面视图

适用场景

传统 Web 应用: • 前后端未分离,后端直接渲染 HTML 页面。 • 用户通过浏览器访问,需要友好的错误页面。 • 需要定制化错误页面: • 例如 404、500 等错误页面,提供更好的用户体验。 • SEO 优化: • 对于搜索引擎优化(SEO),返回 HTML 页面更友好。

实现方式

• 使用 @Controller 返回视图名称,渲染具体的错误页面。 • 使用 @ExceptionHandler 捕获异常并返回视图名称。 • 配置 ErrorController 或自定义错误页面。

示例:返回错误页面视图

@Controller
public class ErrorController {
    @ExceptionHandler(Exception.class)
    public String handleException(Exception ex, Model model) {
        model.addAttribute("error", ex.getMessage());
        return "errorPage"; // 返回视图名称
    }
}

示例:配置自定义错误页面

src/main/resources/templates/ 目录下创建错误页面: • 404.html:404 错误页面。 • 500.html:500 错误页面。

application.properties 中配置:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

结论

Spring Boot 项目:更倾向于返回 JSON/XML 数据: • Spring Boot 通常用于构建 RESTful API 或微服务,前后端分离是主流架构。 • 异常处理时,返回 JSON/XML 格式的错误信息更符合 API 的设计规范。 • Spring Boot 默认支持 JSON 序列化(通过 Jackson),配置简单。

Spring 项目:视项目需求而定: • 如果是传统 Web 应用,可能更倾向于返回错误页面视图。 • 如果是 RESTful API 或微服务,则返回 JSON/XML 数据。

场景返回 JSON/XML 数据返回具体错误页面视图
适用场景RESTful API、微服务、移动端、第三方集成传统 Web 应用、SEO 优化、定制化错误页面
实现方式@RestController@ExceptionHandler@ControllerErrorController
Spring Boot 项目更常见,默认支持 JSON较少见,需手动配置
Spring 项目视项目需求而定视项目需求而定

推荐

RESTful API 或微服务:优先返回 JSON/XML 数据。 • 传统 Web 应用:优先返回具体的错误页面视图。

根据项目架构和需求,选择合适的异常处理方式,既能提高开发效率,也能提升用户体验。

3. 实现异常处理的要求

  1. 异常处理方法的签名: • 方法可以包含 Exception 参数,用于捕获异常。 • 方法可以返回 ResponseEntityModelAndViewString(视图名称)或其他类型。
  2. 异常处理的优先级: • @ExceptionHandler(控制器内部) > @ControllerAdvice(全局) > HandlerExceptionResolver > 默认异常处理。
  3. 异常处理的范围: • 使用 @ControllerAdvice 可以集中处理整个应用程序的异常,避免代码重复。
  4. 异常处理的粒度: • 可以根据业务需求定义不同的异常处理方法,处理特定类型的异常。
  5. 异常信息的传递: • 可以通过 ResponseEntityModelAndView 将异常信息返回给客户端。
  6. 日志记录: • 在异常处理方法中记录日志,便于排查问题。

九、测试支持

Spring MVC Test: 提供了对Spring MVC应用程序的测试支持,可以模拟HTTP请求并验证响应。 • MockMvc: 用于在单元测试中模拟Spring MVC的行为。


Spring MVC Test 是 Spring 提供的一个强大的测试框架,专门用于测试 Spring MVC 控制器(Controller)。它允许开发者在不启动完整 Spring 上下文的情况下,模拟 HTTP 请求并验证控制器的行为。通过 MockMvc,开发者可以轻松编写单元测试和集成测试,确保控制器的逻辑正确无误。无论是简单的 GET 请求,还是复杂的文件上传和异常处理,Spring MVC Test 都提供了丰富的功能支持。


1. Spring MVC Test 的核心组件

1.1 MockMvc

作用: 模拟 HTTP 请求并验证响应。 • 特点: • 支持 GET、POST、PUT、DELETE 等 HTTP 方法。 • 支持验证响应状态码、响应内容、响应头等。 • 支持模拟文件上传、表单提交等复杂请求。

MockMvc 可单独使用,以执行请求并使用 Hamcrest 或通过 MockMvcTester 验证响应,后者使用 AssertJ 提供了流畅的 API。它还可以通过 WebTestClient 使用,在 WebTestClient 中,MockMvc 被插入为服务器来处理请求。使用 WebTestClient 的优势在于,它为您提供了处理更高级对象(而非原始数据)的选项,还能针对实时服务器切换到完整的端到端 HTTP 测试,并使用相同的测试 API。

1.2 MockMvcBuilders

作用: 用于创建 MockMvc 实例。 • 两种模式: • 独立模式(Standalone): 仅加载指定的控制器,不加载完整的 Spring 上下文。 • Web 应用模式(WebApp): 加载完整的 Spring 上下文,包括控制器、服务、DAO 等。

1.3 RequestBuilderResultMatcher

RequestBuilder: 用于构建 HTTP 请求(如 MockMvcRequestBuilders.get())。 • ResultMatcher: 用于验证 HTTP 响应(如 MockMvcResultMatchers.status().isOk())。


2. Spring MVC Test 的常见用法

2.1 测试 GET 请求

@Test
public void testGetUser() throws Exception {
    mockMvc.perform(get("/user").param("id", "1"))
           .andExpect(status().isOk())
           .andExpect(content().string("User ID: 1"));
}

2.2 测试 POST 请求

@Test
public void testCreateUser() throws Exception {
    mockMvc.perform(post("/user")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\": \"John\", \"age\": 30}"))
           .andExpect(status().isCreated());
}

2.3 测试 JSON 响应

@Test
public void testGetUserJson() throws Exception {
    mockMvc.perform(get("/user/json").param("id", "1"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.id").value(1))
           .andExpect(jsonPath("$.name").value("John"));
}

2.4 测试视图和模型

@Test
public void testGetUserView() throws Exception {
    mockMvc.perform(get("/user/view").param("id", "1"))
           .andExpect(status().isOk())
           .andExpect(view().name("user"))
           .andExpect(model().attribute("id", 1));
}

2.5 测试文件上传

@Test
public void testUploadFile() throws Exception {
    MockMultipartFile file = new MockMultipartFile("file", "test.txt", "text/plain", "Hello World".getBytes());
    mockMvc.perform(multipart("/upload").file(file))
           .andExpect(status().isOk())
           .andExpect(content().string("File uploaded successfully"));
}

2.6 测试异常处理

@Test
public void testGetUserNotFound() throws Exception {
    mockMvc.perform(get("/user").param("id", "999"))
           .andExpect(status().isNotFound())
           .andExpect(content().string("User not found"));
}

3. Spring MVC Test 的高级用法

3.1 自定义请求头

@Test
public void testGetUserWithHeader() throws Exception {
    mockMvc.perform(get("/user").header("Authorization", "Bearer token"))
           .andExpect(status().isOk());
}

3.2 模拟会话(Session)

@Test
public void testGetUserWithSession() throws Exception {
    mockMvc.perform(get("/user").sessionAttr("userId", 1))
           .andExpect(status().isOk());
}

3.3 验证响应头

@Test
public void testGetUserWithResponseHeader() throws Exception {
    mockMvc.perform(get("/user"))
           .andExpect(status().isOk())
           .andExpect(header().string("Content-Type", "application/json"));
}

3.4 使用 @MockBean 模拟依赖

@MockBean
private UserService userService;

@Test
public void testGetUserWithMockService() throws Exception {
    when(userService.getUserById(1)).thenReturn(new User(1, "John"));

    mockMvc.perform(get("/user").param("id", "1"))
           .andExpect(status().isOk())
           .andExpect(content().string("User ID: 1"));
}

4. Spring MVC Test 的两种模式

4.1 独立模式(Standalone)

仅加载指定的控制器,适合单元测试。

public class UserControllerTest {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    }

    @Test
    public void testGetUser() throws Exception {
        mockMvc.perform(get("/user").param("id", "1"))
               .andExpect(status().isOk())
               .andExpect(content().string("User ID: 1"));
    }
}

4.2 Web 应用模式(WebApp)

加载完整的 Spring 上下文,适合集成测试。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})
@WebAppConfiguration
public class UserControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    public void testGetUser() throws Exception {
        mockMvc.perform(get("/user").param("id", "1"))
               .andExpect(status().isOk())
               .andExpect(content().string("User ID: 1"));
    }
}

5. Spring MVC Test 的最佳实践

  1. 优先使用独立模式: • 独立模式运行更快,适合单元测试。
  2. 合理使用 Web 应用模式: • Web 应用模式适合测试控制器的完整行为,包括依赖注入、拦截器等。
  3. 模拟依赖: • 使用 @MockBean 模拟服务层或 DAO 层的依赖。
  4. 验证全面的响应: • 不仅验证状态码,还要验证响应内容、响应头等。
  5. 保持测试简洁: • 每个测试方法只测试一个功能点,避免过于复杂的验证逻辑。

6. Spring Test MVC应用完整示例

1. 添加依赖

pom.xml 中添加 spring-testjunit 依赖:

<dependencies>
    <!-- Spring MVC Test -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.22</version>
        <scope>test</scope>
    </dependency>

    <!-- JUnit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 编写控制器

假设有一个简单的控制器 UserController

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    @GetMapping("/user")
    @ResponseBody
    public String getUser(@RequestParam("id") int id) {
        return "User ID: " + id;
    }
}

3. 编写测试类

使用 Spring MVC Test 测试 UserController

3.1 使用 MockMvc

MockMvc 是 Spring MVC Test 的核心类,用于模拟 HTTP 请求。

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})
@WebAppConfiguration
public class UserControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    public void testGetUser() throws Exception {
        mockMvc.perform(get("/user").param("id", "1"))
               .andExpect(status().isOk())
               .andExpect(content().string("User ID: 1"));
    }
}
3.2 使用 StandaloneMockMvcBuilder

如果不需要加载完整的 Spring 上下文,可以使用 StandaloneMockMvcBuilder 进行测试。

import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class UserControllerTest {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    }

    @Test
    public void testGetUser() throws Exception {
        mockMvc.perform(get("/user").param("id", "1"))
               .andExpect(status().isOk())
               .andExpect(content().string("User ID: 1"));
    }
}

4. 测试结果

运行测试后,MockMvc 会模拟 HTTP 请求并验证控制器的行为: • status().isOk():验证 HTTP 状态码是否为 200。 • content().string("User ID: 1"):验证响应内容是否为 "User ID: 1"


5. 扩展功能

Spring MVC Test 还支持以下功能:

5.1 验证 JSON 响应

如果控制器返回 JSON 数据,可以使用 jsonPath 验证响应内容。

@Test
public void testGetUserJson() throws Exception {
    mockMvc.perform(get("/user/json").param("id", "1"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.id").value(1))
           .andExpect(jsonPath("$.name").value("John"));
}
5.2 验证视图和模型

如果控制器返回视图和模型数据,可以使用 view().name()model().attribute() 进行验证。

@Test
public void testGetUserView() throws Exception {
    mockMvc.perform(get("/user/view").param("id", "1"))
           .andExpect(status().isOk())
           .andExpect(view().name("user"))
           .andExpect(model().attribute("id", 1));
}
5.3 模拟文件上传

使用 MockMultipartFile 模拟文件上传。

@Test
public void testUploadFile() throws Exception {
    MockMultipartFile file = new MockMultipartFile("file", "test.txt", "text/plain", "Hello World".getBytes());
    mockMvc.perform(multipart("/upload").file(file))
           .andExpect(status().isOk())
           .andExpect(content().string("File uploaded successfully"));
}

国际化(i18n)

在 Spring MVC 中,国际化(Internationalization,简称 i18n)是通过 MessageSourceLocaleResolver 实现的。以下是 Spring MVC 支持国际化的具体应用示例。


1. 添加依赖

pom.xml 中添加 spring-webmvc 依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.22</version>
</dependency>

2. 配置国际化资源文件

src/main/resources 目录下创建国际化资源文件: • messages.properties(默认语言) • messages_en.properties(英语) • messages_zh_CN.properties(简体中文)

messages.properties

welcome.message=Welcome!

messages_en.properties

welcome.message=Welcome!

messages_zh_CN.properties

welcome.message=欢迎!

3. 配置 Spring MVC 支持国际化

在 Spring 配置文件中配置 MessageSourceLocaleResolver

3.1 基于 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"
       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">

    <!-- 配置 MessageSource -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="messages" />
        <property name="defaultEncoding" value="UTF-8" />
    </bean>

    <!-- 配置 LocaleResolver -->
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
        <property name="defaultLocale" value="en" />
    </bean>

    <!-- 配置 LocaleChangeInterceptor -->
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
        <property name="paramName" value="lang" />
    </bean>
</beans>

3.2 基于 Java 的配置

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.util.Locale;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver resolver = new SessionLocaleResolver();
        resolver.setDefaultLocale(Locale.ENGLISH);
        return resolver;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang");
        return interceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

4. 编写控制器

在控制器中使用 MessageSource 获取国际化消息。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Locale;

@Controller
public class HomeController {

    @Autowired
    private MessageSource messageSource;

    @GetMapping("/")
    public String home(@RequestParam(value = "lang", required = false) String lang, Locale locale) {
        String welcomeMessage = messageSource.getMessage("welcome.message", null, locale);
        System.out.println("Welcome Message: " + welcomeMessage);
        return "home";
    }
}

5. 编写视图

在视图(如 JSP 或 Thymeleaf)中使用国际化消息。

5.1 JSP 视图

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
    <title>Home</title>
</head>
<body>
    <h1><spring:message code="welcome.message" /></h1>
</body>
</html>

5.2 Thymeleaf 视图

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <h1 th:text="#{welcome.message}">Welcome!</h1>
</body>
</html>

6. 测试国际化

通过 URL 参数 lang 切换语言。

6.1 默认语言(英语)

访问 http://localhost:8080/,输出:

Welcome Message: Welcome!

6.2 切换到简体中文

访问 http://localhost:8080/?lang=zh_CN,输出:

Welcome Message: 欢迎!

7. 其他 LocaleResolver 实现

Spring MVC 提供了多种 LocaleResolver 实现,开发者可以根据需求选择。

7.1 SessionLocaleResolver

特点: 将语言信息存储在会话中。 • 适用场景: 用户登录后保持语言偏好。

7.2 CookieLocaleResolver

特点: 将语言信息存储在 Cookie 中。 • 适用场景: 用户未登录时保持语言偏好。

7.3 AcceptHeaderLocaleResolver

特点: 根据请求头 Accept-Language 自动选择语言。 • 适用场景: 根据浏览器语言设置自动切换语言。


总结

Spring MVC 的国际化支持非常灵活,通过 MessageSourceLocaleResolver 可以轻松实现多语言支持。开发者可以通过配置文件、URL 参数、会话或 Cookie 等方式切换语言,同时还可以在视图层(如 JSP 或 Thymeleaf)中直接使用国际化消息。

性能优化

缓存: 支持HTTP缓存,可以通过配置Cache-Control头来优化性能。 • 异步处理: 支持异步请求处理,提高应用程序的并发处理能力。

安全性

• 虽然Spring MVC本身不提供安全功能,但可以与Spring Security集成,提供身份验证、授权等安全功能。

与其他技术的集成

WebSocket: 支持WebSocket协议,用于实现实时通信。 • SockJS: 提供WebSocket的降级方案,兼容不支持WebSocket的浏览器。 • STOMP Messaging: 支持STOMP协议,用于在WebSocket之上实现消息传递。

扩展性

• Spring MVC具有高度的扩展性,可以通过自定义HandlerInterceptor、ViewResolver等组件来扩展框架的功能。