← 返回面试专题导航

🔄 Spring MVC 请求处理流程面试题

深入理解 DispatcherServlet 与请求处理机制

📚 Spring MVC 请求处理核心

1. 详细说明 Spring MVC 的完整请求处理流程?困难

🔍 Spring MVC 请求处理流程

Spring MVC 的核心是 DispatcherServlet(前端控制器),它负责协调各个组件完成请求处理。

📋 完整流程(9个步骤)

1️⃣ 用户发送请求

用户通过浏览器发送 HTTP 请求到服务器,请求被 DispatcherServlet 接收。

# 示例请求
GET http://localhost:8080/users/123

2️⃣ DispatcherServlet 接收请求

DispatcherServlet 作为前端控制器,拦截所有符合配置的请求。

// DispatcherServlet 配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
    // DispatcherServlet 默认拦截 "/"
}

3️⃣ HandlerMapping 查找处理器

DispatcherServlet 调用 HandlerMapping,根据请求 URL 查找对应的 Handler(Controller)。

// HandlerMapping 查找过程
HandlerExecutionChain chain = handlerMapping.getHandler(request);
// chain 包含:Handler + 拦截器链

// 常用的 HandlerMapping 实现:
// 1. RequestMappingHandlerMapping(处理 @RequestMapping)
// 2. BeanNameUrlHandlerMapping(根据 Bean 名称匹配)
// 3. SimpleUrlHandlerMapping(简单 URL 映射)

4️⃣ HandlerAdapter 执行处理器

DispatcherServlet 通过 HandlerAdapter 适配器执行 Handler。

// HandlerAdapter 适配过程
HandlerAdapter adapter = getHandlerAdapter(handler);
ModelAndView mv = adapter.handle(request, response, handler);

// 常用的 HandlerAdapter 实现:
// 1. RequestMappingHandlerAdapter(处理 @RequestMapping 方法)
// 2. HttpRequestHandlerAdapter(处理 HttpRequestHandler)
// 3. SimpleControllerHandlerAdapter(处理 Controller 接口)

5️⃣ 执行拦截器的 preHandle 方法

在执行 Handler 之前,执行拦截器链的 preHandle 方法。

// 拦截器执行
for (HandlerInterceptor interceptor : interceptors) {
    if (!interceptor.preHandle(request, response, handler)) {
        return; // 如果返回 false,中断请求
    }
}

6️⃣ Handler 处理请求

Controller 方法执行业务逻辑,返回 ModelAndView 或数据。

@Controller
@RequestMapping("/users")
public class UserController {
    
    @GetMapping("/{id}")
    public ModelAndView getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        
        ModelAndView mv = new ModelAndView("user/detail");
        mv.addObject("user", user);
        return mv;
    }
    
    // 或者返回 JSON
    @GetMapping("/{id}")
    @ResponseBody
    public User getUserJson(@PathVariable Long id) {
        return userService.findById(id);
    }
}

7️⃣ 执行拦截器的 postHandle 方法

Handler 执行完成后,执行拦截器链的 postHandle 方法。

// 逆序执行 postHandle
for (int i = interceptors.length - 1; i >= 0; i--) {
    interceptors[i].postHandle(request, response, handler, mv);
}

8️⃣ ViewResolver 解析视图

如果返回 ModelAndView,ViewResolver 根据视图名称解析出具体的 View 对象。

// ViewResolver 解析过程
View view = viewResolver.resolveViewName(viewName, locale);

// 常用的 ViewResolver 实现:
// 1. InternalResourceViewResolver(JSP 视图)
// 2. ThymeleafViewResolver(Thymeleaf 模板)
// 3. FreeMarkerViewResolver(FreeMarker 模板)

// 配置示例
@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
}

9️⃣ 渲染视图并返回响应

View 渲染模型数据,生成 HTML 响应返回给客户端。

// 视图渲染
view.render(mv.getModelMap(), request, response);

