Java教程

Spring MVC学习教程之RequestMappingHandlerMapping匹配

本文主要是介绍Spring MVC学习教程之RequestMappingHandlerMapping匹配,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

对于RequestMappingHandlerMapping,使用Spring的同学基本都不会陌生,该类的作用有两个:

  • 通过request查找对应的HandlerMethod,即当前request具体是由Controller中的哪个方法进行处理;

  • 查找当前系统中的Interceptor,将其与HandlerMethod封装为一个HandlerExecutionChain。

本文主要讲解RequestMappingHandlerMapping是如何获取HandlerMethod和Interceptor,并且将其封装为HandlerExecutionChain的。

下面话不多说了,来一起看看详细的介绍吧

1.整体封装结构

RequestMappingHandlerMapping实现了HandlerMapping接口,该接口的主要方法如下:

public interface HandlerMapping { // 通过request获取HandlerExecutionChain对象 HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;}

这里我们直接看RequestMappingHandlerMapping是如何实现该接口的:

@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request)  throws Exception { // 通过request获取具体的处理bean,这里handler可能有两种类型:HandlerMethod和String。 // 如果是String类型,那么就在BeanFactory中查找该String类型的bean,需要注意的是,返回的 // bean如果是需要使用RequestMappingHandlerAdapter处理,那么也必须是HandlerMethod类型的 Object handler = getHandlerInternal(request); if (handler == null) {  // 如果找不到处理方法,则获取自定义的默认handler  handler = getDefaultHandler(); } if (handler == null) {  return null; } if (handler instanceof String) {  // 如果获取的handler是String类型的,则在当前BeanFactory中获取该名称的bean,  // 并将其作为handler返回  String handlerName = (String) handler;  handler = obtainApplicationContext().getBean(handlerName); } // 获取当前系统中配置的Interceptor,将其与handler一起封装为一个HandlerExecutionChain HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); // 这里CorsUtils.isCorsRequest()方法判断的是当前请求是否为一个跨域的请求,如果是一个跨域的请求, // 则将跨域相关的配置也一并封装到HandlerExecutionChain中 if (CorsUtils.isCorsRequest(request)) {  CorsConfiguration globalConfig =    this.globalCorsConfigSource.getCorsConfiguration(request);  CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);  CorsConfiguration config = (globalConfig != null ?    globalConfig.combine(handlerConfig) : handlerConfig);  executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain;}

从上面的代码可以看出,对于HandlerExecutionChain的获取,RequestMappingHandlerMapping首先会获取当前request对应的handler,然后将其与Interceptor一起封装为一个HandlerExecutionChain对象。这里在进行封装的时候,Spring会对当前request是否为跨域请求进行判断,如果是跨域请求,则将相关的跨域配置封装到HandlerExecutionChain中,关于跨域请求.

2. 获取HandlerMethod

关于RequestMappingHandlerMapping是如何获取handler的,其主要在getHandlerInternal()方法中,如下是该方法的源码:

@Overrideprotected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 获取当前request的URI String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); if (logger.isDebugEnabled()) {  logger.debug("Looking up handler method for path " + lookupPath); } // 获取注册的Mapping的读锁 this.mappingRegistry.acquireReadLock(); try {  // 通过path和request查找具体的HandlerMethod  HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);  if (logger.isDebugEnabled()) {   if (handlerMethod != null) {    logger.debug("Returning handler method [" + handlerMethod + "]");   } else {    logger.debug("Did not find handler method for [" + lookupPath + "]");   }  }  // 如果获取到的bean是一个String类型的,则在BeanFactory中查找该bean,  // 并将其封装为一个HandlerMethod对象  return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally {  // 释放当前注册的Mapping的读锁  this.mappingRegistry.releaseReadLock(); }}

上述方法中,其首先会获取当前request的uri,然后通过uri查找HandlerMethod,并且在最后,会判断获取到的HandlerMethod中的bean是否为String类型的,如果是,则在当前BeanFactory中查找该名称的bean,并且将其封装为HandlerMethod对象。这里我们直接阅读lookupHandlerMethod()方法:

