详解SpringMVC组件之HandlerMapping(二)

 更新时间:2023年08月31日 11:02:10   作者:姠惢荇者  
这篇文章主要介绍了详解SpringMVC组件之HandlerMapping(二),HandlerMapping组件是Spring MVC核心组件,用来根据请求的request查找对应的Handler,在Spring MVC中,有各式各样的Web请求,每个请求都需要一个对应的Handler来处理,需要的朋友可以参考下

1、前言

在前面《详解SpringMVC组件之HandlerMapping(一)》中,我们分析了HandlerMapping组件的整体逻辑及其AbstractUrlHandlerMapping系列的实现方式。

这一节,我们将分析AbstractHandlerMethodMapping系列的实现方式。

2、AbstractHandlerMethodMapping体系

在AbstractHandlerMethodMapping体系中,只有三个类,分别是

  1. AbstractHandlerMethodMapping
  2. RequestMappingInfoHandlerMapping
  3. RequestMappingHandlerMapping

这三个类依次继承于前面的类,而最前面的AbstractHandlerMethodMapping抽象类则继承于AbstractHandlerMapping抽象类,并实现了InitializingBean接口,实现了InitializingBean接口后,就会在Bean实例化后调用afterPropertiesSet()方法。

在AbstractHandlerMethodMapping体系中,类的层级结构比较简单明确,但是Spring为了保证AbstractHandlerMethodMapping体系的灵活性,逻辑还是比较复杂的。

在分析AbstractHandlerMethodMapping类之前,我们先认识一下其中的几个核心的基础类。

  • HandlerMethod
    • 一个基于方法的处理器,包括了该处理器对应的方法和实例Bean,并提供了一些访问方法参数、方法返回值、方法注解等方法。
  • RequestMappingInfo
    • 表示请求信息,并封装了映射关系的匹配条件。使用@RequestMapping注解时,配置的信息最后都设置到了RequestMappingInfo中,@RequestMapping注解的不同属性,会映射到对应的XXXRequestCondition上。
  • AbstractHandlerMethodMapping-内部类Match
    • 封装了HandlerMethod和泛型类T。泛型类T实际上表示了RequestMappingInfo。
  • 内部类MappingRegistration
    • 记录映射关系注册时的信息。封装了HandlerMethod、泛型类T、mappingName、directUrls属性(保存url和RequestMappingInfo对应关系,多个url可能对应着同一个mappingInfo)
  • 内部类MappingRegistry
    • 主要维护几个Map,用来存储映射的信息。下面详细介绍该内部类及其定义的映射关系

3、AbstractHandlerMethodMapping抽象类

在AbstractHandlerMethodMapping抽象类中定义了很多个内部类,前面提到的Match、MappingRegistration、MappingRegistry均是在该类中,其中,又以MappingRegistry最重要,下面我们首先分析这个内部类,因为其中涉及到的几个Map属性,是维护request与处理器Handler间关系的核心所在。

3.1、MappingRegistry内部类

MappingRegistry内部类主要维护了T(RequestMappingInfo)、mappingName、HandlerMethod、CorsConfiguration等对象间的映射关系,同时提供了一个读写锁readWriteLock对象。

1、属性

//维护mapping与MappingRegistration注册信息的关系
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//维护mapping与HandlerMethod的关系
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
//保存着URL与匹配条件(mapping)的对应关系,key是那些不含通配符的URL,value对应的是一个list类型的值。
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
//保存着name和HandlerMethod的对应关系(一个name可以有多个HandlerMethod)
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
//保存HandlerMethod与跨域配置的关系
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
//读写锁
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

2、register()方法、unregister()方法 在register()方法、unregister()方法中主要是用来注册T(RequestMappingInfo)、mappingName、HandlerMethod、CorsConfiguration等对象间的映射关系。

其中,register()方法代码如下:

public void register(T mapping, Object handler, Method method) {
	// Assert that the handler method is not a suspending one.
	if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
		throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
	}
	//添加写锁
	this.readWriteLock.writeLock().lock();
	try {
		//创建一个HandlerMethod对象(构造函数创建对象)
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		//验证handlerMethod和mapping的对应关系,如果已经存在一个HandlerMethod对象且与handlerMethod不一样,就会抛出异常“Ambiguous mapping. Cannot map”
		validateMethodMapping(handlerMethod, mapping);
		//维护mappingLookup对象,建立mapping和handlerMethod的映射关系
		this.mappingLookup.put(mapping, handlerMethod);
		//获取匹配条件mapping,对应的URL(那些不含通配符的URL),且URL是由子类实现getMappingPathPatterns()方法提供的,实际上还是由PatternsRequestCondition提供的,那个该directUrls 是什么时候初始化的呢?我们后续在分析。
		List<String> directUrls = getDirectUrls(mapping);
		for (String url : directUrls) {
			this.urlLookup.add(url, mapping);
		}
		String name = null;
		//获取mappingName,并维护与handlerMethod的关系
		if (getNamingStrategy() != null) {
			//namingStrategy属性对应的是RequestMappingInfoHandlerMethodMappingNamingStrategy对象(是在子类RequestMappingInfoHandlerMapping构造函数中进行了设置),命名规则:类名中全部大小字符+“#”+方法名
			name = getNamingStrategy().getName(handlerMethod, mapping);
			addMappingName(name, handlerMethod);
		}
		//获取跨域配置CorsConfiguration对象,维护与handlerMethod的关系。initCorsConfiguration()定了空方法,在RequestMappingHandlerMapping类中进行了重写。
		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
		if (corsConfig != null) {
			this.corsLookup.put(handlerMethod, corsConfig);
		}
		//维护mapping与MappingRegistration的关系
		this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
	}
	finally {
		this.readWriteLock.writeLock().unlock();
	}
}

而unregister()方法主要是移除mapping匹配条件与其他对象的映射关系,这里不在贴出代码了。

3.2、初始化

在前面我们提到了AbstractHandlerMethodMapping实现了InitializingBean接口,所以就会在Bean实例化后调用afterPropertiesSet()方法。

其实,AbstractHandlerMethodMapping类的初始化工作也就是从这个地方开始的。

代码如下:

@Override
public void afterPropertiesSet() {
	initHandlerMethods();
}
protected void initHandlerMethods() {
	//获取候选的bean实例
	for (String beanName : getCandidateBeanNames()) {
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			//遍历后续的bean实例,并分别进行处理
			processCandidateBean(beanName);
		}
	}
	//只是打印了日志,没有实际的处理逻辑
	handlerMethodsInitialized(getHandlerMethods());
}

在afterPropertiesSet()方法中,调用了initHandlerMethods()方法,实际初始化逻辑就是在该方法中实现的。首先通过getCandidateBeanNames()方法获取所有候选的Bean实例的name,代码如下:

protected String[] getCandidateBeanNames() {
	//detectHandlerMethodsInAncestorContexts 表示是否从祖先容器中查找bean实例
	return (this.detectHandlerMethodsInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
			obtainApplicationContext().getBeanNamesForType(Object.class));
}

然后,遍历通过getCandidateBeanNames()方法获取的Bean实例,调用processCandidateBean()方法进行处理,代码如下:

protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		//获取对应的Class类型
		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
		// An unresolvable bean type, probably from a lazy bean - let's ignore it.
		if (logger.isTraceEnabled()) {
			logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
		}
	}
	//判断beanType是不是处理器类型,通过isHandler()方法判断,该方法在RequestMappingHandlerMapping类中实现,根据是否有Controller或RequestMapping注解进行判断
	if (beanType != null && isHandler(beanType)) {
		//获取指定Bean实例中对应的处理方法,并通过mappingRegistry注册到对应的Map关系映射中
		detectHandlerMethods(beanName);
	}
}

在processCandidateBean()方法中,判断beanName对应的Bean实例是否是处理器类型(isHandler方法判断),如果是的话,则调用detectHandlerMethods()方法,获取该实例中对应的处理方法,并通过mappingRegistry注册到对应的Map关系映射中,代码如下:

protected void detectHandlerMethods(Object handler) {
	//获取处理器对应的Class
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());
	if (handlerType != null) {
		//获取用户定义的本来的类型,大部分情况下就是类型本身,主要针对cglib做了额外的判断,获取cglib代理的父类;
		Class<?> userType = ClassUtils.getUserClass(handlerType);
		//查找给定类型userType中的指定方法,具体判断条件由getMappingForMethod()方法来决定
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isTraceEnabled()) {
			logger.trace(formatMappings(userType, methods));
		}
		//把筛选出来的方法,通过registerHandlerMethod()注册到映射关系中
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

自此,初始化工作就完成了,即注册了mapping匹配条件与HandlerMethod的映射关系。

3.3、getHandlerInternal()方法