// 执行拦截器的 afterCompletion 方法
for (int i = interceptors.length - 1; i >= 0; i--) {
    interceptors[i].afterCompletion(request, response, handler, ex);
}

📊 完整流程图

1. 用户发送请求
   ↓
2. DispatcherServlet 接收请求
   ↓
3. HandlerMapping 查找 Handler
   ├─ RequestMappingHandlerMapping
   ├─ BeanNameUrlHandlerMapping
   └─ SimpleUrlHandlerMapping
   ↓
4. 返回 HandlerExecutionChain(Handler + 拦截器)
   ↓
5. HandlerAdapter 适配 Handler
   ├─ RequestMappingHandlerAdapter
   ├─ HttpRequestHandlerAdapter
   └─ SimpleControllerHandlerAdapter
   ↓
6. 执行拦截器 preHandle()
   ↓
7. 执行 Handler(Controller 方法)
   ↓
8. 执行拦截器 postHandle()
   ↓
9. ViewResolver 解析视图
   ├─ InternalResourceViewResolver
   ├─ ThymeleafViewResolver
   └─ FreeMarkerViewResolver
   ↓
10. View 渲染视图
   ↓
11. 执行拦截器 afterCompletion()
   ↓
12. 返回响应给客户端

💻 核心组件代码示例

// DispatcherServlet 核心方法(简化版)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    HandlerExecutionChain mappedHandler = null;
    ModelAndView mv = null;
    
    try {
        // 1. 查找 Handler
        mappedHandler = getHandler(request);
        if (mappedHandler == null) {
            noHandlerFound(request, response);
            return;
        }
        
        // 2. 获取 HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        
        // 3. 执行拦截器 preHandle
        if (!mappedHandler.applyPreHandle(request, response)) {
            return;
        }
        
        // 4. 执行 Handler
        mv = ha.handle(request, response, mappedHandler.getHandler());
        
        // 5. 执行拦截器 postHandle
        mappedHandler.applyPostHandle(request, response, mv);
        
        // 6. 处理结果(视图渲染)
        processDispatchResult(request, response, mappedHandler, mv, null);
        
    } catch (Exception ex) {
        // 异常处理
        processHandlerException(request, response, mappedHandler, ex);
    } finally {
        // 7. 执行拦截器 afterCompletion
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }
}
💡 面试技巧:回答时按照"接收 → 查找 → 适配 → 执行 → 解析 → 渲染"的流程说明,重点强调 DispatcherServlet、HandlerMapping、HandlerAdapter、ViewResolver 四大核心组件的作用。
⚠️ 注意:如果使用 @ResponseBody 或 @RestController,不会经过 ViewResolver 和 View 渲染,而是直接通过 HttpMessageConverter 将返回值转换为 JSON/XML 等格式。
2. Spring MVC 的九大核心组件是什么?各自的作用是什么?中等

📋 Spring MVC 九大核心组件

1️⃣ HandlerMapping(处理器映射器)

作用:根据请求 URL 查找对应的 Handler(Controller)。

// 常用实现
RequestMappingHandlerMapping  // 处理 @RequestMapping 注解
BeanNameUrlHandlerMapping     // 根据 Bean 名称匹配 URL
SimpleUrlHandlerMapping       // 简单 URL 映射

// 使用示例
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userService.findById(id);
}

2️⃣ HandlerAdapter(处理器适配器)

作用:适配不同类型的 Handler,执行具体的处理方法。

// 常用实现
RequestMappingHandlerAdapter   // 处理 @RequestMapping 方法
HttpRequestHandlerAdapter      // 处理 HttpRequestHandler
SimpleControllerHandlerAdapter // 处理 Controller 接口

// 适配器模式:统一调用接口
ModelAndView handle(HttpServletRequest request, 
                   HttpServletResponse response, 
                   Object handler);

3️⃣ HandlerExceptionResolver(异常处理器)

作用:处理 Handler 执行过程中抛出的异常。