@Nullableprotected HandlerMethod lookupHandlerMethod(String lookupPath,   HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // 通过uri直接在注册的RequestMapping中获取对应的RequestMappingInfo列表,需要注意的是, // 这里进行查找的方式只是通过url进行查找,但是具体哪些RequestMappingInfo是匹配的,还需要进一步过滤 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) {  // 对获取到的RequestMappingInfo进行进一步过滤,并且将过滤结果封装为一个Match列表  addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) {  // 如果无法通过uri进行直接匹配,则对所有的注册的RequestMapping进行匹配,这里无法通过uri  // 匹配的情况主要有三种:  // ①在RequestMapping中定义的是PathVariable,如/user/detail/{id};  // ②在RequestMapping中定义了问号表达式,如/user/?etail;  // ③在RequestMapping中定义了*或**匹配,如/user/detail/**  addMatchingMappings(this.mappingRegistry.getMappings().keySet(),    matches, request); } if (!matches.isEmpty()) {  // 对匹配的结果进行排序,获取相似度最高的一个作为结果返回,这里对相似度的判断时,  // 会判断前两个是否相似度是一样的,如果是一样的,则直接抛出异常,如果不相同,  // 则直接返回最高的一个  Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));  matches.sort(comparator);  if (logger.isTraceEnabled()) {   logger.trace("Found " + matches.size()        + " matching mapping(s) for [" + lookupPath + "] : " + matches);  }  // 获取匹配程度最高的一个匹配结果  Match bestMatch = matches.get(0);  if (matches.size() > 1) {   // 如果匹配结果不止一个,首先会判断是否是跨域请求,如果是,   // 则返回PREFLIGHT_AMBIGUOUS_MATCH,如果不是,则会判断前两个匹配程度是否相同,   // 如果相同则抛出异常   if (CorsUtils.isPreFlightRequest(request)) {    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();    throw new IllegalStateException("Ambiguous handler methods mapped for"      + " HTTP path '" + request.getRequestURL() + "': {" + m1      + ", " + m2 + "}");   }  }  // 这里主要是对匹配结果的一个处理,主要包含对传入参数和返回的MediaType的处理  handleMatch(bestMatch.mapping, lookupPath, request);  return bestMatch.handlerMethod; } else {  // 如果匹配结果是空的,则对所有注册的Mapping进行遍历,判断当前request具体是哪种情况导致  // 的无法匹配:①RequestMethod无法匹配;②Consumes无法匹配;③Produces无法匹配;  // ④Params无法匹配  return handleNoMatch(this.mappingRegistry.getMappings().keySet(),    lookupPath, request); }}

3. Interceptor的封装

关于Inteceptor的封装,由前述第一点可以看出,其主要在getHandlerExecutionChain()方法中,如下是该方法的源码:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler,   HttpServletRequest request) { // 将当前handler封装到HandlerExecutionChain对象中 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?  (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); // 获取当前request的URI,用于MappedInterceptor的匹配 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); // 对当前所有注册的Interceptor进行遍历,如果其是MappedInterceptor类型,则调用其matches() // 方法,判断当前Interceptor是否能够应用于该request,如果可以,则添加到HandlerExecutionChain中 for (HandlerInterceptor interceptor : this.adaptedInterceptors) {  if (interceptor instanceof MappedInterceptor) {   MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;   if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {    chain.addInterceptor(mappedInterceptor.getInterceptor());   }  } else {   // 如果当前Interceptor不是MappedInterceptor类型,则直接将其添加到   // HandlerExecutionChain中   chain.addInterceptor(interceptor);  } } return chain;}

对于拦截器,理论上,Spring是会将所有的拦截器都进行一次调用,对于是否需要进行拦截,都是用户自定义实现的。这里如果对于URI有特殊的匹配,可以使用MappedInterceptor,然后实现其matches()方法,用于判断当前MappedInterceptor是否能够应用于当前request。

4. 小结

本文首先讲解了Spring是如何通过request进行匹配,从而找到具体处理当前请求的RequestMapping的,然后讲解了Spring是如何封装Interceptor,将HandlerMethod和Interceptor封装为一个HandlerExecutionChain的。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。


这篇关于Spring MVC学习教程之RequestMappingHandlerMapping匹配的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!