zoukankan      html  css  js  c++  java
  • Spring MVC中的HandlerMapping与HandlerAdapter

    Spring MVC中的HandlerMapping与HandlerAdapter的关系

    最近和同事讨论一个spring mvc的问题,问到HandlerMapping与HandlerAdapter有什么关系?虽然使用spring mvc时间也不短,但是瞬间能起来的只有两个关键词:

    • @RequestMapping,这个经常用的,每个 Controller下面的action方法上一般都会定义一个特有的url路径。当HTTP请求请求发送到服务端后会根据url来查找应该执行哪个Controller下面的哪个action,我理解为url与java代码的一个路由关系。
        @RequestMapping(value = "/bss/{priceId}", method = RequestMethod.GET)
        public ValueResult<ProductPrice> getProductPrice(HttpServletRequest request,
                                                                     @Min(value = 1,message = "priceId不合法")
                                                                     @PathVariable final long priceId)  {
    
            //省略
        }
    • HandlerInterceptor,这个也是经常用的,做请求拦截时比较常用。

    上面两个关键词尽管与问题有所关联,但很明显不是主要的,核心还是这两个接口都是做什么的,两者之间有什么互动。于是我们可以从一个请求开始调试下spring mvc的调用过程,以此来分析它们的作用以及关系。

    Spring MVC配置

    两个配置文件:

    • 应用程序级别的applicationContext.xml,一般加载非web的配置,比如数据库配置,redis配置等等。
    • web级别的mvc-dispatcher-servlet.xml,这里专注mvc的配置。
    XmlWebApplicationContext context = new XmlWebApplicationContext();
    context.setConfigLocations(new String[]{"classpath*:applicationContext.xml","classpath*:spring/mvc-dispatcher-servlet.xml"});
    
    ServletContextHandler spingMvcHandler = new ServletContextHandler();
    spingMvcHandler.setContextPath(appConfig.getContext());
    spingMvcHandler.addEventListener(new ContextLoaderListener(context));
    spingMvcHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), "/*");

     这里引用《张开涛》同学的图来说明上面两个配置的作用以及关系:

     

    两个核心类

    • ContextLoaderListener
      这的作用主要是在启动web容器时加载ApplicationContext的信息,用来创建ROOT ApplicationContext的,可以接收XML类型的,比如XmlWebApplicationContext,它将从XML配置文件中加载配置信息。

    这篇它不是重点至此主止。

    • DispatcherServlet
      也叫前端控制器,它是Spring MVC的统一访问入口,负责职责的分配以及工作调试,由于它的功能复杂这里只关心与HandlerMapping与HandlerAdaper的内容。下面是初始化的功能,其中有初始化HandlerMapping与HandlerAdaper。
    @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) {
    	//其它初始化
    	initHandlerMappings(context);
    	initHandlerAdapters(context);
    	//其它初始化
    }

    initHandlerMappings,主要是调用BeanFactoryUtils.beansOfTypeIncludingAncestors,其中一种非常重要的HandlerMapping是RequestMappingHandlerMapping,我们通过在Controller方面上加@RequestMapping注释来配合使用,系统会将我们配置的RequestMapping信息注册到其中,详细数据参数此图:mappingRegistry中包含了所有的请求路由信息。

    代码如下:

    private void initHandlerMappings(ApplicationContext context) {
    		this.handlerMappings = null;
    
    		if (this.detectAllHandlerMappings) {
    			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
    			Map<String, HandlerMapping> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
    				// We keep HandlerMappings in sorted order.
    				AnnotationAwareOrderComparator.sort(this.handlerMappings);
    			}
    		}
    		//不加载全部的先省略
    
    		//加载默认的逻辑先省略
    	}

    DispatcherServlet核心方法:doDispatch,三个重要步骤:

    • getHandler,获取页面处理器,通俗点就是获取由哪个Controller来执行,包含方法信息以及方法参数等信息。
    • getHandlerAdapter,获取HandlerAdapter,它包含一个handle方法,负责调用真实的页面处理器进行请求处理并返回一个ModelAndView。HandlerAdpter里面有一些常见的处理,比如消息转移,参数处理等,详见此图:里面的argumentResolvers可以用来处理请求的参数,messageConverts是作消息转换等等。

        

    • HandlerAdapter.handle,执行真实页面处理器的处理请求。

    请求时序图(只关注HandlerMapping与HandlerAdapter)

    doDispath获取页面处理器,然后根据页面处理器获取对应的HanlerAdapter,最后由HanlerAdaper来调用页面处理器的方法。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		//初始化省略
    
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    
    			try {
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				// Determine handler for the current request.
    				mappedHandler = getHandler(processedRequest);
    				if (mappedHandler == null || mappedHandler.getHandler() == null) {
    					noHandlerFound(processedRequest, response);
    					return;
    				}
    
    				// Determine handler adapter for the current request.
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    				//其它逻辑省略
    
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				//其它逻辑省略
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		//异常逻辑省略
    	}

    具体的调用逻辑比较复杂,只选取与HandlerMapping与HandlerAdaper的部分,时序图图如下:

    引用

    http://jinnianshilongnian.iteye.com/blog/1602617

  • 相关阅读:
    点双连通分量模板
    Caocao's Bridges HDU
    边双连通分量模板
    夏令营501-511NOIP训练18——高三楼
    夏令营501-511NOIP训练17——蛇形矩阵
    夏令营501-511NOIP训练16——数字转换
    模板——最小费用最大流
    洛谷P1792——[国家集训队]种树
    XJOI夏令营501-511NOIP训练14——好朋友
    codeforces 1178E-Archaeology
  • 原文地址:https://www.cnblogs.com/ASPNET2008/p/6278206.html
Copyright © 2011-2022 走看看