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

来源:互联网 发布:可以制作手机壁纸软件 编辑:程序博客网 时间:2024/05/17 19:21

           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方法,如下:
@Overridepublic 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的配置//传递回不同的ControllerinitHandlerMappings(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类型的requestprocessedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 根据request信息,寻找具体的处理handlermappedHandler = getHandler(processedRequest, false);if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}// 根据当前对的handler 查找对应的handlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//处理 带last-modified的headerString 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 afterCompletionif (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的大体初始化逻辑,和处理逻辑已经分析完了,以上的描述,如有不对,请各位大神指出,谢谢~



0 0
原创粉丝点击