// 常用实现
ExceptionHandlerExceptionResolver  // 处理 @ExceptionHandler
ResponseStatusExceptionResolver    // 处理 @ResponseStatus
DefaultHandlerExceptionResolver    // 处理 Spring MVC 标准异常

// 使用示例
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    public ModelAndView handleException(Exception e) {
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("message", e.getMessage());
        return mv;
    }
}

4️⃣ ViewResolver(视图解析器)

作用:根据视图名称解析出具体的 View 对象。

// 常用实现
InternalResourceViewResolver  // JSP 视图
ThymeleafViewResolver        // Thymeleaf 模板
FreeMarkerViewResolver       // FreeMarker 模板

// 配置示例
@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
}

5️⃣ RequestToViewNameTranslator(视图名称转换器)

作用:当 Handler 没有返回视图名称时,根据请求 URL 自动生成视图名称。

// 默认实现:DefaultRequestToViewNameTranslator
// 请求 /users/list → 视图名称 users/list

@GetMapping("/users/list")
public void listUsers(Model model) {
    // 没有返回视图名称,自动使用 "users/list"
    model.addAttribute("users", userService.findAll());
}

6️⃣ LocaleResolver(国际化解析器)

作用:解析客户端的 Locale(语言环境),支持国际化。

// 常用实现
AcceptHeaderLocaleResolver  // 根据 HTTP 请求头
SessionLocaleResolver       // 存储在 Session
CookieLocaleResolver        // 存储在 Cookie

// 配置示例
@Bean
public LocaleResolver localeResolver() {
    SessionLocaleResolver resolver = new SessionLocaleResolver();
    resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
    return resolver;
}

7️⃣ ThemeResolver(主题解析器)

作用:解析应用的主题,支持动态切换主题。

// 常用实现
FixedThemeResolver    // 固定主题
SessionThemeResolver  // Session 存储
CookieThemeResolver   // Cookie 存储

// 配置示例
@Bean
public ThemeResolver themeResolver() {
    SessionThemeResolver resolver = new SessionThemeResolver();
    resolver.setDefaultThemeName("default");
    return resolver;
}

8️⃣ MultipartResolver(文件上传解析器)

作用:处理文件上传请求。

// 常用实现
CommonsMultipartResolver      // 基于 Commons FileUpload
StandardServletMultipartResolver  // 基于 Servlet 3.0

// 配置示例
@Bean
public MultipartResolver multipartResolver() {
    CommonsMultipartResolver resolver = new CommonsMultipartResolver();
    resolver.setMaxUploadSize(10485760); // 10MB
    resolver.setDefaultEncoding("UTF-8");
    return resolver;
}

// 使用示例
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
    file.transferTo(new File("/uploads/" + file.getOriginalFilename()));
    return "success";
}

9️⃣ FlashMapManager(重定向数据管理器)

作用:管理重定向时的临时数据(FlashMap)。

// 默认实现:SessionFlashMapManager
// 将数据存储在 Session 中,重定向后自动删除

@PostMapping("/save")
public String save(User user, RedirectAttributes attributes) {
    userService.save(user);
    
    // 添加 Flash 属性(重定向后可用)
    attributes.addFlashAttribute("message", "保存成功");
    
    return "redirect:/users/list";
}

@GetMapping("/list")
public String list(Model model) {
    // 可以获取 Flash 属性
    String message = (String) model.getAttribute("message");
    return "users/list";
}

📊 九大组件对比表

组件 作用 是否必需
HandlerMapping 查找 Handler ✅ 必需
HandlerAdapter 执行 Handler ✅ 必需
HandlerExceptionResolver 异常处理 ✅ 必需
ViewResolver 解析视图 ⚠️ 视图渲染需要
RequestToViewNameTranslator 视图名称转换 ❌ 可选
LocaleResolver 国际化 ❌ 可选
ThemeResolver 主题切换 ❌ 可选
MultipartResolver 文件上传 ⚠️ 文件上传需要
FlashMapManager 重定向数据 ❌ 可选
💡 记忆技巧:核心三大件(HandlerMapping、HandlerAdapter、ViewResolver)+ 异常处理 + 五个辅助组件。
3. DispatcherServlet 的初始化过程是怎样的?中等

