SpringMVC-详解HandlerMapping的前世今生之BeanNameUrlHandlerMapping

本文基于spring 5.5.2.release

本文将分析HandlerMapping的另一个实现类:BeanNameUrlHandlerMapping,它的作用是将请求映射到bean名字为http请求路径的Controller对象,之后springmvc将请求转发给该对象处理。
本文将首先介绍BeanNameUrlHandlerMapping的继承结构,然后介绍创建该对象的代码、初始化代码,最后介绍如何查找Controller对象。

一、继承结构

在这里插入图片描述
BeanNameUrlHandlerMapping的继承结构还是挺复杂的,如果要研究每个继承类的作用需要花费很多篇幅,这里就不一一介绍了,本文只关注在处理请求过程中调用的方法。
从继承结构上我们看到,BeanNameUrlHandlerMapping实现了接口ApplicationContextAware,因此对象创建完后,会调用setApplicationContext()方法,该方法也就是上文所说的初始化方法。

二、对象创建

BeanNameUrlHandlerMapping对象是由WebMvcConfigurationSupport.beanNameHandlerMapping()方法创建的,创建的过程很简单:

	@Bean
	public BeanNameUrlHandlerMapping beanNameHandlerMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

		BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
		//顺序为2,它位于RequestMappingHandlerMapping之后进行遍历
		mapping.setOrder(2);
		//设置拦截器,在调用Controller对象前和后执行拦截器
		mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
		//CorsConfiguration用于处理http请求头
		mapping.setCorsConfigurations(getCorsConfigurations());
		return mapping;
	}

三、初始化

在第一小节提到,初始化是调用setApplicationContext()方法,该方法是在父类ApplicationObjectSupport中实现的,方法里面会首先对拦截器做初始化,之后调用AbstractDetectingUrlHandlerMapping.detectHandlers()方法注册Controller对象。我们主要关注它是如何注册Controller对象的。下面看一下该方法代码:

	protected void detectHandlers() throws BeansException {
		//获取容器对象
		ApplicationContext applicationContext = obtainApplicationContext();
		//将容器中所有的bean对象名字组成数组
		String[] beanNames = (this.detectHandlersInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
				applicationContext.getBeanNamesForType(Object.class));

		//遍历每个bean对象
		for (String beanName : beanNames) {
			//下面这个方法是在BeanNameUrlHandlerMapping实现的,下面有该方法代码
			String[] urls = determineUrlsForHandler(beanName);
			if (!ObjectUtils.isEmpty(urls)) {
				//注册Controller对象,Controller对象其实也就是Handler对象
				//后面介绍该方法
				registerHandler(urls, beanName);
			}
		}
		//代码删减
	}
	//如果入参bean的名字或者其别名是以"/"开头,那么便表示该bean是一个Controller对象,
	//可以处理http请求,该bean的名字便可以加入到方法返回值中
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>();
		//检查bean对象名字是否以"/"开头
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		//检查是否有别名
		String[] aliases = obtainApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}

从determineUrlsForHandler()方法可以看出springmvc只检查bean名字是否是以“/”开头,只要是,就认为该bean可以处理http请求,所以在定义bean名字时一定注意这一点。
检查完bean名字后,调用registerHandler()注册该对象:

	//下面代码有删减
	protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
		for (String urlPath : urlPaths) {
			registerHandler(urlPath, beanName);
		}
	}
	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Object resolvedHandler = handler;
		
		//lazyInitHandlers默认是false,表示是否懒初始化
		if (!this.lazyInitHandlers && handler instanceof String) {
			//下面逻辑是从容器中获取bean对象
			//对该bean对象没有特殊要求,只要是singleton的就可以,这个对象也就是Controller对象
			String handlerName = (String) handler;
			ApplicationContext applicationContext = obtainApplicationContext();
			if (applicationContext.isSingleton(handlerName)) {
				resolvedHandler = applicationContext.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("/")) {
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				setDefaultHandler(resolvedHandler);
			}
			else {
				//将Controller对象注册到Map对象中,key是请求路径,value是Controller对象
				this.handlerMap.put(urlPath, resolvedHandler);
			}
		}
	}

