SpringMVC中的HandlerMapping详解

 更新时间:2023年09月09日 08:50:32   作者:果子爸聊技术  
这篇文章主要介绍了SpringMVC中的HandlerMapping详解,HandlerMapping是请求映射处理器,也就是通过请求的url找到对应的逻辑处理单元(Controller),注意这里只是建立请求与Controller的映射关系,最终的处理是通过HandlerAdapt来进行处理的,需要的朋友可以参考下

1.简介

在SpringMVC请求流程中我们可以看出,HandlerMapping是请求映射处理器,也就是通过请求的url找到对应的逻辑处理单元(Controller),注意这里只是建立请求与Controller的映射关系,最终的处理是通过HandlerAdapt来进行处理的。

2.初始化分析

在初识DispatcherServlet中提到过DispatcherServlet的初始化中会调用initStrategies()方法,实际上HandlerMapping的初始化是调用initStrategies()中的

initHandlerMappings(context);

从方法名称就可以看出来是进行HandlerMapping的初始化。下面来看一下具体的源码:

/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 */
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		if (this.detectAllHandlerMappings) {
			//从ApplicationContext(包括继承来的上下文)中获取所有的HandlerMapping
			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);
			}
		}
		else {
			try {
			   //从ApplicationContext中获取HandlerMapping
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}
		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
		   //若上下文中没有handlerMapping,就使用Spring默认的handlerMapping
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

从以上初始化源码中我们可以看出handlerMapping的初始化:

1.通过detectAllHandlerMappings参数来控制是仅从当前context中获取handlerMapping,还是需要从当前+继承来的context中获取所有的handlerMapping

2.若context中没有获取到handlerMapping,SpringMVC提供了默认的handlerMapping来处理请求
下面我们来看一下,默认的handlerMapping

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
		//关键是这句,从默认配置中获取handlerMapping
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<T>(classNames.length);
			for (String className : classNames) {
				try {
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Error loading DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]: problem with class file or dependent class", err);
				}
			}
			return strategies;
		}
		else {
			return new LinkedList<T>();
		}
	}

关键是defaultStrategies,它是个Properties对象,用来存储读取到的SpringMVC默认的配置,那SpringMVC的默认配置是在哪里定义的呢?在DispatcherServlet中有这么一段:

static {
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
		}
	}

这段就是读取默认配置的代码,它从DispatcherServlet同级目录中的DispatcherServlet.properties文件中读到相关配置,文件内容如下:

.
.****省略其他配置*****
.
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

从默认配置中可以明显看出,SpringMVC默认提供了两种HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping

3.请求处理分析

HandlerMapping是请求处理的映射器,那是它是如何做请求映射处理的呢?下面我们来看一下DispatcherServlet中对请求映射的处理。 DispatcherServlet的doDispatch方法中有这么一段:

mappedHandler = getHandler(processedRequest);

这一段就是HandlerMapping对请求映射的处理,返回一个Handler执行链(HandlerExecutionChain)

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //根据reuqest中的请求url获取handler
		Object handler = getHandlerInternal(request);
		if (handler == null) {
		   //若没有获取到handler,就获取默认的handler
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}

4.请求是如何找到Controller的

我们知道Web容器在启动的时候,会自动进行Servlet的初始化,由Servlet的初始化来驱动Spring容器(ApplicationContext)的初始化,在Spring容器初始化的时候,会把标注为@Controller的类作为Bean加载到Spring容器中,如下图方法的调用栈可以看出初始化过程

初始化过程

//org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping
protected void detectHandlers() throws BeansException {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
		}
		//获取Spring容器中的BeanName
		String[] beanNames = (this.detectHandlersInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));
		// 遍历Spring容器中的Bean
		for (String beanName : beanNames) {
			String[] urls = determineUrlsForHandler(beanName);
			if (!ObjectUtils.isEmpty(urls)) {
				// 将RequestMapping中的url与beanName建立关系,注册到handlerMap中
				registerHandler(urls, beanName);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
				}
			}
		}
	}
