Spring MVC重点功能底层源码深度解析

一、Spring MVC核心架构回顾

Spring MVC是Spring框架的Web模块,基于Servlet API构建,采用前端控制器模式。其核心架构围绕DispatcherServlet展开,通过一系列策略接口实现请求处理的高度可配置性。

核心流程图


二、方法参数解析机制详解

2.1 参数解析器体系

Spring MVC通过HandlerMethodArgumentResolver接口实现方法参数的解析,共有近30种实现类处理不同类型的参数:

复制代码
// 核心解析器接口
public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter parameter);
    Object resolveArgument(MethodParameter parameter, 
                          ModelAndViewContainer mavContainer,
                          NativeWebRequest webRequest,
                          WebDataBinderFactory binderFactory) throws Exception;
}

2.2 常见参数解析器

解析器 处理注解 数据来源
RequestParamMethodArgumentResolver @RequestParam或简单类型 请求参数
PathVariableMethodArgumentResolver @PathVariable URL路径变量
RequestHeaderMethodArgumentResolver @RequestHeader 请求头
SessionAttributeMethodArgumentResolver @SessionAttribute Session属性
RequestBodyAdviceArgumentResolver @RequestBody 请求体
ServletModelAttributeMethodProcessor 无(非简单类型) 请求参数+对象绑定

2.3 参数解析流程

复制代码
// 简化版解析流程
public Object resolveArgument(MethodParameter parameter, ...) {
    // 1. 获取参数解析器
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    
    // 2. 解析参数值
    Object arg = resolver.resolveArgument(parameter, ...);
    
    // 3. 类型转换(如果需要)
    if (arg != null && !parameter.getParameterType().isInstance(arg)) {
        arg = typeConverter.convertIfNecessary(arg, parameter.getParameterType());
    }
    
    return arg;
}

2.4 类型转换机制

当参数类型不是String时,Spring MVC需要进行类型转换:

情况一:自定义PropertyEditor
复制代码
// 自定义编辑器
public class StringToUserEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) {
        User user = new User();
        user.setName(text);
        setValue(user);
    }
}

// 注册编辑器
@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(User.class, new StringToUserEditor());
}
情况二:构造方法转换

如果类型有String参数的构造方法,Spring会尝试使用:

复制代码
// User类
public class User {
    private String name;
    
    public User(String name) {  // Spring会尝试使用此构造方法
        this.name = name;
    }
}

// Controller方法
@GetMapping("/test")
public String test(@RequestParam User user) {
    return user.getName();  // "zhouyu" → new User("zhouyu")
}

源码位置TypeConverterDelegate.convertIfNecessary()


三、文件上传(MultipartFile)源码解析

3.1 Servlet原生文件上传

复制代码
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        // 获取所有Part
        Collection<Part> parts = request.getParts();
        
        for (Part part : parts) {
            String header = part.getHeader("content-disposition");
            // form-data; name="file"; filename="test.jpg"
            
            if (part.getSubmittedFileName() != null) {
                // 文件Part
                part.write("/upload/" + part.getSubmittedFileName());
            } else {
                // 文本Part
                String value = request.getParameter(part.getName());
            }
        }
    }
}

3.2 Spring MVC文件上传封装

3.2.1 MultipartResolver

Spring MVC通过MultipartResolver判断是否为文件上传请求:

复制代码
// DispatcherServlet中
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    // 检查是否为multipart请求
    processedRequest = checkMultipart(request);
    // ...
}

private HttpServletRequest checkMultipart(HttpServletRequest request) {
    if (this.multipartResolver != null && 
        this.multipartResolver.isMultipart(request)) {
        // 转换为MultipartHttpServletRequest
        return this.multipartResolver.resolveMultipart(request);
    }
    return request;
}
3.2.2 文件参数解析

当方法参数为MultipartFile类型时:

复制代码
@PostMapping("/upload")
public String upload(MultipartFile file, 
                    @RequestPart String description) {
    // file: 文件Part,封装为MultipartFile
    // description: 表单文本字段
    file.transferTo(new File("/upload/" + file.getOriginalFilename()));
    return "success";
}

注意@RequestPart vs @RequestParam

  • @RequestParam:获取请求参数(包括URL参数和表单字段)

  • @RequestPart:只获取multipart请求的表单字段

3.3 文件上传源码实现