🔍 DispatcherServlet 初始化流程

1️⃣ Servlet 初始化

DispatcherServlet 继承自 HttpServlet,在 Web 容器启动时初始化。

// 继承关系
DispatcherServlet 
  → FrameworkServlet 
    → HttpServletBean 
      → HttpServlet

// 初始化入口
public void init(ServletConfig config) throws ServletException {
    // 1. 设置 ServletConfig
    this.config = config;
    
    // 2. 初始化 Bean 属性
    initBeanWrapper();
    
    // 3. 初始化 Servlet Bean
    initServletBean();
}

2️⃣ 创建 WebApplicationContext

// FrameworkServlet.initServletBean()
protected final void initServletBean() throws ServletException {
    // 创建或刷新 WebApplicationContext
    this.webApplicationContext = initWebApplicationContext();
    
    // 初始化 FrameworkServlet
    initFrameworkServlet();
}

// 创建 WebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
    // 1. 获取根容器(由 ContextLoaderListener 创建)
    WebApplicationContext rootContext = 
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    
    // 2. 创建子容器
    WebApplicationContext wac = createWebApplicationContext(rootContext);
    
    // 3. 刷新容器
    configureAndRefreshWebApplicationContext(wac);
    
    return wac;
}

3️⃣ 初始化九大组件

// DispatcherServlet.onRefresh()
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

// 初始化所有策略组件
protected void initStrategies(ApplicationContext context) {
    // 1. 初始化文件上传解析器
    initMultipartResolver(context);
    
    // 2. 初始化国际化解析器
    initLocaleResolver(context);
    
    // 3. 初始化主题解析器
    initThemeResolver(context);
    
    // 4. 初始化 HandlerMapping
    initHandlerMappings(context);
    
    // 5. 初始化 HandlerAdapter
    initHandlerAdapters(context);
    
    // 6. 初始化异常处理器
    initHandlerExceptionResolvers(context);
    
    // 7. 初始化视图名称转换器
    initRequestToViewNameTranslator(context);
    
    // 8. 初始化视图解析器
    initViewResolvers(context);
    
    // 9. 初始化 FlashMap 管理器
    initFlashMapManager(context);
}

4️⃣ 初始化 HandlerMapping 示例

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    
    if (this.detectAllHandlerMappings) {
        // 从容器中查找所有 HandlerMapping
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerMapping.class, true, false);
        
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        // 只查找名为 "handlerMapping" 的 Bean
        HandlerMapping hm = context.getBean("handlerMapping", HandlerMapping.class);
        this.handlerMappings = Collections.singletonList(hm);
    }
    
    // 如果没有找到,使用默认策略
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
}

📊 初始化流程图

Web 容器启动
   ↓
Servlet.init()
   ↓
HttpServletBean.init()
   ↓
FrameworkServlet.initServletBean()
   ↓
创建 WebApplicationContext
   ├─ 获取根容器(Root WebApplicationContext)
   ├─ 创建子容器(Servlet WebApplicationContext)
   └─ 刷新容器
   ↓
DispatcherServlet.onRefresh()
   ↓
initStrategies() - 初始化九大组件
   ├─ initMultipartResolver()
   ├─ initLocaleResolver()
   ├─ initThemeResolver()
   ├─ initHandlerMappings()
   ├─ initHandlerAdapters()
   ├─ initHandlerExceptionResolvers()
   ├─ initRequestToViewNameTranslator()
   ├─ initViewResolvers()
   └─ initFlashMapManager()
   ↓
初始化完成,等待请求

💻 配置示例

// 1. 传统 web.xml 配置
<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-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

// 2. Spring Boot 自动配置
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// Spring Boot 自动注册 DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
    return new DispatcherServlet();
}
💡 核心要点:
  • DispatcherServlet 在 Servlet 容器启动时初始化
  • 创建 WebApplicationContext(子容器)
  • 初始化九大核心组件
  • 组件可以自定义配置,也可以使用默认策略
