Spring MVC概述
1.1 MVC设计模式
MVC(Model-View-Controller)是一种软件设计模式,旨在将应用程序的业务逻辑、用户界面和用户输入分离。Spring MVC遵循这一模式,提供了以下几个核心组件:
- Model:表示应用程序的数据和业务逻辑。
- View:负责呈现模型数据的用户界面。
- Controller:接收用户输入并调用相应的业务逻辑。
生活场景比喻
- Model:厨房,负责准备食材和做菜。
- View:餐桌,展示菜品和菜单。
- Controller:服务员,负责接收你的订单,并将其传递给厨房。
1.2 Spring MVC的核心组件
- DispatcherServlet:前端控制器,负责接收请求并将其分发到相应的处理器。
- HandlerMapping:根据请求的URL找到对应的处理器。
- Controller:处理请求的业务逻辑。
- HandlerAdapter:用于调用具体的处理器。
- ViewResolver:根据视图名称解析出具体的视图实现。
- ModelAndView:封装模型数据和视图信息的对象。
1.3 Spring MVC的工作流程
Spring MVC的工作流程如下:
- 客户端发送请求到
DispatcherServlet
。 DispatcherServlet
通过HandlerMapping
查找对应的Controller
。Controller
处理请求并返回ModelAndView
对象。DispatcherServlet
通过ViewResolver
解析视图。- 渲染视图并返回响应给客户端。
生活场景比喻
- 客户(客户端)向服务员(
DispatcherServlet
)下单。 - 服务员根据订单(请求的URL)找到对应的厨师(
Controller
)。 - 厨师准备好菜品(处理请求),并返回给服务员(
ModelAndView
)。 - 服务员将菜品放在餐桌上(视图),客户享用美食。
二、Spring MVC的请求处理
2.1 DispatcherServlet的实现
DispatcherServlet
是Spring MVC的核心组件,负责接收和处理所有HTTP请求。它是前端控制器,所有请求首先到达这里。 (Spring Boot应用中添加@SpringBootApplication
注解,框架会自动创建并配置一个DispatcherServlet
)
public class DispatcherServlet extends HttpServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) {
// 1. 获取请求的处理器
HandlerExecutionChain handler = getHandler(request);
// 2. 执行处理器
ModelAndView mv = handlerAdapter.handle(request, response, handler.getHandler());
// 3. 渲染视图
render(mv, request, response);
}
}
2.2 HandlerMapping的实现
HandlerMapping
用于将请求映射到对应的处理器。Spring MVC提供多种实现,最常用的是RequestMappingHandlerMapping
。
public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 1. 根据请求信息找到对应的HandlerMethod
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
}
2.3 Controller的实现
Controller
是业务逻辑的处理器,负责接收请求并返回模型和视图。
@Controller
public class MyController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "Hello, World!");
return "helloView"; // 返回视图名称
}
}
2.4 HandlerAdapter的实现
HandlerAdapter
负责调用具体的处理器方法,并返回一个ModelAndView
对象。
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter {
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 执行Controller的方法
Method method = ((HandlerMethod) handler).getMethod();
Object returnValue = method.invoke(handler, ...); // 传入方法参数
return new ModelAndView("viewName", "model", returnValue);
}
}
2.5 ViewResolver的实现
ViewResolver
根据视图名称解析出实际的视图实现。
public class InternalResourceViewResolver extends AbstractUrlBasedViewResolver {
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// 1. 创建JSP视图
String url = getPrefix() + viewName + getSuffix();
return new JstlView(url);
}
}
三、Spring MVC的请求参数处理
3.1 请求参数的获取
Spring MVC提供了多种方式来获取请求参数。可以使用@RequestParam
注解来获取单个参数,也可以使用@RequestParam
注解结合数组或集合来获取多个参数。
@GetMapping("/greet")
public String greet(@RequestParam String name, Model model) {
model.addAttribute("message", "Hello, " + name);
return "greetView";
}
3.2 请求体的处理
对于POST请求,Spring MVC可以使用@RequestBody
注解将请求体中的JSON数据自动转换为Java对象。
@PostMapping("/user")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 处理用户创建逻辑
return ResponseEntity.ok(user);
}
3.3 文件上传处理
Spring MVC支持文件上传,可以使用MultipartFile
来处理上传的文件。
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
// 处理文件上传逻辑
return "uploadSuccessView";
}
四、Spring MVC的视图解析
4.1 视图解析器的配置
视图解析器用于将视图名称解析为实际的视图实现。Spring MVC支持多种视图技术,如JSP、Thymeleaf、Freemarker等。
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
4.2 JSP视图的使用
jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>Hello Page</title>
</head>
<body>
<h1>${message}</h1>
</body>
</html>
4.3 Thymeleaf视图的使用
Thymeleaf是一种现代的服务器端Java模板引擎,支持HTML5和XML。
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello Page</title>
</head>
<body>
<h1 th:text="${message}">Hello, World!</h1>
</body>
</html>
五、Spring MVC的异常处理
5.1 全局异常处理
Spring MVC提供了全局异常处理机制,可以使用@ControllerAdvice
注解定义一个全局异常处理类。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
5.2 自定义异常处理
除了全局异常处理,还可以在Controller中使用@ExceptionHandler
注解处理特定异常。
@Controller
public class MyController {
@GetMapping("/error")
public String error() {
throw new RuntimeException("Something went wrong");
}
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException e) {
return "errorView"; // 返回错误视图
}
}
六、Spring MVC的拦截器
6.1 拦截器的定义
拦截器可以在请求处理之前和之后执行一些操作。可以通过实现HandlerInterceptor
接口定义一个拦截器。
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求处理之前执行
return true; // 返回true继续执行,返回false则中断请求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后执行
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在请求完成之后执行
}
}
6.2 拦截器的注册
可以通过实现WebMvcConfigurer
接口将拦截器注册到Spring MVC中。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); // 拦截所有请求
}
}
七、Spring MVC的安全性
7.1 Spring Security的集成
Spring Security是一个强大的安全框架,可以与Spring MVC无缝集成。以下是基本的集成步骤:
- 添加Spring Security依赖。
- 创建Security配置类,继承
WebSecurityConfigurerAdapter
。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll() // 公开路径
.anyRequest().authenticated() // 其他路径需要认证
.and()
.formLogin(); // 表单登录
}
}
7.2 基于注解的安全控制
可以使用@PreAuthorize
和@PostAuthorize
注解进行方法级别的安全控制。
八、Spring MVC的测试
8.1 单元测试
可以使用JUnit和Spring Test进行Spring MVC的单元测试。
@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testGreet() throws Exception {
mockMvc.perform(get("/greet").param("name", "John"))
.andExpect(status().isOk())
.andExpect(model().attribute("message", "Hello, John"));
}
}
8.2 集成测试
使用@SpringBootTest
进行集成测试。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testGreet() {
ResponseEntity<String> response = restTemplate.getForEntity("/greet?name=John", String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
}
}
九、Spring MVC的处理流程
9.1 详细处理流程
Spring MVC的处理流程主要分为以下几个步骤:
- 请求到达DispatcherServlet:客户端发送HTTP请求,首先到达
DispatcherServlet
,它是所有请求的入口。 - 查找处理器:
DispatcherServlet
使用HandlerMapping
查找对应的处理器(Controller
)。它根据请求的URL和HTTP方法来匹配合适的处理器。 - 执行处理器:找到处理器后,
DispatcherServlet
使用HandlerAdapter
执行该处理器。处理器方法被调用,并处理请求。 - 返回ModelAndView:处理器方法返回一个
ModelAndView
对象,包含模型数据和视图信息。 - 视图解析:
DispatcherServlet
使用ViewResolver
解析视图名称,找到实际的视图实现(如JSP、Thymeleaf等)。 - 渲染视图:视图被渲染,并将最终的HTML响应返回给客户端。
生活场景比喻
- 客户(客户端)向服务员(
DispatcherServlet
)下单。 - 服务员根据订单(请求的URL)找到对应的厨师(
Controller
)。 - 厨师准备好菜品(处理请求),并返回给服务员(
ModelAndView
)。 - 服务员将菜品放在餐桌上(视图),客户享用美食。
十、Spring MVC如何统一封装响应信息?
1. ApiResponse类定义
我们定义一个通用的ApiResponse
类,可以接收任意类型的数据。
public class ApiResponse<T> {
private int status; // 响应状态码
private String message; // 响应消息
private T data; // 响应数据
// 构造函数
public ApiResponse(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
// Getter和Setter方法
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
2. GlobalResponseWrapper类
在GlobalResponseWrapper
中,可以使用不同的方法来处理不同类型的响应。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
@ControllerAdvice // 该注解用于定义全局的异常处理和响应封装
public class GlobalResponseWrapper {
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Object>> handleException(Exception e) {
// 创建一个统一的响应对象,状态码为500,包含异常信息
ApiResponse<Object> response = new ApiResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), null);
// 返回响应实体,包含HTTP状态和响应体
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
// 在每个请求中添加统一的成功响应结构
@ModelAttribute
public void wrapResponse(Model model) {
// 向模型中添加一个成功的响应对象
model.addAttribute("apiResponse", new ApiResponse<>(200, "Success", null));
}
}
3. 控制器示例
在控制器中,可以根据不同的情况返回字符串或对象。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController {
// 返回字符串响应
@GetMapping("/message")
public ResponseEntity<ApiResponse<String>> getMessage() {
// 创建一个字符串响应
ApiResponse<String> response = new ApiResponse<>(200, "Success", "Hello, this is a string response!");
return ResponseEntity.ok(response);
}
// 返回对象响应
@GetMapping("/user")
public ResponseEntity<ApiResponse<User>> getUser() {
// 创建一个用户对象
User user = new User(1, "John Doe", "john@example.com");
// 创建一个对象响应
ApiResponse<User> response = new ApiResponse<>(200, "Success", user);
return ResponseEntity.ok(response);
}
}
4. 用户类定义
定义一个简单的用户类User
,以便在响应中使用。
public class User {
private int id; // 用户ID
private String name; // 用户姓名
private String email; // 用户邮箱
// 构造函数
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getter和Setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
生活场景比喻
- 状态码(status):就像菜单卡上的价格,告诉你这道菜的价值。
- 消息(message):就像菜单卡上的描述,告诉你这道菜的特点和风味。
- 数据(data):就像你点的菜品本身,是你真正想要的内容。
设计模式
-
模板方法模式:在
GlobalResponseWrapper
中,使用了模板方法模式,通过定义统一的异常处理和成功响应的封装,确保所有控制器都遵循相同的响应结构。这种方式使得代码更加一致。 -
建造者模式:虽然在这个示例中没有明显使用建造者模式,但可以看到
ApiResponse
类的构造函数允许灵活创建响应对象。将复杂的对象构建过程封装在构造函数中,提升了代码的可读性。
十一、Spring MVC如何处理Date相关的参数
11.1 日期参数处理
在Web应用中,处理日期参数是一项常见需求。Spring MVC提供了多种方式来处理日期参数,例如使用@DateTimeFormat
注解。
11.2 使用@DateTimeFormat
可以在控制器的方法参数上使用@DateTimeFormat
注解来指定日期格式。
@GetMapping("/date")
public String getDate(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
// 处理日期逻辑
return "dateView";
}
11.3 自定义日期格式化器
如果需要更复杂的日期处理,可以自定义格式化器。
@Component
public class CustomDateFormatter implements Formatter<LocalDate> {
@Override
public LocalDate parse(String text, Locale locale) throws ParseException {
return LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
@Override
public String print(LocalDate object, Locale locale) {
return object.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
十二、相关问题
12.1 常见问题
1. Spring MVC的核心组件
1.1 DispatcherServlet
- 作用:DispatcherServlet是Spring MVC的前端控制器,负责接收所有HTTP请求并将其分发到相应的处理器(Controller)。它是整个Spring MVC框架的核心。
1.2 HandlerMapping
- 作用:HandlerMapping用于根据请求的URL查找匹配的Controller。它将请求映射到具体的处理方法,并为DispatcherServlet提供相应的处理器信息。
1.3 Controller
- 作用:Controller是处理请求的核心组件。它接收来自DispatcherServlet的请求,执行业务逻辑,并返回一个ModelAndView对象,封装了模型数据和视图信息。
1.4 HandlerAdapter
- 作用:HandlerAdapter用于调用具体的处理方法。它支持多种类型的处理器(Controller),并且以统一的方式调用这些处理器的方法。
1.5 ViewResolver
- 作用:ViewResolver用于解析视图名称,将其转换为实际的视图实现。它根据返回的视图名称找到对应的视图(如JSP、Thymeleaf等),并将模型数据传递给视图进行渲染。
2. Spring MVC的请求处理流程
- 客户端发送请求:用户通过浏览器发送HTTP请求,目标URL对应于DispatcherServlet。
- DispatcherServlet接收请求:DispatcherServlet作为前端控制器,接收所有请求。
- HandlerMapping查找Controller:DispatcherServlet通过HandlerMapping查找与请求URL匹配的Controller。
- Controller处理请求:找到相应的Controller后,DispatcherServlet调用该Controller的方法处理请求,并返回一个ModelAndView对象。
- HandlerAdapter调用处理方法:HandlerAdapter负责调用具体的处理方法,确保请求的处理逻辑被执行。
- ViewResolver解析视图:DispatcherServlet使用ViewResolver解析返回的视图名称,找到实际的视图实现。
- 渲染视图并返回响应:视图被渲染后,生成的HTML内容被返回给客户端,完成请求处理。
3. 如何在Spring MVC中处理异常?
3.1 全局异常处理的实现方式
使用
@ControllerAdvice
和@ExceptionHandler
注解可以实现全局异常处理,捕获应用中的所有未处理异常,并返回统一的错误响应。3.2 示例代码
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice // 定义全局异常处理 public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) // 捕获所有异常 public ResponseEntity<ApiResponse<String>> handleException(Exception e) { // 创建统一的错误响应 ApiResponse<String> response = new ApiResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), null); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); } }
4. Spring MVC与Spring Boot的区别是什么?
- Spring MVC是一个用于构建Web应用程序的框架,而Spring Boot是一个用于快速构建Spring应用程序的工具,简化了Spring配置。
- Spring Boot提供了自动配置功能,简化了Spring MVC的配置过程,允许开发者快速启动项目。
- Spring Boot集成了Spring MVC,可以通过简单的注解和配置快速构建RESTful API。
5. 如何处理Spring MVC中的跨域请求(CORS)?
- 可以在Spring MVC中通过
@CrossOrigin
注解来处理跨域请求。- 也可以在
WebMvcConfigurer
接口的实现中重写addCorsMappings
方法进行全局配置。示例代码:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 允许所有路径 .allowedOrigins("http://example.com") // 允许的来源 .allowedMethods("GET", "POST", "PUT", "DELETE"); // 允许的方法 } }
6 Spring MVC如何支持文件上传?
- 可以使用
MultipartFile
类来处理文件上传。- 需要在配置文件中设置文件上传的大小限制和其他相关配置。
示例代码:
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController public class FileUploadController { @PostMapping("/upload") public String handleFileUpload(@RequestParam("file") MultipartFile file) { // 处理文件上传 if (!file.isEmpty()) { // 保存文件或其他操作 return "File uploaded successfully: " + file.getOriginalFilename(); } return "File upload failed."; } }
7. Spring MVC如何实现请求参数的验证?
- 可以使用JSR-303/JSR-380(如Hibernate Validator)进行请求参数的验证。
- 在模型类属性上使用注解,如
@NotNull
、@Size
等,结合@Valid
注解进行验证。示例代码:
import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class User { @NotNull(message = "Name cannot be null") @Size(min = 2, max = 30, message = "Name must be between 2 and 30 characters") private String name; // getters and setters } import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @PostMapping("/user") public String createUser(@Valid @RequestBody User user) { return "User created: " + user.getName(); } }
8. Spring MVC如何实现请求的限流?
- 可以使用AOP(面向切面编程)来实现请求限流,记录请求次数并限制频率。
- 还可以使用第三方库,如Bucket4j或Guava RateLimiter,来实现更复杂的限流策略。
示例代码:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class RateLimiterAspect { private final RateLimiter rateLimiter = RateLimiter.create(1.0); // 每秒1个请求 @Before("execution(* com.example.controller.*.*(..))") public void limit() { if (!rateLimiter.tryAcquire()) { throw new RuntimeException("Too many requests"); } } }
9. Spring MVC如何支持异步请求
- 可以使用
@Async
注解将方法标记为异步执行。- 还可以使用
DeferredResult
和Callable
来处理异步请求,避免阻塞。示例代码:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; @RestController public class AsyncController { @GetMapping("/async") public DeferredResult<String> async() { DeferredResult<String> deferredResult = new DeferredResult<>(); // 模拟异步处理 new Thread(() -> { try { Thread.sleep(2000); // 模拟耗时操作 deferredResult.setResult("Async response"); } catch (InterruptedException e) { deferredResult.setErrorResult("Error occurred"); } }).start(); return deferredResult; } }
10. Spring MVC中的拦截器(Interceptor)是什么?如何使用?
- 拦截器用于在请求处理前后进行处理,可以用于日志记录、权限验证等。
- 需要实现
HandlerInterceptor
接口并在配置类中注册拦截器。示例代码:
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; @Component public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 请求处理前逻辑 System.out.println("Request intercepted: " + request.getRequestURI()); return true; // 返回true继续处理,false则停止 } } import org.springframework.beans.factory.annotation.Autowired; 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 { @Autowired private MyInterceptor myInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myInterceptor).addPathPatterns("/**"); // 拦截所有请求 } }