在前面分析了AbstractHandlerMethodMapping类的初始化方法,现在我们开始分析该类如何实现父类的getHandlerInternal()方法,即如何通过request获取对应的处理器HandlerMethod对象。

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	//获取lookupPath
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	//设置“org.springframework.web.servlet。HandlerMapping.lookupPath”参数
	request.setAttribute(LOOKUP_PATH, lookupPath);
	//获取读锁
	this.mappingRegistry.acquireReadLock();
	try {
		//获取request对应的处理器
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		//如果处理器对应的是bean name,则调用createWithResolvedBean()方法,创建对应的Bean实例
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

在getHandlerInternal()方法中,我们知道lookupHandlerMethod()方法是真正实现查找处理器的方法,代码如下:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	//从MappingRegistry.urlLookup属性中,获取lookupPath对应的mapping集合
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		//获取匹配的mapping,添加到matches变种
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// 如果没有匹配lookupPath的实例,则遍历所有的mapping,查找符合条件的mapping
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}
	if (!matches.isEmpty()) {//说明存在符合条件的mapping,可能是多个
		//获取匹配条件的排序器,由抽象方法getMappingComparator()方法获取,该方法由子类实现
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		//排序
		matches.sort(comparator);
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {//不止一个匹配时
			if (logger.isTraceEnabled()) {
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			if (CorsUtils.isPreFlightRequest(request)) {//OPTIONS请求时,直接处理
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = matches.get(1);
			//如果存在多个处理器,则直接抛出异常
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				String uri = request.getRequestURI();
				throw new IllegalStateException(
						"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
			}
		}
		//定义“.bestMatchingHandler”属性
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
		//处理匹配的处理器,这里只是添加了一个".pathWithinHandlerMapping"属性,具体实现在子类中进行。
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}
	else {//不存在匹配的bean时,调用handleNoMatch()方法,空方法,有子类进行实现。
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}

4、RequestMappingInfoHandlerMapping抽象类

RequestMappingInfoHandlerMapping抽象类继承自AbstractHandlerMethodMapping类,并明确了其中的泛型类为RequestMappingInfo。

在RequestMappingInfoHandlerMapping抽象类中做了以下几件事:

1、定义了Options的默认处置方法,其中涉及到了HTTP_OPTIONS_HANDLE_METHOD常量、内部类HttpOptionsHandler和静态代码块初始化常量。

2、构造函数,在构造函数中设置了映射的命名策略,即RequestMappingInfoHandlerMethodMappingNamingStrategy实现的方式。

3、实现了父类中的几个方法,如下所示:

//获取RequestMappingInfo 对应的URL集合
@Override
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
	return info.getPatternsCondition().getPatterns();
}
//判断当前的RequestMappingInfo与request是否匹配,实际上是由RequestMappingInfo的getMatchingCondition()方法实现判断,并返回一个新建的RequestMappingInfo 实例
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
	return info.getMatchingCondition(request);
}
//获取比较器
@Override
protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
	return (info1, info2) -> info1.compareTo(info2, request);
}

4、重新父类的handleMatch()方法,在该方法中主要添加了处理变量(模板变量和矩阵变量(MatrixVariable))和媒体类型的逻辑。后续再详细分析参数处理的过程。

5、重写父类的handleNoMatch()方法,该方法中使用了内部类PartialMatchHelper来判断不匹配的原因,然后抛出指定的异常。不在贴出代码。

5、RequestMappingHandlerMapping类

RequestMappingHandlerMapping类除了继承了RequestMappingInfoHandlerMapping之外,还实现了MatchableHandlerMapping和EmbeddedValueResolverAware两个接口。

其中,实现MatchableHandlerMapping接口的方法,暂时未使用;而实现了EmbeddedValueResolverAware接口,说明要支持解析String字符串。

定义的属性:

//是否启用后缀匹配
private boolean useSuffixPatternMatch = true;
//后缀模式匹配是否应该只对显式地在ContentNegotiationManager中注册的路径扩展有效。
private boolean useRegisteredSuffixPatternMatch = false;
//尾部斜杠匹配
private boolean useTrailingSlashMatch = true;
//根据条件设置前缀path
private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
//多媒体类型判断
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
//字符串解析器,处理spring表达式
@Nullable
private StringValueResolver embeddedValueResolver;
//RequestMappingInfo构建配置
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

afterPropertiesSet()方法

在前面,我们知道afterPropertiesSet()方法是实现初始化的方法。在AbstractHandlerMethodMapping抽象类中,实现了handler类和方法的检测和注册。在RequestMappingHandlerMapping类中,又增加了RequestMappingInfo构建配置的初始化,代码如下:

@Override
public void afterPropertiesSet() {
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setUrlPathHelper(getUrlPathHelper());
	this.config.setPathMatcher(getPathMatcher());
	this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
	this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
	this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
	this.config.setContentNegotiationManager(getContentNegotiationManager());
	super.afterPropertiesSet();
}