4. HandlerMapping 有哪些实现类?它们的作用是什么?中等

📋 HandlerMapping 实现类

1️⃣ RequestMappingHandlerMapping(最常用)

处理 @RequestMapping 注解的 Handler。

@Controller
@RequestMapping("/user")
public class UserController {
    
    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id) {
        return "user";
    }
}

2️⃣ BeanNameUrlHandlerMapping

根据 Bean 名称匹配 URL。

@Component("/hello")
public class HelloController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, 
                                     HttpServletResponse response) {
        return new ModelAndView("hello");
    }
}

3️⃣ SimpleUrlHandlerMapping

通过配置文件映射 URL 到 Handler。

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/welcome">welcomeController</prop>
        </props>
    </property>
</bean>
💡 默认:Spring MVC 默认使用 RequestMappingHandlerMapping。
5. HandlerAdapter 的作用是什么?为什么需要适配器?中等

🔍 HandlerAdapter 作用

作用:适配不同类型的 Handler,统一调用方式。

为什么需要适配器?

Spring MVC 支持多种 Handler 类型:

  • @RequestMapping 注解的方法
  • 实现 Controller 接口的类
  • 实现 HttpRequestHandler 接口的类
  • 实现 Servlet 接口的类

主要实现类

// 1. RequestMappingHandlerAdapter
// 处理 @RequestMapping 注解的方法
@GetMapping("/user")
public String getUser() { }

// 2. HttpRequestHandlerAdapter
// 处理 HttpRequestHandler
public class MyHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, 
                             HttpServletResponse response) {
        // 处理请求
    }
}

// 3. SimpleControllerHandlerAdapter
// 处理 Controller 接口
public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
                                     HttpServletResponse response) {
        return new ModelAndView("view");
    }
}
💡 设计模式:使用了适配器模式,统一不同 Handler 的调用接口。
6. ViewResolver 有哪些常用实现?如何配置?简单

📋 ViewResolver 实现类

1️⃣ InternalResourceViewResolver(JSP)

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
}

// Controller 返回 "user"
// 实际路径:/WEB-INF/views/user.jsp

2️⃣ ThymeleafViewResolver

@Bean
public SpringResourceTemplateResolver templateResolver() {
    SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
    resolver.setPrefix("classpath:/templates/");
    resolver.setSuffix(".html");
    return resolver;
}

3️⃣ FreeMarkerViewResolver

@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
    FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
    resolver.setSuffix(".ftl");
    return resolver;
}

4️⃣ ContentNegotiatingViewResolver

根据请求的 Content-Type 选择合适的 ViewResolver。

💡 Spring Boot:自动配置 Thymeleaf ViewResolver。
7. Spring MVC 如何处理异常?有哪些异常处理方式?中等

📋 异常处理方式

1️⃣ @ExceptionHandler(方法级别)

@Controller
public class UserController {
    
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id) {
        if (id < 0) {
            throw new IllegalArgumentException("ID 不能为负数");
        }
        return "user";
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public ModelAndView handleIllegalArgument(IllegalArgumentException ex) {
        ModelAndView mav = new ModelAndView("error");
        mav.addObject("message", ex.getMessage());
        return mav;
    }
}

2️⃣ @ControllerAdvice(全局异常处理)

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    public ModelAndView handleException(Exception ex) {
        ModelAndView mav = new ModelAndView("error");
        mav.addObject("message", ex.getMessage());
        return mav;
    }
    
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleNullPointer() {
        return "null-pointer-error";
    }
}

3️⃣ @ResponseStatus

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "用户不存在")
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

4️⃣ SimpleMappingExceptionResolver

