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
导致偶发异常?
-
视图解析依赖文件系统 当使用
@Controller
返回视图名时:return "jobPromotion/syncIndustriallnjuryByStaff";
Spring 会尝试定位物理文件:
/WEB-INF/views/jobPromotion/syncIndustriallnjuryByStaff.jsp
偶发问题根源:文件系统操作不可靠(尤其在云环境/容器中) -
典型故障场景:
- 热部署时:IDE 未及时同步文件到
target/classes
- 集群环境:某节点文件同步延迟
- 文件锁冲突:JSP 编译时其他进程占用文件
- 类加载器隔离:DevTools 重启后资源加载不同步
- 热部署时:IDE 未及时同步文件到
-
视图解析器的脆弱性:
// 伪代码:视图解析过程 View resolveView(String viewName) { String physicalPath = prefix + viewName + suffix; if(!Files.exists(physicalPath)) { // 文件存在性检查 throw new ServletException("Could not resolve view..."); } }
文件存在性检查 受外部因素影响(磁盘IO、权限、同步延迟)
为什么改为 @RestController
解决问题?
-
绕过视图解析环节
@RestController
的@ResponseBody
机制直接写入响应体,完全跳过了 ViewResolver 流程 -
消除文件系统依赖 不再需要访问物理文件:
graph LR A[请求] --> B[DispatcherServlet] B --> C[控制器] C --> D[返回值序列化] D --> E[HttpResponse]
-
处理过程更稳定:
- 纯内存操作(无磁盘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响应体返回
}
}
根本转变: 从 视图名称 → 变为 原始字符串数据 避免了不可靠的文件系统操作,消除了视图解析环节的偶发故障。
生产环境建议
-
统一技术栈:
- 前后端分离:全部使用
@RestController
- 服务端渲染:用现代模板引擎(Thymeleaf)替代 JSP
- 前后端分离:全部使用
-
JSP 替代方案:
// 改用 Thymeleaf implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
-
监控措施:
@Bean public ServletContextInitializer servletContextMonitor() { return servletContext -> { logger.info("视图目录状态: {}", Files.exists(Paths.get(servletContext.getRealPath("/WEB-INF/views")))); }; }
-
防御式编程:
@Controller public class SafeController { @GetMapping("/safe") public String safeView(HttpServletResponse response) { // 检查视图文件是否存在 if(!checkViewExists("my-view")) { response.setStatus(503); return null; } return "my-view"; } }
通过理解这两种注解的本质区别,您可以根据实际场景选择合适的技术方案,从根本上避免此类偶发故障。