isHandler()方法

在AbstractHandlerMethodMapping类中,进行初始化的时候,在processCandidateBean()方法中使用了isHandler判断当前bean实例是否是处理器。实际判断逻辑在这里实现的。

@Override
protected boolean isHandler(Class<?> beanType) {
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

getMappingForMethod方法

在AbstractHandlerMethodMapping类中,进行初始化的时候,在detectHandlerMethods()方法中,调用该方法实现处理器方法的判断。

@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			info = typeInfo.combine(info);
		}
		String prefix = getPathPrefix(handlerType);
		if (prefix != null) {
			info = RequestMappingInfo.paths(prefix).build().combine(info);
		}
	}
	return info;
}
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
	RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
	RequestCondition<?> condition = (element instanceof Class ?
			getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
	return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
		RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
	RequestMappingInfo.Builder builder = RequestMappingInfo
			.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
			.methods(requestMapping.method())
			.params(requestMapping.params())
			.headers(requestMapping.headers())
			.consumes(requestMapping.consumes())
			.produces(requestMapping.produces())
			.mappingName(requestMapping.name());
	if (customCondition != null) {
		builder.customCondition(customCondition);
	}
	return builder.options(this.config).build();
}

其他方法 在registerMapping()、registerHandlerMethod()方法这两个方法,除了调用父类的方法之外, 主要是设置了ConsumesRequestCondition的判断条件。

后续还有配置跨域相关参数,这里不在详细分析了。

6、总结

在这篇文章中,我们只是分析了AbstractHandlerMethodMapping体系实现的HandlerMapping中这三个类的基本实现,其中涉及到的RequestCondition及RequestMappingInfo(也是RequestCondition的子类)还有HandlerMethod等类的具体介绍,我们在后续过程中在逐渐的学习记录。

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

相关文章

  • Java中正则表达式的使用和详解(下)

    Java中正则表达式的使用和详解(下)

    这篇文章主要介绍了Java正则表达式的使用和详解(下)的相关资料,包括常用正则表达式和正则表达式语法,非常不错,具有参考借鉴价值,需要的的朋友参考下吧
    2017-04-04
  • IntelliJ IDEA安装插件阿里巴巴Java开发手册(Alibaba Java Coding Guidelines)

    IntelliJ IDEA安装插件阿里巴巴Java开发手册(Alibaba Java Coding Guidelines

    这篇文章主要介绍了IntelliJ IDEA安装插件阿里巴巴Java开发手册(Alibaba Java Coding Guidelines),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • Java中的OpenJDK使用原理

    Java中的OpenJDK使用原理

    这篇文章主要介绍了Java中的OpenJDK使用原理,OpenJDK是Java的开发工具包,关于Java为什么要使用它文章作简单介绍,感兴趣的朋友可以参考一下
    2022-06-06
  • Maven创建项目过慢的4种解决办法

    Maven创建项目过慢的4种解决办法

    最近经常会遇到一个困扰,那就是用idea创建maven项目时,速度很慢,本文就来介绍一下Maven创建项目过慢的4种解决办法,感兴趣的可以了解一下
    2021-12-12
  • mybatisplus的逻辑删除问题

    mybatisplus的逻辑删除问题

    这篇文章主要介绍了mybatisplus的逻辑删除问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Spring中的集合注入代码实例

    Spring中的集合注入代码实例

    这篇文章主要介绍了Spring中的集合注入代码实例,集合注入是指在Spring框架中,通过配置文件或注解的方式将集合类型的数据注入到Bean中,集合类型包括List、Set、Map和Properties等,需要的朋友可以参考下
    2023-11-11
  • Java编程用两个栈实现队列代码分享

    Java编程用两个栈实现队列代码分享

    这篇文章主要介绍了Java编程用两个栈实现队列代码分享,具有一定参考价值,这里给大家分享下,供需要的朋友了解。
    2017-10-10
  • javaweb实现注册登录页面

    javaweb实现注册登录页面

    这篇文章主要为大家详细介绍了javaweb实现注册登录页面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • java二叉树面试题详解

    java二叉树面试题详解

    下面小编就为大家带来一篇java二叉树的几道面试题详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-07-07
  • SpringBoot引入swagger报错处理的解决方法

    SpringBoot引入swagger报错处理的解决方法

    这篇文章主要给大家介绍SpringBoot引入swagger是会出现报错的处理解决方法,文中有详细的解决过程,感兴趣的小伙伴可以跟着小编一起来学习吧
    2023-06-06

最新评论