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 的配置非常简单:
- 默认配置:Spring Boot 已经为 Spring MVC 提供了默认的自动配置。无需额外配置即可使用。
- 自定义配置:
• 通过
application.properties
或application.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.properties
或 application.yml
Spring Boot 提供了许多与 Spring MVC 相关的配置属性,可以在 application.properties
或 application.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之间传递数据。
请求处理流程
- 请求到达DispatcherServlet:客户端发送的HTTP请求首先被DispatcherServlet接收。
- HandlerMapping查找处理器:DispatcherServlet通过HandlerMapping找到处理该请求的Controller。
- Controller处理请求:Controller执行业务逻辑,并返回一个ModelAndView对象。
- ViewResolver解析视图:DispatcherServlet通过ViewResolver解析视图名称,找到对应的视图。
- 视图渲染:视图使用模型数据渲染响应,并将结果返回给客户端。
+-------------------+
| HTTP Request |
+-------------------+
|
v
+-------------------+
| DispatcherServlet |
+-------------------+
|
v
+-------------------+
| HandlerMapping |
| (查找处理器) |
+-------------------+
|
v
+-------------------+
| HandlerAdapter |
| (调用处理器) |
+-------------------+
|
v
+-------------------+
| Controller Method |
| (执行业务逻辑) |
+-------------------+
|
v
+-------------------+
| 返回结果 |
| (ModelAndView, |
| JSON, String) |
+-------------------+
|
v
+-------------------+
| ViewResolver |
| (解析视图) |
+-------------------+
|
v
+-------------------+
| HTTP Response |
+-------------------+
详细步骤:
- 接收请求:
•
DispatcherServlet
接收所有到达应用程序的 HTTP 请求。 - 查找处理器:
• 调用
HandlerMapping
,根据请求的 URL 和其他信息,查找合适的处理器(通常是 Controller 中的方法)。 - 调用处理器:
• 调用
HandlerAdapter
,执行处理器方法,并将方法返回的结果(如ModelAndView
、JSON 数据等)封装为响应。 - 处理业务逻辑: • 处理器方法执行业务逻辑,并返回结果。
- 解析视图:
• 如果返回的是视图名称(如
ModelAndView
),则调用ViewResolver
解析视图,并渲染页面。 - 返回响应: • 将最终生成的响应(如 HTML 页面、JSON 数据等)返回给客户端
前端控制器
DispatcherServlet
是 Spring MVC 框架的核心组件,它充当了前端控制器(Front Controller)的角色,负责接收所有的 HTTP 请求,并将这些请求分发给合适的处理器(Handler),最后将处理结果返回给客户端。它依赖于 HandlerMapping
、HandlerAdapter
、ViewResolver
等组件来完成请求的处理和响应的生成。通过灵活的路由配置、强大的扩展性和丰富的特性,DispatcherServlet
使得 Spring MVC 能够轻松构建高效、可维护的 Web 应用程序。
1. DispatcherServlet 的作用
DispatcherServlet
是 Spring MVC 的入口点,它的主要职责包括:
- 接收请求: • 接收所有到达应用程序的 HTTP 请求。
- 分发请求: • 根据请求的 URL 和其他信息,将请求分发给合适的处理器(通常是 Controller 中的方法)。
- 协调处理流程:
• 调用
HandlerMapping
、HandlerAdapter
、ViewResolver
等组件,完成请求的处理和响应的生成。 - 返回响应: • 将处理结果(如 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 的特性
- 灵活的路由:
• 支持基于注解的路由配置(如
@RequestMapping
),也支持传统的 XML 配置。 - 强大的扩展性:
• 可以通过自定义
HandlerMapping
、HandlerAdapter
等组件扩展功能。 - 异常处理:
• 支持全局异常处理(如
@ControllerAdvice
)和局部异常处理(如@ExceptionHandler
)。 - 文件上传:
• 内置对文件上传的支持,可以通过
MultipartResolver
轻松处理文件上传请求。 - 视图解析:
• 支持多种视图技术(如 JSP、Thymeleaf、FreeMarker 等),并可以通过
ViewResolver
灵活配置。
4. DispatcherServlet 的常见问题
- URL 映射冲突: • 如果多个处理器映射到同一个 URL,可能会导致冲突。需要确保 URL 映射的唯一性。
- 性能问题:
•
DispatcherServlet
处理请求时会涉及多个组件的调用,可能会影响性能。可以通过缓存、异步处理等方式优化性能。 - 配置错误:
• 如果
HandlerMapping
、HandlerAdapter
等组件配置错误,可能会导致请求无法正确处理。需要仔细检查配置。
其他组件
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中,可以通过以下方式获取请求的消息头和消息体:
- 获取请求头:
• 使用
@RequestHeader
注解:可以将请求头直接注入到Controller方法的参数中。
@GetMapping("/example")
public String example(@RequestHeader("User-Agent") String userAgent) {
return "User-Agent: " + userAgent;
}
- 获取请求体:
• 使用
@RequestBody
注解:可以将请求体(通常是JSON或XML)直接绑定到方法的参数中。
@PostMapping("/example")
public String example(@RequestBody User user) {
return "Received user: " + user.getName();
}
- 获取请求参数:
• 使用
@RequestParam
注解:可以将请求参数(通常是URL中的查询参数或表单数据)直接注入到方法的参数中。
@GetMapping("/example")
public String example(@RequestParam("name") String name) {
return "Hello, " + name;
}
- 获取整个请求对象:
• 使用
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
视图,并传递 category
、minPrice
和 maxPrice
属性。
示例 5: 处理可选参数,使用 @RequestParam
的 required
属性
@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
视图,并传递 status
、startDate
和 endDate
属性。
处理表单提交
@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();
}
}
- 结合路径变量和查询参数
示例 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
视图,并传递 userId
和 format
属性。
@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
实现,具体步骤如下:
-
初始化阶段: • 在应用启动时,Spring MVC会扫描所有带有
@Controller
或@RestController
注解的类,并解析其中的@RequestMapping
注解。 • 每个@RequestMapping
注解会被转换为一个RequestMappingInfo
对象,包含URL模式、请求方法、请求头、参数等条件。 • 这些RequestMappingInfo
对象与对应的HandlerMethod
会被注册到RequestMappingHandlerMapping
中。 -
请求匹配阶段: • 当一个请求到达时,
DispatcherServlet
会调用HandlerMapping
的getHandler
方法。 •RequestMappingHandlerMapping
会根据请求的URL、请求方法、请求头等信息,查找与之匹配的RequestMappingInfo
。 • 如果找到匹配的RequestMappingInfo
,则会返回对应的HandlerMethod
。 -
请求处理阶段: •
DispatcherServlet
会调用HandlerAdapter
的handle
方法,将请求交给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 |
+-------------------+
详细步骤说明:
-
HTTP Request: • 客户端发送HTTP请求到服务器。
-
DispatcherServlet: •
DispatcherServlet
是Spring MVC的核心组件,负责接收所有请求,并将其分发给合适的处理程序。 -
HandlerMapping: •
DispatcherServlet
调用HandlerMapping
(如RequestMappingHandlerMapping
)来查找与请求匹配的处理程序。 •HandlerMapping
根据请求的URL、请求方法、请求头等信息,查找对应的RequestMappingInfo
对象。 -
RequestMappingInfo: •
RequestMappingInfo
封装了请求的映射条件(如URL模式、请求方法、请求头、参数等)。 • 如果找到匹配的RequestMappingInfo
,则返回对应的HandlerMethod
。 -
HandlerAdapter: •
DispatcherServlet
调用HandlerAdapter
(如RequestMappingHandlerAdapter
)来执行处理程序。 •HandlerAdapter
负责调用HandlerMethod
对应的方法,并将方法返回的结果转换为ModelAndView
对象或其他响应类型。 -
HandlerMethod: •
HandlerMethod
封装了Controller方法的元数据(如方法对象、参数类型、返回类型等)。 •HandlerAdapter
调用HandlerMethod
对应的方法,执行业务逻辑。 -
Controller Method: • Controller方法处理业务逻辑,并返回结果(如
ModelAndView
、JSON、字符串等)。 -
Return Result: • Controller方法返回的结果会被
HandlerAdapter
处理,并最终由DispatcherServlet
转换为HTTP响应。 -
HTTP Response: • 服务器将HTTP响应返回给客户端。
关键点:
• HandlerMapping负责将请求映射到处理程序。 • HandlerAdapter负责执行处理程序并处理结果。 • RequestMappingInfo和HandlerMethod是路径映射和处理的核心数据结构。
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=John
,name
参数会被绑定到方法参数。
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 方法 | 示例 | 说明 |
---|---|---|---|---|
获取资源 | /users | GET | GET /users | 获取所有用户资源。 |
/users/{id} | GET | GET /users/1 | 获取 ID 为 1 的用户资源。 | |
创建资源 | /users | POST | POST /users | 创建新用户,请求体包含用户数据。 |
更新资源 | /users/{id} | PUT | PUT /users/1 | 更新 ID 为 1 的用户资源,请求体包含更新数据。 |
删除资源 | /users/{id} | DELETE | DELETE /users/1 | 删除 ID 为 1 的用户资源。 |
嵌套资源 | /users/{userId}/orders | GET | GET /users/1/orders | 获取用户 ID 为 1 的所有订单。 |
/users/{userId}/orders/{orderId} | GET | GET /users/1/orders/100 | 获取用户 ID 为 1 的订单 ID 为 100 的订单。 |
通过上表可以清晰地看到 RESTful 风格的路径设计规则,包括:
- 资源为核心:URL 表示资源(如
/users
、/orders
)。 - HTTP 方法表示操作:使用
GET
、POST
、PUT
、DELETE
等 HTTP 方法表示对资源的操作。 - 路径变量表示资源标识:使用
{id}
等路径变量表示特定资源。 - 嵌套资源:通过 URL 层次化表示资源之间的关系(如
/users/{userId}/orders
)。
这种设计方式使得 RESTful API 更加语义化、清晰且易于维护。
RESTful风格路径的设计原则
- 资源为核心:
• URL表示资源,而不是操作。资源通常使用名词表示,例如
/users
、/orders
。 - HTTP方法表示操作:
• 使用HTTP方法(GET、POST、PUT、DELETE等)表示对资源的操作。
◦
GET
:获取资源。 ◦POST
:创建资源。 ◦PUT
:更新资源。 ◦DELETE
:删除资源。 - 路径变量表示资源标识:
• 使用路径变量(如
/users/{id}
)表示特定资源。 - URL层次化:
• 使用URL的层次结构表示资源之间的关系,例如
/users/{userId}/orders
表示某个用户的订单。
注意事项
- 避免动词:
• URL中应避免使用动词(如
/getUsers
),而应使用名词表示资源。 - 使用复数形式:
• 资源名称通常使用复数形式(如
/users
而不是/user
)。 - 版本控制:
• 如果API需要版本控制,可以在URL或请求头中引入版本号,例如
/v1/users
。 - 状态码: • 使用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路径的优点
- 语义清晰: • URL和HTTP方法结合,明确表达操作意图。
- 易于扩展: • 通过URL层次化,可以轻松扩展资源关系。
- 标准化: • 符合RESTful设计规范,易于理解和维护。
- 与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),值可以是任意类型的对象。
• Attribute
是 Model
的组成部分,Model
可以包含多个 Attribute
。
Model 和 Attribute 的异同
特性 | Model | Attribute |
---|---|---|
定义 | 一个数据容器,用于存储多个键值对。 | 单个键值对,是 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
是一个容器,存储了三个 Attribute
:message
、 user
和products
。
• 这些 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 数据绑定的流程
- 接收请求:DispatcherServlet接收HTTP请求。
- 参数解析:Spring MVC根据请求参数的类型和名称,调用适当的
DataBinder
进行绑定。 - 类型转换:如果请求参数的类型与目标类型不匹配,Spring会尝试进行类型转换(如将字符串转换为整数)。
- 绑定到对象:将解析后的参数绑定到目标对象(如POJO、DTO等)。
1.3 自定义数据绑定
• 自定义类型转换器:通过实现Converter
或Formatter
接口,可以自定义类型转换逻辑。
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中,数据绑定和验证通常是结合使用的:
- 绑定数据:将请求参数绑定到模型对象。
- 验证数据:对绑定后的模型对象进行验证。
- 处理结果:根据验证结果决定后续操作(如返回错误信息或保存数据)。
示例:
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());
}
}
自定义类型转换器在以下场景中非常有用:
- 处理复杂类型或自定义格式的请求参数。
- 处理特殊字符、编码或本地化数据。
- 映射枚举类型或嵌套对象。
- 处理默认转换器不支持的类型或多值参数。
通过自定义类型转换器,开发者可以灵活地处理各种复杂的请求参数,确保数据绑定过程符合业务需求。
总结
• 数据绑定:Spring MVC自动将请求参数绑定到Java对象,支持自定义类型转换和绑定规则。 • 验证:通过JSR-303/JSR-380标准注解对模型数据进行验证,支持自定义验证和分组验证。 • 结合使用:数据绑定和验证通常结合使用,确保用户输入的数据有效且符合业务规则。
通过合理使用数据绑定和验证,开发者可以简化代码、提高开发效率,并确保应用程序的健壮性和安全性。
七、视图技术
• Spring MVC支持多种视图技术,包括JSP、Thymeleaf、FreeMarker、Velocity等。 • 通过配置ViewResolver,可以灵活选择和使用不同的视图技术。
配置视图的一般步骤:
- 在pom.xml中添加依赖。
- 在 Spring(或SpringBoot)配置文件中配置视图解析器。
- 放置视图文件,通常放在
/WEB-INF/views/
目录下。 - 在控制器方法中返回视图名称,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,但可以通过以下方式启用:
- 在
application.properties
中配置:spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp
- 确保 JSP 文件放在
src/main/webapp/WEB-INF/views/
目录下。
3. Thymeleaf 视图技术
现代模板引擎,支持 HTML5,天然支持与 Spring 集成。 • 强调自然模板(Natural Templates),可以在浏览器中直接预览。
配置 ThymeleafViewResolver
和 SpringTemplateEngine
,将视图名称解析为 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 等。配置 FreeMarkerViewResolver
和 FreeMarkerConfigurer
,将视图名称解析为 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 数据时,常用的注解和方式包括:
-
@RestController
:直接返回数据,适合 RESTful API。配合**
@RequestMapping
**:指定 URL 和返回的内容类型。 -
@Controller
+@ResponseBody
:将返回值写入响应体。 -
@ResponseStatus
:指定 HTTP 状态码。 -
ResponseEntity
:灵活控制 HTTP 响应的状态码、头部和内容。 -
@ExceptionHandler
:处理控制器中的异常,返回错误信息。 -
@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 提供了以下几种方式来处理异常:
-
@ExceptionHandler
注解: • 在控制器内部定义异常处理方法,用于处理该控制器中抛出的特定异常。 • 只能处理当前控制器中的异常。 -
@ControllerAdvice
注解: • 定义一个全局的异常处理类,用于处理整个应用程序中的异常。 • 可以结合@ExceptionHandler
注解实现全局异常处理。 -
HandlerExceptionResolver
接口: • 实现自定义的异常解析器,用于处理特定类型的异常。 • 提供了更底层的控制,适合复杂的异常处理场景。 -
@ResponseStatus
注解: • 将特定异常映射到 HTTP 状态码,直接返回给客户端。 -
默认异常处理: • 如果没有显式处理异常,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());
}
}
特点:
• 只能处理当前控制器中的异常。
• 方法可以返回 ResponseEntity
、ModelAndView
或其他类型。
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 | @Controller 、ErrorController |
Spring Boot 项目 | 更常见,默认支持 JSON | 较少见,需手动配置 |
Spring 项目 | 视项目需求而定 | 视项目需求而定 |
推荐
• RESTful API 或微服务:优先返回 JSON/XML 数据。 • 传统 Web 应用:优先返回具体的错误页面视图。
根据项目架构和需求,选择合适的异常处理方式,既能提高开发效率,也能提升用户体验。
3. 实现异常处理的要求
- 异常处理方法的签名:
• 方法可以包含
Exception
参数,用于捕获异常。 • 方法可以返回ResponseEntity
、ModelAndView
、String
(视图名称)或其他类型。 - 异常处理的优先级:
•
@ExceptionHandler
(控制器内部) >@ControllerAdvice
(全局) >HandlerExceptionResolver
> 默认异常处理。 - 异常处理的范围:
• 使用
@ControllerAdvice
可以集中处理整个应用程序的异常,避免代码重复。 - 异常处理的粒度: • 可以根据业务需求定义不同的异常处理方法,处理特定类型的异常。
- 异常信息的传递:
• 可以通过
ResponseEntity
或ModelAndView
将异常信息返回给客户端。 - 日志记录: • 在异常处理方法中记录日志,便于排查问题。
九、测试支持
• 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 RequestBuilder
和 ResultMatcher
• 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 的最佳实践
- 优先使用独立模式: • 独立模式运行更快,适合单元测试。
- 合理使用 Web 应用模式: • Web 应用模式适合测试控制器的完整行为,包括依赖注入、拦截器等。
- 模拟依赖:
• 使用
@MockBean
模拟服务层或 DAO 层的依赖。 - 验证全面的响应: • 不仅验证状态码,还要验证响应内容、响应头等。
- 保持测试简洁: • 每个测试方法只测试一个功能点,避免过于复杂的验证逻辑。
6. Spring Test MVC应用完整示例
1. 添加依赖
在 pom.xml
中添加 spring-test
和 junit
依赖:
<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)是通过 MessageSource
和 LocaleResolver
实现的。以下是 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 配置文件中配置 MessageSource
和 LocaleResolver
。
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 的国际化支持非常灵活,通过 MessageSource
和 LocaleResolver
可以轻松实现多语言支持。开发者可以通过配置文件、URL 参数、会话或 Cookie 等方式切换语言,同时还可以在视图层(如 JSP 或 Thymeleaf)中直接使用国际化消息。
性能优化
• 缓存: 支持HTTP缓存,可以通过配置Cache-Control头来优化性能。 • 异步处理: 支持异步请求处理,提高应用程序的并发处理能力。
安全性
• 虽然Spring MVC本身不提供安全功能,但可以与Spring Security集成,提供身份验证、授权等安全功能。
与其他技术的集成
• WebSocket: 支持WebSocket协议,用于实现实时通信。 • SockJS: 提供WebSocket的降级方案,兼容不支持WebSocket的浏览器。 • STOMP Messaging: 支持STOMP协议,用于在WebSocket之上实现消息传递。
扩展性
• Spring MVC具有高度的扩展性,可以通过自定义HandlerInterceptor、ViewResolver等组件来扩展框架的功能。