Spring MVC基于MVC设计模式设计,其实现基于Spring IOC容器和Servlet。
Spring MVC的启动
Spring MVC通常运行在Web容器(如Tomcat)中,其启动由Web容器触发。
以下是一个常规的Web应用部署描述文件Web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name></display-name>
<!-- 配置Spring MVC DispatcherServlet -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 初始化参数 -->
<init-param>
<!-- 加载SpringMVC的xml到 spring的上下文容器中 -->
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/springmvc.xml
</param-value>
</init-param>
</servlet>
<!-- 配置DispatcherServlet所需要拦截的 url -->
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 监听spring上下文容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 加载spring的xml配置文件到 spring的上下文容器中 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-*.xml</param-value>
</context-param>
主要分为两部分:
- DispatcherServlet的配置
- ContextLoaderListener的配置
这两部分的配置为Spring MVC启动的入口,也是Web容器与Spring IOC/MVC相耦合的点(通过ServletContext耦合)。其中DispatcherServlet是一个Servlet,具备Servlet的生命周期,ContextLoaderListener实现了ServletContextListener接口,该接口提供了关于ServletContext生命周期的回调方法。在Web容器启动时,将调用Servlet生命周期的init方法,同时其作为宿主环境的上下文ServletContext将触发事件信息使得ServletContextListener监听器调用contextInitialized方法。
首先来看ContextLoaderListner:
@Override
public void contextInitialized(ServletContextEvent event) {
//启动了Spring IOC容器,其配置文件位置在web.xml中已经设定
//通常这个容器中的Bean主要是web开发中的Service层和DAO层相关的类
initWebApplicationContext(event.getServletContext());
}
再看DispatcherServlet,在Servlet启动时将调用init方法。其继承结构如下图:
init()方法在HttpServletBean中定义,该方法调用了在FrameworkServlet中定义的initServletBean()方法,部分代码如下:
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
try {
//启动另外一个IOC容器,该IOC容器配置文件在web.xml中设定过
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
}
该IOC容器中包含的通常是web开发中的Controller层相关的Bean,启动过程中将会将ContextLoaderListener中启动的容器设置为父容器,形成IOC容器体系,在容器体系中获取Bean时,先在子容器中查找,再去父容器中查找。如Controller层中Service的注入,即需要去父容器中查找。
至此,Spring MVC启动了两个IOC容器,其中ContextLoaderListener启动的为父容器(通常负责Service层和DAO层的相关Bean管理),而DispatcherServlet启动的为子容器(通常负责Controller层的相关Bean管理),IOC容器体系建立完毕,同时两个IOC容器通过ServletContext与Web容器(Tomcat)相耦合。
HttpServletBean的init方法最终将调用DispatcherServlet的initStrategies方法,该方法主要用来初始化Spring MVC的主要支持部件:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
重点分析HandlerMapping/HandlerAdapters/ViewResolvers这三者。
initStrategies中的主要工作就是设置DispatcherServlet中相关属性的值,对于handlerMapping/handlerAdapters/ViewResolvers这三者来说,都是先在IOC容器中查找是否已经有配置的各类实例,如果没有则启用默认类。默认值在DispatcherServlet.properties文件中有定义。至此,Spring MVC已经启动完毕。
可见,SpringMVC由tomcat以web.xml里一个Servlet一个Listener的配置触发启动,然后以这两个建立IOC容器体系,最终进行组件的初始化工作,启动完成。
Spring MVC重要组件
HandlerMapping
HandlerMapping接口只有一个方法,getHandler,但是返回的是HandlerExecutionChain,一个执行链。其主要作用是将Http请求的URL映射到对应的handler上,返回的执行链中同时包含了handler本身和对应的拦截器链。handler的类型是多样的,在源码中handler类型为Object,可以是实现了Controller接口的对象,也可以是某个方法。因此,也有多种实现类,有直接以类作为handler的,有以方法作为handler的。
在web开发中在方法上常见的RequestMapping注解handleMethod类型的handler,对应的handlerMapping为RequestMappingHandlerMapping。继承AbstractHandlerMethodMapping,持有请求URL与HandlerMethod之间的映射表。
HandlerAdapter
由于handler的多样性,需要为框架提供更好的可拓展性,使用了适配器模式,通过handlerAdapter来调用handler的handle方法。在handlerMapping中获取到HandlerExecutionChain后,从中取出handler本身,遍历已经DispatcherServlet中初始化过的handlerAdapter找到可以适配的HandlerAdapter。
以RequestMappingHandlerMapping为例,对应的Adapter为RequestMappingHandlerAdapter,该Adapter知道handler类型为HandlerMethod,最终通过反射调用完成请求的处理,返回ModelAndView结果。
ViewResolver/View
视图解析器,ViewResolver接口只有一个方法,即resolveViewName,将一个视图名解析为一个View对象。View对象只是视图在Spring MVC中的表示,并非实际意义上的视图。由于视图的多样化,一类视图解析图解析一类视图对象。以JSP视图为例,其ViewResolver为InternalResourceViewResolver,通常都会如下配置:
<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
InternalResourceViewResolver持有View对象的缓存,如果缓存中不存在,则根据viewname新建View,InternalResourceViewResolver处理的视图对象类为InternalResourceView。
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// 处理视图名称中的重定向前缀
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
// 处理视图名称中的转发前缀
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//通过反射新建了View对象,并设置其URL等属性
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
视图解析完成之后,就是最后的视图渲染工作了,渲染工作由View对象来完成。
InternalResourceView的render方法实现在其基类AbstractView中定义:
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//整合Model对象
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
//设置response响应头
prepareResponse(request, response);
//最终渲染
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
最终渲染部分代码:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 将各种Model值都设置到Http请求的请求属性中去,这里的Model已经
//是经过应用处理过的值,将其巧妙地加入Request的属性中继续传递给视图
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// 通过View对象的URL即可获取到真正的视图路径
String dispatcherPath = prepareForRendering(request, response);
// 这里获取RequestDispatcher,即JSP对应的Servlet对象,并调用service方法响应请求
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}
View对象并不是真正的视图对象,只是视图对象在SpringMVC中的表示,持有了真正视图对象的路径,渲染时通过RequestDispatcher将Model数据传给真正的视图。
Spring MVC对Http请求的处理
DispatcherServlet是spring mvc的核心Servlet,承担了请求转发处理的工作。它的本质是Servlet,其可以处理的请求在web.xml中定义,处理方法即service方法,该方法在父类HttpServlet中实现,并提供了钩子给子类。在DispatcherServlet中,主要处理逻辑在doDispatch方法中,部分代码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 根据请求获取handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取相应的handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//调用handler的拦截器链的preHandle方法,不符合则返回
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 将请求交给handler处理
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(request, mv);
//调用handler的拦截器链的postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
//处理最终结果,主要是将ModelAndView渲染至相应视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
}
processDispatchResult部分代码如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
// 渲染视图
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
}
总结
Spring MVC启动了两个IOC容器用于构建Spring应用,通过DispatcherServlet拦截Http请求,通过handlerMapping和handlerAdapter获取handler处理得到ModelAndView后,再通过View对象将Model包装在Http请求中,通过RequestDispatcher完成请求的进一步转发给真正的视图进行处理并返回http响应。
https://www.javatpoint.com/requestdispatcher-in-servlet
spring技术内幕