单体架构深度剖析:典型单体架构(Spring MVC)的组件与通信方式
好,咱们接着聊。上一章我们讲了单体架构的整体面貌,这一章,我打算把Spring MVC这个经典的单体架构拿出来,拆开揉碎了给你看。
为什么选Spring MVC?因为它是Java生态里最典型的代表。我这些年见过的单体项目,十有八九都是它。说白了,你搞懂了Spring MVC,就搞懂了绝大多数单体架构的套路。
Spring MVC的核心组件
Spring MVC的架构,其实就围绕几个核心角色转。我习惯把它们分成三层:前端控制器、处理器映射器、视图解析器。嗯,还有模型数据,咱们一个一个说。
1. DispatcherServlet(前端控制器)
这是整个Spring MVC的入口。所有的HTTP请求,第一站就是它。
我记得刚接触Spring MVC时,总觉得这玩意儿像个“二传手”——自己不干活,专门把请求分发给别人。后来才明白,这正是它的高明之处。
它的工作流程很简单:
- 接收请求
- 找合适的处理器(Controller)
- 调用处理器
- 处理返回结果
- 渲染视图
你看,它把整个流程串起来了。没有它,每个Controller都得自己处理这些杂事,那代码就乱套了。
2. HandlerMapping(处理器映射器)
DispatcherServlet拿到请求后,得知道该找谁干活。HandlerMapping就是干这个的。
它维护了一张“请求路径 → 处理器”的映射表。比如:
@RequestMapping("/user/list")
public String listUsers(Model model) {
// 查询用户列表
return "userList";
}
当请求 /user/list 进来时,HandlerMapping会找到 listUsers 这个方法。我见过不少新手在这里踩坑——路径写错了,或者忘了加 @RequestMapping,结果请求一直404。
3. Controller(处理器/控制器)
这是真正干活的地方。Controller负责:
- 解析请求参数
- 调用业务逻辑(Service层)
- 封装模型数据
- 返回视图名称或数据
我个人习惯把Controller写得尽量薄。它只做“调度”,不做“实现”。你想想看,如果Controller里塞满了SQL和业务判断,那维护起来得多痛苦?
4. ModelAndView(模型与视图)
Controller处理完业务后,需要把结果返回给前端。ModelAndView就是装这个的容器。
它包含两部分:
- Model:数据模型,比如用户列表、订单信息
- View:视图名称,比如
userList.jsp
我记得早期做项目时,经常在Model里塞一堆不相关的数据。后来被同事吐槽:“你这Model都快成垃圾堆了。”从那以后,我养成了只放必要数据的习惯。
5. ViewResolver(视图解析器)
Controller返回的是逻辑视图名,比如 "userList"。ViewResolver负责把它解析成真正的视图文件,比如 /WEB-INF/views/userList.jsp。
配置起来很简单:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
这样,你返回 "userList",它自动拼成 /WEB-INF/views/userList.jsp。省事吧?
组件之间的通信方式
搞清楚了组件,咱们再看看它们之间怎么通信的。说白了,就是一条“请求处理流水线”。
1. 请求 → DispatcherServlet
用户发来HTTP请求,Tomcat(或其他Servlet容器)把请求交给DispatcherServlet。
2. DispatcherServlet → HandlerMapping
DispatcherServlet问HandlerMapping:“这个请求该谁处理?”
3. HandlerMapping → Controller
HandlerMapping返回一个HandlerExecutionChain,里面包含了对应的Controller和方法。
4. Controller → Service
Controller调用Service层处理业务逻辑。这里要注意,Controller和Service之间是通过接口通信的。我习惯用 @Autowired 注入Service。
5. Controller → ModelAndView
Service返回结果后,Controller把数据封装到ModelAndView里。
6. DispatcherServlet → ViewResolver
DispatcherServlet拿到ModelAndView后,交给ViewResolver解析视图。
7. ViewResolver → 视图文件
ViewResolver找到JSP文件,渲染成HTML,返回给客户端。
你看,整个流程是单向的、线性的。这也是单体架构的特点——简单、直接、好理解。
一个完整的请求示例
光说理论太枯燥。咱们看个实际例子,你就明白了。
假设用户访问 /user/1,想查看ID为1的用户信息。
// Controller
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "userDetail";
}
}
// Service
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
流程是这样的:
- 请求
/user/1进来,DispatcherServlet接手 - HandlerMapping找到
UserController.getUser方法 - Controller调用
userService.findById(1) - Service从数据库查数据,返回User对象
- Controller把User放进Model,返回
"userDetail" - ViewResolver解析成
/WEB-INF/views/userDetail.jsp - JSP渲染HTML,返回给浏览器
嗯,就是这么简单。但简单背后也有隐患——所有组件都在一个进程里,一个地方出问题,整个应用就挂了。这也是我们后面要讲迁移的原因。
总结
这一章我们剖析了Spring MVC的核心组件和通信方式。说白了,它就是一套“请求处理流水线”:
- DispatcherServlet:总调度
- HandlerMapping:路由表
- Controller:业务调度
- ModelAndView:数据容器
- ViewResolver:视图解析
组件之间通过接口和依赖注入通信,整个流程是线性的、同步的。这种架构简单易懂,适合中小型项目。但当你遇到高并发、复杂业务时,它的局限性就暴露出来了。
下一章,咱们聊聊单体架构的痛点——为什么我们要从它迁移到云原生?