Spring MVC - Controller处理与参数绑定、视图解析与渲染机制

156 阅读5分钟

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 方法的调用由 RequestMappingHandlerAdapterinvokeHandlerMethod 方法驱动。以下是该流程的简化概述: 创建调用对象:基于 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 参数解析接口

上面这个接口里定义了两个关键方法:supportsParameterresolveArgument,分别是判断参数支不支持与解析参数值。

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)并反序列化。

ServletInvocableHandlerMethodinvokeForRequest方法中,解析的步骤是这样的:

  • 遍历方法参数
  • 使用合适的解析器调用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定义了两个方法:supportsReturnTypehandlerReturnValue,也是一个判断一个处理

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 其他关键点

要记住整个参数绑定依赖的是 WebDataBinderConversionService,前者负责将请求数据绑定到对象且支持验证和类型转换。后者处理字符串到其他类型的转换。

另外这整个过程也是具备高度的扩展性的,开发者可以自定义解析器和处理器。

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视图。通过prefixsuffix属性拼接视图路径。

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流程大家一定要心里有数,各个组件的作用也要明白。相信看完大家会有一些收获!

最后,五一快乐!!!

image.png