@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
    SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
    Properties mappings = new Properties();
    mappings.setProperty("NullPointerException", "error/null");
    mappings.setProperty("Exception", "error/generic");
    resolver.setExceptionMappings(mappings);
    return resolver;
}
💡 推荐:使用 @ControllerAdvice 实现全局异常处理。
8. Spring MVC 如何实现文件上传?有哪些注意事项?中等

📤 文件上传实现

1. 配置 MultipartResolver

@Bean
public MultipartResolver multipartResolver() {
    CommonsMultipartResolver resolver = new CommonsMultipartResolver();
    resolver.setMaxUploadSize(10485760); // 10MB
    resolver.setMaxInMemorySize(1048576); // 1MB
    resolver.setDefaultEncoding("UTF-8");
    return resolver;
}

// Spring Boot 配置
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

2. Controller 处理

@Controller
public class FileUploadController {
    
    // 单文件上传
    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "error";
        }
        
        try {
            String filename = file.getOriginalFilename();
            String path = "/uploads/" + filename;
            file.transferTo(new File(path));
            return "success";
        } catch (IOException e) {
            return "error";
        }
    }
    
    // 多文件上传
    @PostMapping("/batch-upload")
    public String batchUpload(@RequestParam("files") MultipartFile[] files) {
        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                // 保存文件
            }
        }
        return "success";
    }
}

3. 前端表单

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <button type="submit">上传</button>
</form>
⚠️ 注意事项:
  • 设置文件大小限制
  • 验证文件类型
  • 防止文件名冲突(使用 UUID)
  • 处理上传异常
9. @Controller 和 @RestController 有什么区别?简单

📊 @Controller vs @RestController

@Controller

返回视图名称,需要配合 ViewResolver 解析视图。

@Controller
public class UserController {
    
    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("name", "张三");
        return "user"; // 返回视图名称
    }
    
    // 如果要返回 JSON,需要加 @ResponseBody
    @GetMapping("/api/user")
    @ResponseBody
    public User getUserJson() {
        return new User("张三", 25);
    }
}

@RestController

相当于 @Controller + @ResponseBody,直接返回数据(JSON/XML)。

@RestController
public class UserRestController {
    
    @GetMapping("/api/user")
    public User getUser() {
        return new User("张三", 25); // 自动转换为 JSON
    }
    
    @GetMapping("/api/users")
    public List<User> getUsers() {
        return Arrays.asList(
            new User("张三", 25),
            new User("李四", 30)
        );
    }
}

// 等价于
@Controller
@ResponseBody
public class UserRestController { }
💡 使用场景:
  • @Controller:传统 MVC 项目,返回页面
  • @RestController:RESTful API,返回 JSON 数据
10. Spring MVC 如何处理跨域请求(CORS)?中等

🌐 CORS 跨域处理

1️⃣ @CrossOrigin 注解(方法/类级别)

@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {
    
    @GetMapping("/api/user")
    public User getUser() {
        return new User();
    }
    
    // 方法级别配置
    @CrossOrigin(
        origins = {"http://localhost:3000", "http://example.com"},
        methods = {RequestMethod.GET, RequestMethod.POST},
        maxAge = 3600
    )
    @PostMapping("/api/user")
    public User createUser(@RequestBody User user) {
        return user;
    }
}

2️⃣ 全局配置

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

3️⃣ 使用过滤器

@Bean
public CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.addAllowedOrigin("http://localhost:3000");
    config.addAllowedMethod("*");
    config.addAllowedHeader("*");
    config.setAllowCredentials(true);
    
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    
    return new CorsFilter(source);
}
💡 推荐:使用全局配置,统一管理 CORS 策略。
11. Spring MVC 的父子容器是什么?如何理解?困难

🔍 父子容器机制

容器层次

┌─────────────────────────────────┐
│  Root ApplicationContext        │  父容器
│  (Spring 容器)                   │
│  - Service                      │
│  - Repository                   │
│  - DataSource                   │
└─────────────────────────────────┘
              ↑
              │ 父子关系
              │