复制代码
// StandardMultipartHttpServletRequest的封装过程
public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {
    
    protected void initializeMultipart() {
        // 获取所有Part
        Collection<Part> parts = getParts();
        
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>();
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        
        for (Part part : parts) {
            String filename = part.getSubmittedFileName();
            
            if (filename != null) {
                // 文件Part → StandardMultipartFile
                files.add(part.getName(), new StandardMultipartFile(part));
            } else {
                // 文本Part
                params.add(part.getName(), getParameter(part.getName()));
            }
        }
        
        setMultipartFiles(files);
        setMultipartParameters(params);
    }
}

四、方法返回值处理机制

4.1 返回值处理器体系

Spring MVC通过HandlerMethodReturnValueHandler处理不同类型的返回值:

复制代码
public interface HandlerMethodReturnValueHandler {
    boolean supportsReturnType(MethodParameter returnType);
    void handleReturnValue(Object returnValue, 
                          MethodParameter returnType,
                          ModelAndViewContainer mavContainer,
                          NativeWebRequest webRequest) throws Exception;
}

4.2 常用返回值处理器

处理器 处理类型 说明
ModelAndViewMethodReturnValueHandler ModelAndView 直接使用ModelAndView
RequestResponseBodyMethodProcessor @ResponseBody 将返回值写入响应体
ViewNameMethodReturnValueHandler String(无@ResponseBody 作为视图名解析
ModelMethodProcessor Model 添加到Model中

4.3 @ResponseBody处理流程

4.3.1 核心处理器:RequestResponseBodyMethodProcessor
复制代码
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
        // 1. 标记请求已处理
        mavContainer.setRequestHandled(true);
        
        // 2. 获取输入输出消息
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
        
        // 3. 使用HttpMessageConverter写入响应
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }
}
4.3.2 HttpMessageConverter选择策略

Spring MVC根据返回值类型请求的Accept头选择合适的Converter:

复制代码
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
                                              ServletServerHttpRequest inputMessage,
                                              ServletServerHttpResponse outputMessage) {
    
    // 获取可接受的MediaType
    List<MediaType> acceptableTypes = getAcceptableMediaTypes(inputMessage);
    
    // 获取支持的MediaType
    List<MediaType> producibleTypes = getProducibleMediaTypes(outputMessage, value, returnType);
    
    // 选择最合适的MediaType
    List<MediaType> mediaTypesToUse = new ArrayList<>();
    for (MediaType requestedType : acceptableTypes) {
        for (MediaType producibleType : producibleTypes) {
            if (requestedType.isCompatibleWith(producibleType)) {
                mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
            }
        }
    }
    
    // 选择Converter并写入
    for (MediaType mediaType : mediaTypesToUse) {
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            if (converter.canWrite(value.getClass(), mediaType)) {
                // 写入响应
                ((HttpMessageConverter<T>) converter).write(value, mediaType, outputMessage);
                return;
            }
        }
    }
}

4.4 自定义HttpMessageConverter

当默认Converter不满足需求时,可以自定义:

复制代码
// 将User对象转为纯文本的自定义Converter
public class UserToTextHttpMessageConverter extends AbstractHttpMessageConverter<User> {
    
    public UserToTextHttpMessageConverter() {
        // 支持所有MediaType
        super(MediaType.ALL);
    }
    
    @Override
    protected boolean supports(Class<?> clazz) {
        return User.class.isAssignableFrom(clazz);
    }
    
    @Override
    protected User readInternal(Class<? extends User> clazz, 
                               HttpInputMessage inputMessage) {
        // 读取逻辑(不需要实现,因为是输出)
        return null;
    }
    
    @Override
    protected void writeInternal(User user, HttpOutputMessage outputMessage) 
        throws IOException, HttpMessageNotWritableException {
        
        // 将User的name字段作为纯文本输出
        String text = "User: " + user.getName();
        outputMessage.getBody().write(text.getBytes(StandardCharsets.UTF_8));
    }
}

// 配置自定义Converter
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new UserToTextHttpMessageConverter());
    }
}

五、Spring MVC父子容器机制

5.1 传统web.xml配置方式

复制代码
<web-app>
    <!-- 1. 父容器(ContextLoaderListener创建) -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    
    <!-- 2. 子容器(DispatcherServlet创建) -->
    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

5.2 父子容器创建流程

5.2.1 父容器创建(ContextLoaderListener)
复制代码
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent event) {
        // 创建父容器
        WebApplicationContext rootContext = initWebApplicationContext(event.getServletContext());
        
        // 存储到ServletContext中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 
                                   rootContext);
    }
}
5.2.2 子容器创建(DispatcherServlet)
复制代码
public abstract class FrameworkServlet extends HttpServletBean {
    