//注册Handler
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;
		// Eagerly resolve handler if referencing singleton via name.
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			if (getApplicationContext().isSingleton(handlerName)) {
				resolvedHandler = getApplicationContext().getBean(handlerName);
			}
		}
		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {
				if (logger.isInfoEnabled()) {
					logger.info("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				if (logger.isInfoEnabled()) {
					logger.info("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
			    //建立url与beanName的关系,方便后面通过请求url找到对应的Bean
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isInfoEnabled()) {
					logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

让我们再回到HandlerMapping的处理,当请求到来时,会先到DispatcherServlet,然后会执行HandlerMapping相关方法,下面我们再次看一下

//DispatcherServlet
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			//获取Handler
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}
//org.springframework.web.servlet.handler.AbstractHandlerMapping
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //通过请求中的url拿到之前初始化时handlerMap中的bean,并返回handler
		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);
		}
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
         //从request中拿到请求的url
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		//通过请求的url拿到handler,具体怎么拿的,请往下看lookupHandler()方法的实现
		Object handler = lookupHandler(lookupPath, request);
		if (handler == null) {
			// We need to care for the default handler directly, since we need to
			// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
			Object rawHandler = null;
			if ("/".equals(lookupPath)) {
				rawHandler = getRootHandler();
			}
			if (rawHandler == null) {
				rawHandler = getDefaultHandler();
			}
			if (rawHandler != null) {
				// Bean name or resolved handler?
				if (rawHandler instanceof String) {
					String handlerName = (String) rawHandler;
					rawHandler = getApplicationContext().getBean(handlerName);
				}
				validateHandler(rawHandler, request);
				handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
			}
		}
		if (handler != null && logger.isDebugEnabled()) {
			logger.debug("Mapping [" + lookupPath + "] to " + handler);
		}
		else if (handler == null && logger.isTraceEnabled()) {
			logger.trace("No handler mapping found for [" + lookupPath + "]");
		}
		return handler;
	}
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		// 从handlerMap中拿handler,有没有很熟悉的感觉,对,前面初始化时我们把url与BeanName的对应关系放到handlerMap中,现在取出Handler,实际上取出来的就是beanName,此时通过请求的url就拿到了对应的beanName,handlerMapping也光荣的完成了它的任务
		Object handler = this.handlerMap.get(urlPath);
		if (handler != null) {
			// Bean name or resolved handler?
			if (handler instanceof String) {
				String handlerName = (String) handler;
				//通过beanName从Spring容器中拿到Bean对象
				handler = getApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}
		// Pattern match?
		List<String> matchingPatterns = new ArrayList<String>();
		for (String registeredPattern : this.handlerMap.keySet()) {
			if (getPathMatcher().match(registeredPattern, urlPath)) {
				matchingPatterns.add(registeredPattern);
			}
			else if (useTrailingSlashMatch()) {
				if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
					matchingPatterns.add(registeredPattern +"/");
				}
			}
		}
		String bestPatternMatch = null;
		Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
		if (!matchingPatterns.isEmpty()) {
			Collections.sort(matchingPatterns, patternComparator);
			if (logger.isDebugEnabled()) {
				logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
			}
			bestPatternMatch = matchingPatterns.get(0);
		}
		if (bestPatternMatch != null) {
			handler = this.handlerMap.get(bestPatternMatch);
			if (handler == null) {
				Assert.isTrue(bestPatternMatch.endsWith("/"));
				handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
			}
			// Bean name or resolved handler?
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = getApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
			// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
			// for all of them
			Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
			for (String matchingPattern : matchingPatterns) {
				if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
					Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
					Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
					uriTemplateVariables.putAll(decodedVars);
				}
			}
			if (logger.isDebugEnabled()) {
				logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
			}
			return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
		}
		// No handler found...
		return null;
	}

总结

1、HandlerMapping的任务就是建立url与Bean(Controller)的对应关系,并将对应关系保存在handlerMap中

2、对应关系在HandlerMapping初始化的时候就已经建立好了

3、当请求到来时,会通过DispatcherServlet找到HandlerMapping,然后通过请求的url在handlerMap中找到对应的Controller,至此HandlerMapping的任务完成了,下一步就是HandlerAdapter出场了

到此这篇关于SpringMVC中的HandlerMapping详解的文章就介绍到这了,更多相关HandlerMapping详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Mybatis-plus 双主键的实现示例

    Mybatis-plus 双主键的实现示例

    本文主要介绍了Mybatis-plus 双主键的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • 详解Spring整合Quartz实现动态定时任务

    详解Spring整合Quartz实现动态定时任务

    本篇文章主要介绍了详解Spring整合Quartz实现动态定时任务,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03
  • java集合Collection实现类解析ArrayList LinkedList及Vector

    java集合Collection实现类解析ArrayList LinkedList及Vector

    这篇文章主要为大家介绍了java集合Collection实现类解析ArrayList LinkedList及Vector,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2022-03-03
  • Java 爬虫工具Jsoup详解

    Java 爬虫工具Jsoup详解

    这篇文章主要介绍了 Java 爬虫工具Jsoup详解的相关资料,需要的朋友可以参考下
    2017-05-05
  • 简单了解java集合框架LinkedList使用方法

    简单了解java集合框架LinkedList使用方法

    这篇文章主要介绍了简单了解java集合框架LinkedList使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 详解Mybatis-plus中更新date类型数据遇到的坑

    详解Mybatis-plus中更新date类型数据遇到的坑

    这篇文章主要介绍了详解Mybatis-plus中更新date类型数据遇到的坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • java开发实现五子棋游戏

    java开发实现五子棋游戏

    这篇文章主要为大家详细介绍了java开发实现五子棋游戏,具有双人对战和人机对战功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • 如何通过properties文件配置web.xml中的参数

    如何通过properties文件配置web.xml中的参数

    这篇文章主要介绍了如何通过properties文件配置web.xml中的参数方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 代理模式:JAVA静态代理和动态代理的实例和实现详解

    代理模式:JAVA静态代理和动态代理的实例和实现详解

    这篇文章主要给大家介绍了关于Java静态代理和动态代理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • SpringBoot+mail 轻松实现各类邮件自动推送

    SpringBoot+mail 轻松实现各类邮件自动推送

    在实际的项目开发过程中,经常需要用到邮件通知功能,例如,通过邮箱注册,邮箱找回密码,邮箱推送报表等等,实际的应用场景非常的多,今天通过这篇文章,我们一起来学习如何在 Spring Boot 中快速实现一个自动发送邮件的功能
    2024-07-07

最新评论