Spring-19 SpringMVC 前端控制器模式
Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。
众所周知 DispatcherServlet
是 SpringMVC
的核心入口,它负责将其他的组件组合起来协同工作(这种代码通常也被称为胶水代码)。它最核心的功能是将一次 HTTP
请求路由与之匹配的 控制器方法
也就是 Controller
类中的某个 请求映射
(@RequestMapping
)方法,简单的来说就是对一次请求的路由。
前端控制器模式 Front Controller Pattern
前端控制器模式(Front Controller Pattern)是一种用于处理 Web 应用程序请求的设计模式。在 JavaEE 中,前端控制器模式通过一个统一的入口点来接收和处理所有的请求,并将它们分发给相应的处理程序或控制器。 在这种模式中,前端控制器充当了应用程序的中央处理器,负责接收用户请求、执行必要的处理逻辑,并将结果返回给用户。它可以处理各种类型的请求,例如 HTTP 请求、SOAP 请求等。 前端控制器模式的主要优点是提供了集中式的请求处理机制,可以更好地管理和控制请求的处理流程。它可以帮助减少代码重复,提高代码的可维护性和可扩展性。此外,前端控制器还可以实现一些通用的功能,如身份验证、日志记录等,从而减少在每个处理程序中重复编写这些功能的需求。 总而言之,JavaEE 中的前端控制器模式是一种用于处理 Web 应用程序请求的设计模式,它通过一个中央控制器来接收和处理各种类型的请求,并提供更好的代码管理和控制流程。
我经常跟别人说其实写框架很简单,框架的本质就是为了专门解决某一类问题的产品,你只需要专注于这一类问题就够了,而不像业务代码需要关注的点很多而且杂乱不堪。只不过是框架内部把脏活累活都干了,所以我们用起来才会觉得简单。如果某一天你想写一个框架,你只需要把目标问题域尽可能的了解,然后把过程中的脏活累活都干了,当然还需要很多的技巧和优雅的设计。
在 SpringMVC
中 DispatcherServlet
担任了 前端控制器
的角色。
DispatcherServlet 的关键方法
这里不会去逐行的阅读分析源码,感兴趣的读者可以自己去仔细研究,只是把一些比较核心重要的方法标识出来,帮助读者明确目的的去学习研究。
DispatcherServlet#initStrategies 初始化组件实现
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
AbstractHandlerMethodMapping#initHandlerMethods
在初始化 HandlerMapping
的时候会去检测项目中的 控制器方法
.
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
DispatcherServlet#doDispatch 路由请求
太多了不贴代码了。
DispatcherServlet#getHandler 寻找控制器方法
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
DispatcherServlet#getHandlerAdapter 将 HTTP Message 转换为 Java Object
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
DispatcherServlet#render 渲染视图
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
以上这些方法从初始化到一次 HTTP
请求的处理的核心过程,过程中还有很多细节这里不展开。
DevX
会持续分享有趣的技术和见闻,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。