第四部分 SpringMVC的实现
Spring的MVC是基于Servlet功能实现的,通过实现Servlet接口的DispatcherServlet来封装其核心功能实现。
一 ContextLoaderListener:
ContextLoaderListener的作用就是启动web容器时,自动装配ApplicationContext的配置信息。
它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法(系统启动时会调用ServletContextListener实现类的contextInitialized方法)。
使用ServletContextListener接口,开发者能够在为客户端请求提供服务之前向ServletContext中添加任意对象。这个对象在ServletContext启动的时候被初始化,然后再ServletContext整个运行期间都是可见的。
每一个web应用都有一个servletContext与之相关联,ServletContext对象在应用启动时被创建,在应用关闭时被销毁。ServletContext在全局范围内有效,类似于应用中的一个全局变量。
在ContextLoaderListener中的核心逻辑便是初始化WebApplicationContext实例并存放只ServletContext中。
1 ContextLoaderListener中的contextInitialized方法
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
WebApplicationContext继承自ApplicationContext,在ApplicationContext的基础上又追加了一些特定于Web的操作及属性。
initWebApplicationContext函数主要是体现了创建WebApplicationContext实例的一个功能架构。
1)在初始化过程中,程序会首先读取ContextLoader类同目录下的配置文件,提前将要实现WebApplicationContext接口的实现类
Class<?> contextClass = determineContextClass(sc);
2)根据这个实现类通过反射的方式进行实例的创建
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
3)将实例记录在servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
4)映射当前的类加载器与创建的实例到全局变量currentContextPerThread中
currentContextPerThread.put(ccl, this.context);
二 DispatcherServlet:
在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现Servlet接口的实现类。
1 DispatcherServlet的初始化
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
DispatcherServlet的初始化过程主要是通过将当前的Servlet类型实例转换为BeanWapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入。
这些属性如contextAttribute、contextConfiguration等,都可以在web.xml文件中以初始化参数的方式配置在Servlet的声明中。
DispatcherServlet继承自FrameworkServlet,FrameworkServlet类上包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。
属性注入包含以下步骤:
1)封装及验证初始化参数:PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
封装属性主要是对初始化的参数进行封装,也就是servlet中配置的<init-param>中配置的封装。
2)将当前servlet实例转换成BeanWrapper实例:BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
3)注册相对于Resource的属性编辑器:bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
4)属性注入:bw.setPropertyValues(pvs, true);
BeanWrapper支持Spring的自动注入,最常用的属性注入无非是contextAttribure、contextClass、nameSpace、contextConfigLocation
5)servletBean的初始化:initServletBean();
在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,在这个函数中最重要的就是对这个实例进行进一步的补充初始化。
该函数设计了计时器来统计初始化的执行时间,而作为关键的初始化逻辑实现委托给了initWebApplicationContext();
二 WebApplicationContext的初始化:initWebApplicationContext()
initWebApplicationContext()函数的主要工作就是创建或刷新WebApplicationContext实例,并对servlet功能所使用的变量进行初始化。
本函数主要包含以下部分:
1 寻找或创建对应的WebApplicationContext实例
1)如果this.webApplicationContext != null,则可以判定this.webApplicationContext 已经通过构造函数初始化了
2)通过contextAttribute进行初始化:wac = findWebApplicationContext();
在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+”.ROOT”为Key放入ServletContext中
3)重新创建WebApplicationContext
如果以上两种方式并没有找到任何突破,那么只能在这里重新创建新的实例了。
a.获取servlet的初始化参数contextClass,如果没有配置默认为XmlWebApplicationContext.class:Class<?> contextClass = getContextClass();
b.通过反射方式实例化contextClass:ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
c.获取contextConfigLocation属性,配置在servlet初始化参数中:wac.setConfigLocation(getContextConfigLocation());
d.初始化Spring环境,包括加载配置文件等:configureAndRefreshWebApplicationContext(wac);
2 configureAndRefreshWebApplicationContext()
无论是通过构造函数注入还是单独创建,都免不了调用configureAndRefreshWebApplicationContext(wac)方法来对已经创建的WebApplicationContext实例进行配置及刷新。
其实只要使用ApplicationContext所提供的功能,最后还是使用其公共父类AbstractApplicationContext提供的refresh()方法进行配置文件加载
3 刷新:onRefresh(wac);
onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写。主要用于刷新Spring在Web功能实现中所必须使用的全局变量
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
1)初始化MultipartResolver:initMultipartResolver(context);
在Spring中,MultipartResolver主要用来处理文件上传。如果想使用,则需要在web应用的上下文中添加multipart解析器
2)初始化LocaleResolver:initLocaleResolver(context);
Spring中的国际化配置解析器
3)初始化ThemeResolver:initThemeResolver(context);
Spring中的主体解析器。在web开发中经常会遇到通过主题Theme来控制网页风格,这将进一步改善用户体验
4)初始化HandlerMappings:initHandlerMappings(context);
当客户端发出Request时DispatcherServl会将Request提交给HandlerMapping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller。
在基于SpringMVC的Web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后有限使用优先级在前的HandlerMapping。
如果当前的HandlerMapping能够返回可用 的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。
默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean。
5)初始化HandlerAdapters:initHandlerAdapters(context);
这是一个典型的适配器模式的使用。使用适配器,可以使接口不兼容而无法在一起工作的类协同工作。
在getDefaultStrategies函数中,Spring尝试从defaultStrategies中加载对应的HandlerAdapter属性。
在系统加载的时候,defaultStrategies会根据当前路径DispatcherServlet.properties来初始化本身
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
由此得知,如果程序开发人员没有在配置文件中定义自己的适配器,那么Spring会默认加载配置文件中的3个适配器。
作为总控制器的派遣器servlet(DispatcherServlet)通过处理器映射(HandlerMapping)得到处理器(Handler)后,会轮训处理器适配器(HandlerAdapter)模块,查找能够处理当前HTTP请求的处理器适配器的实现。
处理器适配器模块根据处理器映射(HandlerMapping) 返回的处理器类型(例如简单的控制器类型、注解控制器类型等),来选择某一个适当的处理器适配器的实现,从而适配当前的HTTP请求。
6)初始化HandlerExceptionResolvers:initHandlerExceptionResolvers(context);
基于HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现resolveException方法。
换句话说,Spring会搜索所有注册在其环境中的实现了HandlerExceptionResolver接口的bean,逐个执行,直到返回了一个ModelAndWiew对象。
7)初始化tRequestToViewNameTranslator:initRequestToViewNameTranslator(context);
当Controller处理器方法没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。
这个逻辑视图名称是通过Spring定义的RequestToViewNameTranslator接口的getViewName方法实现的。
当我们没有在SpringMVC的配置文件中手动的定义一个名为viewNameTranlator的Bean的时候,Spring就会为我们提供一个默认的viewNameTranlator,即DefaultRequestToViewNameTranslator。
8)初始化ViewResolvers:initViewResolvers(context);
当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。
ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现
三 DispatcherServlet的逻辑处理
HttpServlet类中提供了不同的服务方法,它会根据请求的不同形式将程序引导至对应的函数进行处理。
对于不同的方法,Spring并没有做特殊处理,而是统一将程序再一次引导至processRequest(request,response)中。
1 processRequest(HttpServletRequest request, HttpServletResponse response)所做的事情:
函数中已经开始了对请求的处理,虽然吧细节转移到了doService(request, response)函数中实现,但不难看出处理请求前后所做的准备工作。
1)为了保证当前线程的LocaleContext和RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性:
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
2)根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程:
LocaleContext localeContext = buildLocaleContext(request);
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
initContextHolders(request, localeContext, requestAttributes);
3)委托给doService方法进一步处理:
doService(request, response);
4)请求处理结束后恢复线程到原始状态:
resetContextHolders(request, previousLocaleContext, previousAttributes);
5)请求处理结束后,无论成功与否,发布事件通知:
publishRequestHandledEvent(request, startTime, failureCause);
2 doService(HttpServletRequest request, HttpServletResponse response)方法:
在doService中并没有看到 诸如寻找Handler并进行页面跳转之类的处理逻辑,想法却同样是一些准备工作,但这些准备工作却是必不可少的。
Spring将已经初始化的功能辅助工具变量,比如localeResolver、themeResolver等设置在request属性中,而这些属性会在接下来的处理中派上用场。
将处理的核心逻辑委托给了doDispatch(HttpServletRequest request, HttpServletResponse response)方法。
3 doDispatch(HttpServletRequest request, HttpServletResponse response)方法:
doDispatch函数中展示了Spring请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。
1)MultipartContent类型的request处理:processedRequest = checkMultipart(request)
对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request。
2)根据request信息寻找对应的Handler:mappedHandler = getHandler(processedRequest, false)
在Spring加载的过程中,Spring会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings中。
HandlerExecutionChain getHandler(HttpServletRequest request)的目的就是遍历所有的HandlerMapping,并调用其getHandler方法进行封装处理。
AbstractHandlerMapping中的getHandler方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
a.函数首先会使用getHandlerInternal(request)方法根据request信息获取对应的Handler。
(以SimpleUrlHandlerMapping为例,此步骤就是根据URL找到匹配的Controller并返回)
(以AbstractUrlHandlerMapping为例,在getHandlerInternal中的buildPathExposingHandler函数,将Handler封装成了HandlerExecutionChain。
函数首先构建HandlerExecutionChain类型的实例,然后加入了两个拦截器,这是在Spring中常用的链处理机制)
b.如果没有找到对应的Controller处理器,那么程序会尝试去查找配置中的默认处理器
c.当查找的controller为String类型时,意味着返回的是配置的bean名称,需要根据bean名称查找对应的bean
d.最后通过getHandlerExecutionChain(handler, request)方法对返回的Handler进行封装,以保证满足返回类型的匹配
getHandlerExecutionChain函数最主要的目的是将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。
3)没找到对应的Handler的错误处理:noHandlerFound(processedRequest, response)
一旦遇到没有找到Handler的情况,且用户没有设置默认的Handler来处理请求,就只能通过response向用户返回错误信息。
4)根据当前Handler寻找对应的HandlerAdapter:HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())
对于获取适配器的逻辑,无非就是遍历所有适配器来选择合适的适配器并返回它,而某个适配器是否适用于当前的Handler逻辑被封装在具体的适配器中(ha.supports(handler))
在默认情况下,普通的web请求会交给SimpleControllerHandlerAdapter去处理。
5)缓存处理:
Spring提供对Last-Modified机制的支持,只需要实现LastModified接口。同时保证LastModified接口的getLastModified方法,当内容发生改变时返回最新的修改时间即可。
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
以SimpleControllerHandlerAdapter为例,其getLastModified函数实现为
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
如果服务器的内容在http请求的“If-Modified_since”请求头的时间后没有改变,则会返回304状态码。
6)HandlerInterceptor的处理:
a.Servlet API定义的servlet过滤器可以在servlet处理每个web请求的前后分别对它进行前置和后置处理。
b.有些时候,我们可能指向处理由某些SpringMVC处理程序处理的web请求,并在这些处理程序 返回的模型属性 被传递到视图之前,对他们进行一些操作。
c.Spring允许我们通过“处理拦截”Web请求,进行前置处理和后置处理。处理拦截是针对特殊的处理程序映射进行注册的,它只拦截通过这些处理程序映射的请求。
d.每个“处理拦截”都必须实现HandlerInterceptor接口,它包含三个你需要实现的回调方法:preHandle()、postHandle()和afterHandle()
mappedHandler.applyPreHandle(processedRequest, response);
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mappedHandler.applyPostHandle(processedRequest, response, mv);
7)逻辑处理:mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
对于逻辑处理,其实是通过适配器中转调用Handler并返回视图的。
8)异常视图的处理:
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)中的 mv = processHandlerException(request, response, handler, exception);
这里Spring主要的工作就是将逻辑引导至HandlerExceptionResolver类的resolveException方法。
9)根据视图跳转页面:render(mv, request, response)
浏览器在发送请求后无论成功与否都需要一个反馈,因此在逻辑处理的最后一定会涉及一个页面“跳转”的问题。
a.解析视图名称:view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
DispatcherServlet会根据ModelAndView选择合适的视图来进行渲染,而这一功能就是在resolveViewName函数中完成的
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
b.页面跳转:view.render(mv.getModelInternal(), request, response)
当通过viewName解析到对应的View后,就可以进一步地处理跳转逻辑了。
createMergedOutputModel(model, request, response)主要把将要用到的属性放入request中,以便在其他地方可以直接调用
renderMergedOutputModel(mergedModel, request, response):根据不同的view类型进行页面跳转处理,或做forward跳转,或输出json数据等