注册Controller对象是将请求路径与Controller对象之间的对应关系保存到Map对象中。
到这里BeanNameUrlHandlerMapping的创建和初始化完成了,该对象内部保存了请求路径与Controller之间的对应关系。下面来看一下如何查找Controller。注意这里的Controller,也就是Handler。

四、查找Handler

当客户端发起http请求时,DispatcherServlet遍历各个HandlerMapping来查找处理该请求的Handler,对于BeanNameUrlHandlerMapping来说,查找逻辑是在AbstractUrlHandlerMapping类的lookupHandler()方法中处理的:

	//urlPath是http请求路径
	//request是http请求对象
	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		//根据请求路径从Map中直接查找Handler
		Object handler = this.handlerMap.get(urlPath);
		if (handler != null) {
			//如果handler是字符串,表示handler是bean对象的名字
			//还记得上文的lazyInitHandlers属性吗,如果该属性设置为true,那么这里得到的参数handler就是String的
			if (handler instanceof String) {
				String handlerName = (String) handler;
				//根据bean名字从容器中得到对象
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			//下面的方法用于创建HandlerExecutionChain对象
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}
		
		List<String> matchingPatterns = new ArrayList<>();
		//如果根据请求路径无法精确匹配到Handler,那么遍历每个注册的路径,使用模糊匹配
		//这里的匹配规则是正则表达式
		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 bestMatch = null;
		//如果找到多个匹配的路径,那么对这些路径排序,找到一个匹配度最高的
		Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
		if (!matchingPatterns.isEmpty()) {
			matchingPatterns.sort(patternComparator);
			if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
				logger.trace("Matching patterns " + matchingPatterns);
			}
			bestMatch = matchingPatterns.get(0);
		}
		//bestMatch表示匹配度最高的路径,它对应的Controller便是最终要找的
		if (bestMatch != null) {
			handler = this.handlerMap.get(bestMatch);
			if (handler == null) {
				if (bestMatch.endsWith("/")) {
					handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
				}
				if (handler == null) {
					throw new IllegalStateException(
							"Could not find handler for best pattern match [" + bestMatch + "]");
				}
			}
			//如果handler是字符串,表示handler是bean对象的名字,那么便从容器中获得对应的对象
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, 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<>();
			for (String matchingPattern : matchingPatterns) {
				if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
					Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
					Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
					uriTemplateVariables.putAll(decodedVars);
				}
			}
			//下面的方法用于创建HandlerExecutionChain对象
			return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
		}

		//未找到对应的Handler
		return null;
	}

lookupHandler()方法内容比较多,这里做一下总结:

  1. 首先根据http请求路径精确查找Handler,如果找到则创建HandlerExecutionChain对象返回;
  2. 如果上一步没有找到,那么遍历每个之前注册的路径,使用模糊匹配,匹配规则为正则表达式;
  3. 如果上一步找到多个匹配的路径,那么对这些路径排序,找到一个匹配度最高的,最简单的计算匹配度方法是按照长度,如果注册路径的长度越长,意味着匹配度越高;
  4. 根据找到的最优注册路径从Map对象中获得对应的bean对象,然后创建HandlerExecutionChain对象返回。

HandlerExecutionChain在上一篇文章《SpringMVC-详解HandlerMapping的前世今生之RequestMappingHandlerMapping》已经介绍了,这里不再介绍。

五、总结

本文介绍了BeanNameUrlHandlerMapping的对象创建、初始化,以及如何查找Handler。在本类中,Handler指的是bean对象,springmvc并没有对这个bean对象做特殊要求,只要是bean名字以“/”开头都可以处理http请求。但是在调用的时候,springmvc会进行检查,要求必须实现Controller接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值