Spring MVC到这里,我们只剩下最后的一块还没说,这篇文章就把剩下的重要模块说一下。首先,我们知道Controller是处理HTTP请求的核心组件,负责执行业务逻辑并返回响应。那Spring MVC是如何调用Controller方法的呢?以及参数是如何绑定以及返回值是如何转换成HTTP响应的。
1. Controller 处理与参数绑定
1.1 背景介绍
我们在写代码的时候 @Controller 或者 @RestController 注解应该不陌生。它的作用就是接收请求,执行业务逻辑,返回视图或者JSON数据格式。
在Spring MVC请求处理流程中,HandlerAdapter负责调用Controller方法,其中RequestMappingHandlerAdapter是很常用的实现,主要来协调以下工作:
- 解析请求参数:将HTTP请求中的数据绑定到Controller方法中的参数
- 调用方法:执行Controller方法中的逻辑
- 处理返回值:将方法返回值转换为HTTP响应
1.2 Controller方法调用流程
Controller 方法的调用由 RequestMappingHandlerAdapter 的 invokeHandlerMethod 方法驱动。以下是该流程的简化概述:
创建调用对象:基于 Controller 方法创建 ServletInvocableHandlerMethod 实例,设置参数解析器和返回值处理器。
- 初始化模型容器:使用
ModelAndViewContainer存储模型数据和视图信息。 - 解析参数并调用方法:解析 HTTP 请求数据,调用 Controller 方法。
- 处理返回值:根据返回值类型生成
ModelAndView或直接写入响应。
以下是 invokeHandlerMethod 的核心源码片段:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
1.3 参数绑定机制
参数绑定是将 HTTP 请求数据(如查询参数、路径变量、请求体等)映射到 Controller 方法参数的过程。Spring MVC 通过 HandlerMethodArgumentResolver 接口实现这一功能。
1.3.1 参数解析接口
上面这个接口里定义了两个关键方法:supportsParameter和resolveArgument,分别是判断参数支不支持与解析参数值。
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
1.3.2 参数解析器与参数解析流程
Spring MVC中提供了多种内置解析器,例如:
@RequestParam:解析查询参数或表单数据。@PathVariable:解析 URL 路径变量。@ModelAttribute:绑定请求数据到 POJO 对象。@RequestBody:解析请求体(如 JSON)并反序列化。
在ServletInvocableHandlerMethod的invokeForRequest方法中,解析的步骤是这样的:
- 遍历方法参数
- 使用合适的解析器调用
resolveArgument获取参数值 - 将参数值传入Controller方法
protected Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}
1.4 返回值处理机制
Controller方法的返回值需要转换为HTTP响应,Spring MVC使用HandlerMethodReturnValueHandler来处理这一过程。
1.4.1 返回值处理接口
HandlerMethodReturnValueHandler定义了两个方法:supportsReturnType和handlerReturnValue,也是一个判断一个处理
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
1.4.2 内置的返回值处理器
处理器与上面的解析器也是对应的:
ModelAndViewMethodReturnValueHandler:处理 ModelAndView 对象。ViewNameMethodReturnValueHandler:处理视图名字符串。RequestResponseBodyMethodProcessor:处理 @ResponseBody 注解的返回值(如 JSON)。
1.4.3 返回值处理流程
在invokeAndHandle方法中,返回值的处理步骤如下:
- 调用Controller方法获取返回值
- 根据返回值类型选择处理器
- 调用
handleReturnValue生成响应
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
1.5 其他关键点
要记住整个参数绑定依赖的是 WebDataBinder 和 ConversionService,前者负责将请求数据绑定到对象且支持验证和类型转换。后者处理字符串到其他类型的转换。
另外这整个过程也是具备高度的扩展性的,开发者可以自定义解析器和处理器。
2. 视图渲染与渲染机制
我们上面说到,MVC请求最后Controller方法通常会返回一个逻辑视图名(例如字符串"home")或者ModelAndView对象。视图解析的任务是将这个逻辑视图名转换为具体的视图对象(View),而渲染则是利用该视图对象将模型数据生成HTTP响应。
2.1 核心组件
ViewReslover是视图解析的核心接口,定义如下:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
根据逻辑视图名和语言环境,返回对应的View对象,有几个常用实现:
InternalResourceViewResolver:用于解析 JSP 视图。ThymeleafViewResolver:用于解析 Thymeleaf 模板。FreeMarkerViewResolver:用于解析 FreeMarker 模板。
而View接口就负责视图的渲染了,主要是将数据模型渲染成HTTP响应
2.2 视图解析与渲染的流程
视图解析与渲染的流程如下:
- Controller 返回结果:Controller 方法返回逻辑视图名(如 "home")或
ModelAndView对象。 - 视图解析:
DispatcherServlet调用配置的ViewResolver,将逻辑视图名解析为 ``View 对象。 - 视图渲染:
View对象调用render方法,将模型数据渲染并写入 HTTP 响应。
这里看一下简化后的render方法:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
} else {
view = mv.getView();
}
view.render(mv.getModelInternal(), request, response);
}
2.3 常用的视图解析器
InternalResourceViewResolver:这是常用的视图解析器,用来解析JSP视图。通过prefix和suffix属性拼接视图路径。
ContentNegotiatingViewResolver:这个解析器就是支持内容协商,根据客户端请求的Accept头或者URL后缀选择合适的视图。意思就是请求头上带json就返回JSON视图,带html就返回HTML视图。
2.4 看个栗子
2.4.1 配置视图解析器
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
@Bean
public ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
}
视图名“home”被解析为/WEB-INF/templates/home.html
2.4.2 渲染JSON数据
通过 @ResponseBody 注解,Controller 可以直接返回 JSON 数据:
@Controller
public class JsonController {
@GetMapping("/user")
public @ResponseBody User getUser() {
return new User("Alice", 25);
}
}
结果:Spring 自动将 User 对象序列化为 JSON,例如 {"name": "Alice", "age": 25}。
3. 总结
这节说到这里也就把MVC的所有重点说的差不多了,整个MVC流程大家一定要心里有数,各个组件的作用也要明白。相信看完大家会有一些收获!
最后,五一快乐!!!