┌─────────────────────────────────┐
│  WebApplicationContext          │  子容器
│  (Spring MVC 容器)               │
│  - Controller                   │
│  - HandlerMapping               │
│  - ViewResolver                 │
└─────────────────────────────────┘

配置示例

<!-- web.xml -->
<!-- 1. 配置 Spring 容器(父容器)-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 2. 配置 Spring MVC 容器(子容器)-->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
</servlet>

Bean 扫描配置

<!-- applicationContext.xml(父容器)-->
<context:component-scan base-package="com.example">
    <!-- 排除 Controller -->
    <context:exclude-filter type="annotation" 
        expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!-- spring-mvc.xml(子容器)-->
<context:component-scan base-package="com.example.controller">
    <!-- 只扫描 Controller -->
    <context:include-filter type="annotation" 
        expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

访问规则

  • 子容器可以访问父容器的 Bean
  • 父容器不能访问子容器的 Bean
  • Controller 可以注入 Service
  • Service 不能注入 Controller
⚠️ 注意:Spring Boot 默认只有一个容器,不存在父子容器。
12. Spring MVC 如何实现 RESTful 风格的 API?中等

🌐 RESTful API 实现

RESTful 风格

@RestController
@RequestMapping("/api/users")
public class UserRestController {
    
    // GET /api/users - 查询所有用户
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    // GET /api/users/1 - 查询单个用户
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    // POST /api/users - 创建用户
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
    
    // PUT /api/users/1 - 更新用户
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        return userService.update(user);
    }
    
    // DELETE /api/users/1 - 删除用户
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.delete(id);
    }
    
    // PATCH /api/users/1 - 部分更新
    @PatchMapping("/{id}")
    public User patchUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
        return userService.patch(id, updates);
    }
}

统一响应格式

@Data
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    
    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(200);
        response.setMessage("success");
        response.setData(data);
        return response;
    }
}

@GetMapping("/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    return ApiResponse.success(user);
}

HTTP 状态码

@PostMapping
@ResponseStatus(HttpStatus.CREATED) // 201
public User createUser(@RequestBody User user) {
    return userService.save(user);
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) // 204
public void deleteUser(@PathVariable Long id) {
    userService.delete(id);
}
💡 RESTful 规范:
  • 使用名词表示资源(/users 而不是 /getUsers)
  • 使用 HTTP 方法表示操作(GET/POST/PUT/DELETE)
  • 使用正确的 HTTP 状态码
  • 统一的响应格式
13. Spring MVC 如何实现国际化(i18n)?中等

🌍 国际化实现

1. 配置 MessageSource

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
}

2. 创建资源文件

# messages_zh_CN.properties
welcome=欢迎
user.name=用户名
user.age=年龄

# messages_en_US.properties
welcome=Welcome
user.name=Username
user.age=Age

3. 配置 LocaleResolver

// 方式1:基于 Session
@Bean
public LocaleResolver localeResolver() {
    SessionLocaleResolver resolver = new SessionLocaleResolver();
    resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
    return resolver;
}

// 方式2:基于 Cookie
@Bean
public LocaleResolver localeResolver() {
    CookieLocaleResolver resolver = new CookieLocaleResolver();
    resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
    resolver.setCookieName("language");
    return resolver;
}

// 方式3:基于请求头
@Bean
public LocaleResolver localeResolver() {
    AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
    resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
    return resolver;
}

4. 配置 LocaleChangeInterceptor

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang"); // 参数名
        registry.addInterceptor(interceptor);
    }
}

// 访问:http://localhost:8080/user?lang=en_US

5. 使用国际化

@Controller
public class UserController {
    
    @Autowired
    private MessageSource messageSource;
    
    @GetMapping("/welcome")
    public String welcome(Model model, Locale locale) {
        String message = messageSource.getMessage("welcome", null, locale);
        model.addAttribute("message", message);
        return "welcome";
    }
}

// JSP 中使用
<spring:message code="welcome" />
14. Spring MVC 如何实现重定向和转发?有什么区别?简单

🔄 重定向 vs 转发

