您的位置:首页 > 编程语言 > Java开发

spring源码剖析(九)springMVC源码剖析

2017-02-26 14:02 246 查看
           springMVC 相信大伙都用过,但是spring框架对于你请求的一个url 到你看到的返回结果,期间做了哪些出来呢,文件上传的封装?controller的寻找?过滤器的调用?AOP的调用?视图的解析?页面的跳转?  这些过程具体是怎么实现的,下面我们一一来向大家介绍springMVC的架构。

让我们来先从SpringMVC的初始化说起吧

SpringMVC的初始化

ContextLoaderListener初始化

我们先看看ContextLoaderListener大体的初始化逻辑时序图:



一般来说我们配置spring的时候都会在web.xml里面加上下面这段配置:
<!--Spring ApplicationContext 载入 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
添加这段配置的目的在于,启动项目的时候让系统去回调ContextLoaderListener的contextInitialized()方法。

我们先来看看ContextLoaderListener的类继承的关系



我们看到ContextLoaderListener 继承了ServletContextListener ,由于系统启动的时候会回调ServletContextListener 的contextInitialized()方法, 所以我们可以知道,当容器启动的时候就会调用ContextLoaderListener 的contextInitialized()方法,如下:
/**
* Initialize the root web application context.
*/
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}


上面这段回调的具体作用在用,项目启动的时候让系统去初始化WebApplicationContext,至于为什么要初始化WebApplicationContext呢,因为我们的DispatcherServlet需要用到它。

DispatcherServlet的初始化

让我们先看看DispatcherServlet的类继承结构先



先浏览DispatcherServlet的初始化逻辑时序图:



我们看到DispatcherServlet继承了HttpServlet类,关于HttpServlet,让我们来回顾一下普通的HttpServlet的生命周期
1)初始化阶段

servlet容器加载servlet类,把Servlet的.class文件中的数据读到内存中
servlet容器创建一个ServletConfig对象,ServletConfig对象包含了servlet的初始化配置信息。
servlet容器创建一个servlet对象。
servlet容器调用servlet对象的init方法进行初始化。

2)运行阶段
当servlet容器接受到一个请求的时候,servlet容器会创建servletRequest和servletResponse对象,然后调用service方法。

3)销毁阶段
当Web应用被终止时候,servlet容器会先调用servlet对象的destroy方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的servletConfig对象。

由于DispatcherServlet继承了HttpServlet类,所以当DispatcherServlet初始化的时候,首先会调用它的init方法。我们再HttpServletBean找到了init方法,如下:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}

// Set bean properties from init parameters.
try {
//解析init-param参数,并封装到pvs中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//将当前的servlet类转换成bw类,目的是能配合init-param进行使用
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册自定义属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析
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;
}

//留给子类扩展
initServletBean();

if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}

 这里的关键方法在于initServletBean(),这个方法里面主要是为了初始化WebApplicationContext所做的操作,下面我们来看看里面比较这个方法里面调用得到的比较重要的方法,跟着initServletBean调用的调用链,我们找到DispatcherServlet的initStrategies方法:

/**
* 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) {
//初始化MultipartResolver,这个主要是用来处理文件上传的
initMultipartResolver(context);
//初始化LocaleResolver,这个主要用来配置国际化的。
initLocaleResolver(context);
//初始化ThemeResolver,用于用控制不同的页面风格
initThemeResolver(context);
//初始化HandlerMappings,客户端发出request时候,DispatcherServlet会将
//request提交给handlerMappers,然后handlerMappers根据WebApplicationContext的配置
//传递回不同的Controller
initHandlerMappings(context);
//初始化适配器
initHandlerAdapters(context);
//异常处理
initHandlerExceptionResolvers(context);
//当处理器没有返回View对象获取视图名称,并且没有往response里面写数据的时候,
//spring就会采用约定好的方式提供一个逻辑视图名称
initRequestToViewNameTranslator(context);
//初始化视图解析器
initViewResolvers(context);
//Srping提供了一个请求存储属性,可以提供给其他请求使用。
initFlashMapManager(context);
}


上面的初始化完成,那就说明DispatcherServlet已经可以开始 工作了,下面,让我看看DispatcherServlet的处理逻辑

DispatcherServlet处理逻辑

http有很多请求方式,有delete,post,get,options,trace,put这些方法,servlet是怎么处理这些方法的呢,让我们来看看FrameworkServlet的源代码;

protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

processRequest(request, response);
}


其实delete,options,trace以及put的请求方式都一样,都是最终会转交到processRequest方法去处理。

让我们看看大体的处理逻辑时序图:



下面我们来看看核心的处理逻辑代码doDispatch()的具体实现吧:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
//如果是multipartContext类型的request,则转换为MultipartHttpServletRequest类型的request
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// 根据request信息,寻找具体的处理handler
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

// 根据当前对的handler 查找对应的handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

//处理 带last-modified的header
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// 真正激活handler 并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//处理默认的视图名称,就是替视图加上前后缀
applyDefaultViewName(request, mv);
//interceptor的调用
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}//处理返回结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}


下面我们来一一解析,具体实施过程:

MultipartContont 类型的request处理

        如果处理的request带有文件上传处理,那么就会转换成MultipartHttpServletRequest类型的request

 根据request信息,寻找具体的处理handler

         这个里面的实现过程稍稍复杂一点,不过大体的逻辑还是不变的,具体是:
当spring初始化的时候,会把所有的控制器(controller)都加载到HandlerMapper 里面去,然后根据Request里面请求的url,定位到具体的控制器(controller)里面,然后返回handler,返回的handler,包含了拦截器执行的调用链。

根据当前对的handler 查找对应的handlerAdapter

           handlerAdapter也是在DispatcherServlet初始化的时候已经把所有的Adapter初始化好了,放到HandlerAdaptes里面去了,有人也许会问adapter是什么啊?我们看看具体的实现就知道了,下面是获取adapter的具体实现:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}


让我们再看看ha.support()是判断什么的:



如果适配器属于右侧中类型指定的适配器,那么就可以返回给程序使用,当然,里面我们最常用的就是controller类。具体实现大家有兴趣的可以深入的看看调用执行链。

处理 带last-modified的header

     如果处理器实现了LastModified接口,那么就可以做到缓存的效果,具体是:如果页面第一次请求成功了,在发起第二次请求的时候,如果请求头包含的时间与服务器端的返回的lastmodified时间一致,那么就会返回HTTP304的状态吗(只需要响应头,内容为空,这样就节省了带宽)。

真正激活handler 并返回视图

      这一步骤 具体的话就是调用用户的逻辑处理了。

处理默认的视图名称,就是替视图加上前后缀

      在spring配置的视图解析器里面,我们一般会配置返回视图的前缀以及视图指定的后缀名称,这一步骤的目的就是根据返回的视图配置前后缀的名称。

interceptor的调用

     在这里,执行拦截器的调用,下面是具体实现:

/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}

处理返回结果

        根据逻辑处理返回的结果,执行跳转,或者response的回写。

  好了,DispatcherServlet的大体初始化逻辑,和处理逻辑已经分析完了,以上的描述,如有不对,请各位大神指出,谢谢~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