    protected WebApplicationContext initWebApplicationContext() {
        // 1. 获取父容器
        WebApplicationContext rootContext = 
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        
        // 2. 创建子容器
        WebApplicationContext wac = createWebApplicationContext(rootContext);
        
        // 3. 刷新子容器(加载配置、创建Bean)
        configureAndRefreshWebApplicationContext(wac);
        
        return wac;
    }
}

5.3 父子容器分工

容器 配置位置 包含的Bean 作用
父容器 /WEB-INF/applicationContext.xml Service、Repository、DataSource等 业务逻辑层、数据访问层
子容器 /WEB-INF/spring-mvc.xml Controller、HandlerMapping、ViewResolver等 Web层组件

重要规则:子容器可以访问父容器的Bean,但父容器不能访问子容器的Bean。

5.4 父子容器隔离的好处

  1. 职责分离:Web层和业务层明确分离

  2. 配置独立:可以独立配置扫描路径、AOP等

  3. 灵活性:可以配置多个DispatcherServlet共享父容器

  4. 避免冲突:防止Controller被意外注入Service层


六、@EnableWebMvc深度解析

6.1 @EnableWebMvc的作用

复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)  // 关键!
public @interface EnableWebMvc {
}

6.2 DelegatingWebMvcConfiguration

复制代码
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    // 收集所有WebMvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
    
    // 代理所有配置方法
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addInterceptors(registry);
        }
    }
    
    // 其他配置方法类似...
}

6.3 配置Spring MVC的三种方式

方式一:实现WebMvcConfigurer(推荐)
复制代码
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor());
    }
    
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }
}
方式二:继承WebMvcConfigurationSupport
复制代码
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
    // 重写特定方法进行配置
}
方式三:使用@Bean注解
复制代码
@Configuration
public class WebConfig {
    
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        // 自定义HandlerAdapter
        return new RequestMappingHandlerAdapter();
    }
}

6.4 @EnableWebMvc的HandlerMapping顺序

复制代码
// 使用@EnableWebMvc时
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    // RequestMappingHandlerMapping优先级最高
    return new RequestMappingHandlerMapping();
}

@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
    // BeanNameUrlHandlerMapping优先级第二
    BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
    mapping.setOrder(1);  // RequestMappingHandlerMapping默认order为0
    return mapping;
}

重要影响:当同一路径被不同HandlerMapping匹配时,order值小的优先处理。


七、WebApplicationInitializer:无web.xml配置

7.1 替代web.xml的现代方式