转发(Forward)

@Controller
public class UserController {
    
    // 方式1:返回字符串
    @GetMapping("/user")
    public String getUser() {
        return "forward:/user/detail"; // 转发到 /user/detail
    }
    
    // 方式2:使用 RequestDispatcher
    @GetMapping("/user2")
    public void getUser2(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        request.getRequestDispatcher("/user/detail").forward(request, response);
    }
}

重定向(Redirect)

@Controller
public class UserController {
    
    // 方式1:返回字符串
    @PostMapping("/user")
    public String createUser() {
        // 保存用户
        return "redirect:/user/list"; // 重定向到 /user/list
    }
    
    // 方式2:使用 RedirectAttributes 传递参数
    @PostMapping("/user2")
    public String createUser2(RedirectAttributes attributes) {
        attributes.addAttribute("id", 1); // URL 参数
        attributes.addFlashAttribute("message", "创建成功"); // Flash 属性
        return "redirect:/user/detail";
    }
    
    // 方式3:重定向到外部 URL
    @GetMapping("/external")
    public String redirectExternal() {
        return "redirect:https://www.example.com";
    }
}

区别对比

特性 转发(Forward) 重定向(Redirect)
请求次数 1次 2次
URL 变化 不变 改变
数据共享 Request 域共享 不共享
跨域 不能 可以
💡 使用场景:
  • 转发:内部跳转,数据共享
  • 重定向:POST 后跳转(防止重复提交)、跨域跳转
15. Spring MVC 的执行流程源码分析(DispatcherServlet.doDispatch)困难

🔍 doDispatch 源码分析

核心方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    ModelAndView mv = null;
    Exception dispatchException = null;
    
    try {
        // 1. 检查是否文件上传请求
        processedRequest = checkMultipart(request);
        
        // 2. 根据请求获取 Handler(HandlerMapping)
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }
        
        // 3. 根据 Handler 获取 HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        
        // 4. 执行拦截器的 preHandle 方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
        
        // 5. 执行 Handler(Controller 方法)
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        
        // 6. 设置默认视图名
        applyDefaultViewName(processedRequest, mv);
        
        // 7. 执行拦截器的 postHandle 方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        
    } catch (Exception ex) {
        dispatchException = ex;
    }
    
    // 8. 处理结果(渲染视图或处理异常)
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) {
    
    // 9. 如果有异常,处理异常
    if (exception != null) {
        mv = processHandlerException(request, response, handler, exception);
    }
    
    // 10. 渲染视图
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
    }
    
    // 11. 执行拦截器的 afterCompletion 方法
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

关键步骤详解

// 1. getHandler - 获取 Handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

// 2. getHandlerAdapter - 获取适配器
protected HandlerAdapter getHandlerAdapter(Object handler) {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler");
}

// 3. render - 渲染视图
protected void render(ModelAndView mv, HttpServletRequest request, 
                     HttpServletResponse response) {
    View view;
    String viewName = mv.getViewName();
    
    if (viewName != null) {
        // 通过 ViewResolver 解析视图
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    } else {
        view = mv.getView();
    }
    
    // 渲染视图
    view.render(mv.getModelInternal(), request, response);
}

执行流程图

doDispatch 执行流程:
1. checkMultipart()          → 检查文件上传
2. getHandler()              → 获取 Handler
3. getHandlerAdapter()       → 获取 HandlerAdapter
4. applyPreHandle()          → 拦截器前置处理
5. ha.handle()               → 执行 Controller 方法
6. applyPostHandle()         → 拦截器后置处理
7. processDispatchResult()   → 处理结果
   ├─ processHandlerException() → 处理异常
   ├─ render()                  → 渲染视图
   └─ triggerAfterCompletion()  → 拦截器完成处理
💡 核心要点:
  • DispatcherServlet 是整个流程的核心控制器
  • 使用责任链模式处理拦截器
  • 使用适配器模式支持多种 Handler 类型
  • 使用策略模式支持多种视图解析器