Spring-19 SpringMVC 前端控制器模式

44 阅读4分钟

Spring-19 SpringMVC 前端控制器模式

Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。

众所周知 DispatcherServletSpringMVC 的核心入口,它负责将其他的组件组合起来协同工作(这种代码通常也被称为胶水代码)。它最核心的功能是将一次 HTTP 请求路由与之匹配的 控制器方法 也就是 Controller 类中的某个 请求映射@RequestMapping)方法,简单的来说就是对一次请求的路由。

前端控制器模式 Front Controller Pattern

前端控制器模式(Front Controller Pattern)是一种用于处理 Web 应用程序请求的设计模式。在 JavaEE 中,前端控制器模式通过一个统一的入口点来接收和处理所有的请求,并将它们分发给相应的处理程序或控制器。 在这种模式中,前端控制器充当了应用程序的中央处理器,负责接收用户请求、执行必要的处理逻辑,并将结果返回给用户。它可以处理各种类型的请求,例如 HTTP 请求、SOAP 请求等。 前端控制器模式的主要优点是提供了集中式的请求处理机制,可以更好地管理和控制请求的处理流程。它可以帮助减少代码重复,提高代码的可维护性和可扩展性。此外,前端控制器还可以实现一些通用的功能,如身份验证、日志记录等,从而减少在每个处理程序中重复编写这些功能的需求。 总而言之,JavaEE 中的前端控制器模式是一种用于处理 Web 应用程序请求的设计模式,它通过一个中央控制器来接收和处理各种类型的请求,并提供更好的代码管理和控制流程。

我经常跟别人说其实写框架很简单,框架的本质就是为了专门解决某一类问题的产品,你只需要专注于这一类问题就够了,而不像业务代码需要关注的点很多而且杂乱不堪。只不过是框架内部把脏活累活都干了,所以我们用起来才会觉得简单。如果某一天你想写一个框架,你只需要把目标问题域尽可能的了解,然后把过程中的脏活累活都干了,当然还需要很多的技巧和优雅的设计。

SpringMVCDispatcherServlet 担任了 前端控制器 的角色。

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】, 方便在微信客户端阅读。

DevX 不止于技术