复制代码
public class MyWebAppInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) {
        // 1. 创建Spring容器
        AnnotationConfigWebApplicationContext context = 
            new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);
        
        // 2. 创建DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        
        // 3. 注册Servlet
        ServletRegistration.Dynamic registration = 
            servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/*");
    }
}

7.2 工作原理:ServletContainerInitializer

Spring MVC通过SPI机制注册SpringServletContainerInitializer

  1. META-INF/services/javax.servlet.ServletContainerInitializer文件内容:

    复制代码
    org.springframework.web.SpringServletContainerInitializer
  2. SpringServletContainerInitializer实现

    复制代码
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
        
        @Override
        public void onStartup(Set<Class<?>> webAppInitializerClasses, 
                             ServletContext servletContext) {
            // 1. 实例化所有WebApplicationInitializer
            List<WebApplicationInitializer> initializers = new ArrayList<>();
            
            // 2. 排序并执行
            AnnotationAwareOrderComparator.sort(initializers);
            for (WebApplicationInitializer initializer : initializers) {
                initializer.onStartup(servletContext);
            }
        }
    }

八、拦截器(Interceptor)源码解析

8.1 拦截器接口

复制代码
public interface HandlerInterceptor {
    // 前置处理
    default boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) {
        return true;
    }
    
    // 后置处理(渲染视图前)
    default void postHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler,
                           ModelAndView modelAndView) {
    }
    
    // 完成处理(渲染视图后)
    default void afterCompletion(HttpServletRequest request, 
                                HttpServletResponse response, 
                                Object handler,
                                Exception ex) {
    }
}

8.2 拦截器执行流程

复制代码
// HandlerExecutionChain中的执行逻辑
public class HandlerExecutionChain {
    
    boolean applyPreHandle(HttpServletRequest request, 
                          HttpServletResponse response) throws Exception {
        // 顺序执行preHandle
        for (int i = 0; i < this.interceptorList.size(); i++) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 如果返回false,触发afterCompletion(已执行的拦截器)
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;  // 记录最后一个成功的拦截器
        }
        return true;
    }
    
    void applyPostHandle(HttpServletRequest request, 
                        HttpServletResponse response,
                        ModelAndView mv) throws Exception {
        // 逆序执行postHandle
        for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

8.3 拦截器执行时序图

复制代码

8.4 拦截器异常流程时序图(preHandle返回false)

8.5详细执行流程时序图


九、RequestMappingHandlerMapping初始化

9.1 映射注册流程

复制代码
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping {
    
    protected void initHandlerMethods() {
        // 1. 扫描所有Bean
        String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
        
        for (String beanName : beanNames) {
            Class<?> beanType = getType(beanName);
            
            // 2. 判断是否为Handler(有@Controller或@RequestMapping)
            if (isHandler(beanType)) {
                // 3. 解析Handler方法
                detectHandlerMethods(beanName);
            }
        }
    }
    
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = getType(handler);
        
        // 4. 遍历所有方法
        Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(
            handlerType, 
            (Method method) -> getMappingForMethod(method, handlerType)
        );
        
        // 5. 注册映射
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, handlerType);
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

9.2 映射存储结构

复制代码
public class MappingRegistry {
    // 路径→映射信息
    private final Map<String, List<RequestMappingInfo>> pathLookup = new HashMap<>();
    
    // 映射信息→HandlerMethod
    private final Map<RequestMappingInfo, HandlerMethod> mappingLookup = new LinkedHashMap<>();
    
    // HandlerMethod→映射信息
    private final Map<HandlerMethod, RequestMappingInfo> registry = new HashMap<>();
    
    // 跨域配置
    private final Map<RequestMappingInfo, CorsConfiguration> corsLookup = new HashMap<>();
}

十、总结与最佳实践

10.1 核心要点回顾

  1. 参数解析 :通过HandlerMethodArgumentResolver实现,支持类型转换

  2. 文件上传 :基于Servlet的Part API封装为MultipartFile

  3. 返回值处理 :通过HandlerMethodReturnValueHandlerHttpMessageConverter实现

  4. 父子容器:实现Web层与业务层分离,子容器可访问父容器Bean

  5. 配置方式 :支持web.xml、@EnableWebMvcWebApplicationInitializer

  6. 拦截器:提供请求处理的前置、后置、完成三个阶段拦截

10.2 性能优化建议

  1. 合理使用参数注解:明确指定参数来源,减少解析开销

  2. 缓存HandlerMethod:避免重复解析方法元数据

  3. 选择合适的MessageConverter:只添加必要的Converter

  4. 合理配置拦截器:避免在拦截器中执行耗时操作

10.3 常见问题排查

问题 可能原因 解决方案
参数绑定失败 类型转换失败 添加PropertyEditor或Converter
文件上传失败 未配置MultipartResolver 配置CommonsMultipartResolver
中文乱码 字符编码不一致 统一使用UTF-8编码
拦截器不生效 顺序问题或路径匹配 检查order和pathPatterns

10.4 扩展开发指南

  1. 自定义参数解析器 :实现HandlerMethodArgumentResolver

  2. 自定义返回值处理器 :实现HandlerMethodReturnValueHandler

  3. 自定义HttpMessageConverter :继承AbstractHttpMessageConverter

  4. 自定义HandlerMapping :实现HandlerMapping接口

相关推荐
沛沛老爹20 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理
专注_每天进步一点点20 小时前
【java开发】写接口文档的札记
java·开发语言
代码方舟20 小时前
Java企业级实战:对接天远名下车辆数量查询API构建自动化风控中台
java·大数据·开发语言·自动化
zgl_2005377920 小时前
ZGLanguage 解析SQL数据血缘 之 标识提取SQL语句中的目标表
java·大数据·数据库·数据仓库·hadoop·sql·源代码管理
liwulin050620 小时前
【JAVA】创建一个不需要依赖的websocket服务器接收音频文件
java·服务器·websocket
钦拆大仁21 小时前
统一数据返回格式和统一异常处理
java
czlczl2002092521 小时前
OAuth 2.0 解析:后端开发者视角的原理与流程讲解
java·spring boot·后端
颜淡慕潇21 小时前
Spring Boot 3.3.x、3.4.x、3.5.x 深度对比与演进分析
java·后端·架构
g***557521 小时前
Java高级开发进阶教程之系列
java·开发语言