频道澳门葡京手机版网址
登录注册
澳门葡京手机版网址 > 澳门葡京手机版网址 > App开发 > 其他 > 正文
SpringMVC 如何将请求找到匹配的处理方法
2018-08-15 10:51:46      个评论      
收藏   我要投稿

在SpringMVC的模式下,浏览器的一个请求是如何映射到指定的controller的呢?

初始化映射关系

在web服务器启动时,Spring容器中会保存一个map的数据结构,里边记录这controller和url请求中的对应关系。那么这个map中的数据是如何来的呢?

首先来看AbstractHandlerMethodMapping的initHandlerMethods方法(至于为什么直接找到这个方法,我也是网上搜索的,之前的调用链没去纠结)

protected void initHandlerMethods() {
	if (logger.isDebugEnabled()) {
			logger.debug("Looking for request mappings in application context: " + getApplicationContext());
		}

  //获取Spring容器装配的所有bean的名称
	String[] beanNames = (this.detectHandlerMethodsInAncestorContexts 
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));

  //遍历
	for (String beanName : beanNames) {
 //判断该bean是否有@controller或者@RequestMapping注解
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
				isHandler(getApplicationContext().getType(beanName))){
//如果有上述注解,则需要保存对应关系
			detectHandlerMethods(beanName);
		}
	}
	handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
  //获取传过来handler的类信息
	Class handlerType =
			(handler instanceof String  getApplicationContext().getType((String) handler) : handler.getClass());

	// Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
  //初始化一个保存映射信息的map
	final Map mappings = new IdentityHashMap();
	final Class userType = ClassUtils.getUserClass(handlerType);

	Set methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
		@Override
		public boolean matches(Method method) {
 //获取该类里所有方法的映射信息 T为RequestMappingInfo
 //mapping值的形式为{[/test/test1],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
			T mapping = getMappingForMethod(method, userType);
			if (mapping != null) {
  //将信息加入map
				mappings.put(method, mapping);
				return true;
			}
			else {
				return false;
			}
		}
	});

	for (Method method : methods) {
 //注册HandlerMethod,在里边进行一些重复验证等
		registerHandlerMethod(handler, method, mappings.get(method));
	}
}

上述方法中调用了一个比较重要的方法,getMappingForMethod,通过这个方法生成后续大家一直会用到的一个RequestMappingInfo对象。具体方法如下:

@Override
//该方法接收两个参数,一个是具体方法,一个是方法所在的类
	protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) {
		RequestMappingInfo info = null;
//找到方法的@RequestMapping注解
		RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
		if (methodAnnotation != null) {
//这个方法返回null
			RequestCondition methodCondition = getCustomMethodCondition(method);
//创建RequestMappingInfo对象
			info = createRequestMappingInfo(methodAnnotation, methodCondition);
//找到类的@RequestMapping注解
			RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
			if (typeAnnotation != null) {
//该方法也返回一个null
				RequestCondition typeCondition = getCustomTypeCondition(handlerType);
//如果类和方法都有@RequestMapping注解,则进行combine操作
				info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
			}
		}
		return info;
	}

那么上述方法中调用的createRequestMappingInfo方法有事如何真正的创建出一个RequestMappingInfo对象的呢?

protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition customCondition) {
//拿到@RequestMapping注解上的value值
		String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
//创建一个RequestMappingInfo,参数为一堆condition,出了PatternsRequestCondition,其余全部使用@RequestMapping注解上的值
		return new RequestMappingInfo(
				annotation.name(),
				new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
						this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
				new RequestMethodsRequestCondition(annotation.method()),
				new ParamsRequestCondition(annotation.params()),
				new HeadersRequestCondition(annotation.headers()),
				new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
				new ProducesRequestCondition(annotation.produces(), annotation.headers(), this.contentNegotiationManager),
				customCondition);
	}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//handler此处为带有@controller或者@RequestMapping的类的名称
//初始化一个HandlerMethod,包含一些类的名称和方法等信息
		HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
		HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
//判断是否有handlerMethods是否有重复数据,有则抛异常,没有则将其加入handlerMethods map中
		if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
			throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
					"' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
					oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
		}

		this.handlerMethods.put(mapping, newHandlerMethod);
		if (logger.isInfoEnabled()) {
			logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
		}

//将没有*号和问好的pattern加入到urlMap中
		Set patterns = getMappingPathPatterns(mapping);
		for (String pattern : patterns) {
			if (!getPathMatcher().isPattern(pattern)) {
				this.urlMap.add(pattern, mapping);
			}
		}

//维护一个nameMap,key为名字,格式为congroller类大写字母+#+方法名
//比如TestBank类的getBank方法,可以为TB#getBank
		if (this.namingStrategy != null) {
			String name = this.namingStrategy.getName(newHandlerMethod, mapping);
			updateNameMap(name, newHandlerMethod);
		}
	}

由上述registerHandlerMethod方法大家可以看出,该方法共维护了三个map分别是:

handlermethods: key为RequestMappingInfo value为HandlerMethod urlMap: key为没有*和?的pattern(比如/test/test1)value为RequestMappingInfo nameMap: key为名字,格式为congroller类大写字母+#+方法名,比如TestBank类的getBank方法,key为TB#getBank

上述三个map在后续匹配浏览器请求用哪个方法来处理时会重点用到。

从映射关系中寻找匹配方法

那么DispatcherServlet是如何处理一个请求的呢?

大家从DispatcherServlet的doService方法来看起,该方法中,最终会调用到AbstractHandlerMethodMapping类的lookupHandlerMethod方法来确定这个请求应该由哪个方法处理,代码如下:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List matches = new ArrayList();
//从urlMap中寻找能匹配的处理方法
		List directPathMatches = this.urlMap.get(lookupPath);
//如果从urlMap中找到匹配的处理方法,则调用addMatchingMappings方法,将匹配的方法放入matches集合
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
//如果urlMap中没有找到直接匹配的方法
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.handlerMethods.keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
//如果找到了匹配的方法,先获取一个比较器
			Comparator comparator = new MatchComparator(getMappingComparator(request));
//将匹配到的方法按照比较器排序
			Collections.sort(matches, comparator);
			if (logger.isTraceEnabled()) {
				logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
			}
//如果成功匹配的方法只有一个,拿这个方法返回。如果匹配到多个方法,取最匹配的前两个进行比较。
//如果比较结果为0,则抛出没有找到唯一合适处理方法的异常
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					throw new IllegalStateException(
							"Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
							m1 + ", " + m2 + "}");
				}
			}
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
//没有找到匹配的则返回null
			return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
		}
	}

从上述代码可以看出,程序会先从this.urlMap中寻找是否有匹配的方法,那么这个urlMap中的数据是从什么时候加载的呢?大家网上翻看registerHandlerMethod方法,在web服务器启动时,该方法初始化了urlMap中的数据。

通过上述分析,大致可以了解到Spring容器是如何维护url和方法之间的映射关系,以及当收到请求时又是如何将请求匹配到正确的方法的。

至于没有分析到的当类和方法都有@RequestMapping注解时触发的combine操作究竟做了什么,当找到多个匹配方法是又是如何通过比较器进行排序的,大家下次再分析。

点击复制链接 与好友分享!回澳门葡京手机版网址澳门葡京手机版网址
上一篇:C++11 override解析
下一篇:Java语言之jsp页面过滤器Filter
相关文章
图文推荐
点击排行

关于大家 | 联系大家 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 澳门葡京手机版网址_澳门新莆京娱乐_www.88807.com - 点此进入--致力于做实用的IT技术学习网站

XML 地图 | Sitemap 地图