Home
img of docs

due to exception [Could not resolve view with name 'con/xxx' in servlet with name 'dispatcherServlet'] javax.servlet.ServletException

chou403

/ Default

/ c:

/ u:

/ 6 min read


一学一个不吱声

@Controller 改为 @RestController 后问题解决,这揭示了 Spring MVC 处理机制的核心差异。以下是关键区别分析和问题原因:

@Controller vs @RestController 核心区别

特性@Controller@RestController
本质普通 Spring MVC 控制器注解@Controller + @ResponseBody 的组合注解
返回值处理返回视图名称(由 ViewResolver 解析)直接序列化返回值到 HTTP 响应体
响应类型HTML 视图渲染JSON/XML 等数据格式
适用场景传统 Web 页面渲染RESTful API 开发
方法注解通常配合 @ResponseBody 返回数据所有方法自动应用 @ResponseBody

返回值处理机制对比

1. @Controller 工作流程

   @Controller
public class MyController {
    @GetMapping("/page")
    public String showPage() {
        return "view-name"; // 交由 ViewResolver 解析为物理视图
    }
}

处理链: 请求 → DispatcherServlet → 控制器 → 返回字符串 → ViewResolver 解析 → 渲染视图 → 响应

2. @RestController 工作流程

   @RestController
public class MyApi {
    @GetMapping("/data")
    public Object getData() {
        return new MyDTO(); // 直接序列化为 JSON 写入响应体
    }
}

处理链: 请求 → DispatcherServlet → 控制器 → 直接序列化返回值 → 响应

问题原因分析

为什么 @Controller 导致偶发异常?

  1. 视图解析依赖文件系统 当使用 @Controller 返回视图名时:

       return "jobPromotion/syncIndustriallnjuryByStaff";

    Spring 会尝试定位物理文件: /WEB-INF/views/jobPromotion/syncIndustriallnjuryByStaff.jsp 偶发问题根源:文件系统操作不可靠(尤其在云环境/容器中)

  2. 典型故障场景

    • 热部署时:IDE 未及时同步文件到 target/classes
    • 集群环境:某节点文件同步延迟
    • 文件锁冲突:JSP 编译时其他进程占用文件
    • 类加载器隔离:DevTools 重启后资源加载不同步
  3. 视图解析器的脆弱性

       // 伪代码:视图解析过程
    View resolveView(String viewName) {
        String physicalPath = prefix + viewName + suffix;
        if(!Files.exists(physicalPath)) { // 文件存在性检查
            throw new ServletException("Could not resolve view...");
        }
    }

    文件存在性检查 受外部因素影响(磁盘IO、权限、同步延迟)

为什么改为 @RestController 解决问题?

  1. 绕过视图解析环节 @RestController@ResponseBody 机制直接写入响应体,完全跳过了 ViewResolver 流程

  2. 消除文件系统依赖 不再需要访问物理文件:

       graph LR
    A[请求] --> B[DispatcherServlet]
    B --> C[控制器]
    C --> D[返回值序列化]
    D --> E[HttpResponse]
  3. 处理过程更稳定

    • 纯内存操作(无磁盘IO)
    • 不依赖外部文件状态
    • 避免 JSP 编译器等第三方组件错误

深度技术对比

1. HTTP 响应头差异

@Controller 返回视图:

   HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8

<!DOCTYPE html>...

@RestController 返回数据:

   HTTP/1.1 200 OK
Content-Type: application/json

{"data": "value"}

2. 异常处理差异

  • @Controller 视图解析异常:ServletException
  • @RestController 序列化异常:HttpMessageNotWritableException

3. 性能影响

指标@Controller@RestController
请求延迟较高(需文件IO)较低(纯内存操作)
CPU消耗高(JSP编译)低(JSON序列化)
扩展性难(依赖服务器环境)易(纯数据)

最佳实践建议

何时使用 @Controller

  • 需要服务端渲染 HTML 页面
  • 使用模板引擎(Thymeleaf、Freemarker)
  • 传统多页面应用(MPA)

何时使用 @RestController

  • 构建 RESTful API
  • 前后端分离架构
  • 移动端/第三方接口
  • 需要稳定数据输出的场景

混合使用方案

   @Controller // 主注解
public class HybridController {

    // 返回 HTML 视图
    @GetMapping("/page")
    public String htmlPage() {
        return "template-page";
    }

    // 返回 JSON 数据
    @GetMapping("/api/data")
    @ResponseBody // 单个方法覆盖
    public ApiResponse data() {
        return new ApiResponse();
    }
}

问题解决的本质

您的案例中:

   // 修改前:依赖视图解析
@Controller
public class SyncController {
    @GetMapping("/sync")
    public String sync() {
        return "jobPromotion/syncIndustriallnjuryByStaff";
    }
}

// 修改后:绕过视图解析
@RestController // 关键修改
public class SyncController {
    @GetMapping("/sync")
    public String sync() {
        return "jobPromotion/syncIndustriallnjuryByStaff";
        // 现在这个字符串直接作为HTTP响应体返回
    }
}

根本转变: 从 视图名称 → 变为 原始字符串数据 避免了不可靠的文件系统操作,消除了视图解析环节的偶发故障。

生产环境建议

  1. 统一技术栈

    • 前后端分离:全部使用 @RestController
    • 服务端渲染:用现代模板引擎(Thymeleaf)替代 JSP
  2. JSP 替代方案

       // 改用 Thymeleaf
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
  3. 监控措施

       @Bean
    public ServletContextInitializer servletContextMonitor() {
        return servletContext -> {
            logger.info("视图目录状态: {}",
                Files.exists(Paths.get(servletContext.getRealPath("/WEB-INF/views"))));
        };
    }
  4. 防御式编程

       @Controller
    public class SafeController {
        @GetMapping("/safe")
        public String safeView(HttpServletResponse response) {
            // 检查视图文件是否存在
            if(!checkViewExists("my-view")) {
                response.setStatus(503);
                return null;
            }
            return "my-view";
        }
    }

通过理解这两种注解的本质区别,您可以根据实际场景选择合适的技术方案,从根本上避免此类偶发故障。