Spring-mvc-MappingRegistry实践

简介: 《基础系列》
  • 源码路径: org.springframework.jms.annotation.EnableJms
  • org.springframework.web.servlet.HandlerMapping
  • HandlerMapping 处理映射关系, 通过请求转换成对象HandlerExecutionChain
public interface HandlerMapping {
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
// 其他静态变量省略
}Copy to clipboardErrorCopied


@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   // 转换成handler
   Object handler = getHandlerInternal(request);
   if (handler == null) {
      // 获取默认的 handler
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }
   // Bean name or resolved handler?
   if (handler instanceof String) {
      // handler 是beanName 直接从容器中获取
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }
   HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
   if (logger.isTraceEnabled()) {
      logger.trace("Mapped to " + handler);
   }
   else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
      logger.debug("Mapped to " + executionChain.getHandler());
   }
   if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
      CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
      CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
      config = (config != null ? config.combine(handlerConfig) : handlerConfig);
      executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
   }
   return executionChain;
}Copy to clipboardErrorCopied
  • getHandlerInternal方法是一个抽象方法
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;Copy to clipboardErrorCopied
  • 存在的实现方法
  • 先看org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法是怎么一回事.
@Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 获取当前请求路径
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        // 设置属性
        request.setAttribute(LOOKUP_PATH, lookupPath);
        // 上锁
        this.mappingRegistry.acquireReadLock();
        try {
            // 寻找 handler method
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            // 释放锁
            this.mappingRegistry.releaseReadLock();
        }
    }
Copy to clipboardErrorCopied

UrlPathHelper

  • 全路径:org.springframework.web.util.UrlPathHelper
  • 几个属性
/**
 * 是否全路径标记
 */
private boolean alwaysUseFullPath = false;
/**
 * 是否需要 decode
 */
private boolean urlDecode = true;
private boolean removeSemicolonContent = true;
/**
 * 默认的encoding编码格式
 */
private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;Copy to clipboardErrorCopied

getPathWithinApplication

public String getPathWithinApplication(HttpServletRequest request) {
   // 获取 context path
   String contextPath = getContextPath(request);
   // 获取 uri
   String requestUri = getRequestUri(request);
   String path = getRemainingPath(requestUri, contextPath, true);
   if (path != null) {
      // Normal case: URI contains context path.
      return (StringUtils.hasText(path) ? path : "/");
   }
   else {
      return requestUri;
   }
}Copy to clipboardErrorCopied
  1. 从 request 中获取 context-path
  1. 从属性中直接获取
  2. 从 request 中调用 getContextPath 获取
  3. 判断是否是**/**
  4. decode request string
  1. 从 request 中虎丘 request-uri
  1. 从属性中获取
  2. 从 request 中调用 getRequestURI 获取
  3. decode
  1. 获取剩余路径

getContextPath

  • 获取 context-path 地址
public String getContextPath(HttpServletRequest request) {
   // 从 request 获取 context path
   String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE);
   if (contextPath == null) {
      contextPath = request.getContextPath();
   }
   if ("/".equals(contextPath)) {
      // Invalid case, but happens for includes on Jetty: silently adapt it.
      contextPath = "";
   }
   // decode context path
   return decodeRequestString(request, contextPath);
}Copy to clipboardErrorCopied

decodeRequestString

  • 判断是否需要编码, 需要编码就做编码操作,不需要就直接返回
public String decodeRequestString(HttpServletRequest request, String source) {
   // 判断是否需要编码
   if (this.urlDecode) {
      // 进行编码
      return decodeInternal(request, source);
   }
   return source;
}Copy to clipboardErrorCopied

decodeInternal

  • 编码方法
@SuppressWarnings("deprecation")
private String decodeInternal(HttpServletRequest request, String source) {
   // 确定编码方式
   String enc = determineEncoding(request);
   try {
      // 将 source 编译成 enc 的编码方式
      return UriUtils.decode(source, enc);
   }
   catch (UnsupportedCharsetException ex) {
      if (logger.isWarnEnabled()) {
         logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
               "': falling back to platform default encoding; exception message: " + ex.getMessage());
      }
      // 直接编码,JDK底层编码
      return URLDecoder.decode(source);
   }
}Copy to clipboardErrorCopied

determineEncoding

  • 确认编码
protected String determineEncoding(HttpServletRequest request) {
   // 从 request 中获取编码方式
   String enc = request.getCharacterEncoding();
   if (enc == null) {
      // 默认编码
      enc = getDefaultEncoding();
   }
   return enc;
}Copy to clipboardErrorCopied

getRequestUri

  • 获取 uri 地址
public String getRequestUri(HttpServletRequest request) {
        // 从属性中获取
        String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
        if (uri == null) {
            // 调用方法获取
            uri = request.getRequestURI();
        }
        //编码和清理数据
        return decodeAndCleanUriString(request, uri);
    }
Copy to clipboardErrorCopied

decodeAndCleanUriString

  • 编码和清理数据
private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
   // 去掉分号
   uri = removeSemicolonContent(uri);
   // decoding
   uri = decodeRequestString(request, uri);
   // 去掉 // 双斜杠
   uri = getSanitizedPath(uri);
   return uri;
}Copy to clipboardErrorCopied

shouldRemoveTrailingServletPathSlash

  • 是否删除 servlet path 后的斜杠
  • 默认是 false .
  • 代码流程
  1. 通过 classLoader 加载 "com.ibm.ws.webcontainer.WebContainer"
  2. 调用方法 "getWebContainerProperties"
  3. 从方法结果中取"getWebContainerProperties"
private boolean shouldRemoveTrailingServletPathSlash(HttpServletRequest request) {
   if (request.getAttribute(WEBSPHERE_URI_ATTRIBUTE) == null) {
      // Regular servlet container: behaves as expected in any case,
      // so the trailing slash is the result of a "/" url-pattern mapping.
      // Don't remove that slash.
      return false;
   }
   Boolean flagToUse = websphereComplianceFlag;
   if (flagToUse == null) {
      ClassLoader classLoader = UrlPathHelper.class.getClassLoader();
      String className = "com.ibm.ws.webcontainer.WebContainer";
      String methodName = "getWebContainerProperties";
      String propName = "com.ibm.ws.webcontainer.removetrailingservletpathslash";
      boolean flag = false;
      try {
         Class<?> cl = classLoader.loadClass(className);
         Properties prop = (Properties) cl.getMethod(methodName).invoke(null);
         flag = Boolean.parseBoolean(prop.getProperty(propName));
      }
      catch (Throwable ex) {
         if (logger.isDebugEnabled()) {
            logger.debug("Could not introspect WebSphere web container properties: " + ex);
         }
      }
      flagToUse = flag;
      websphereComplianceFlag = flag;
   }
   // Don't bother if WebSphere is configured to be fully Servlet compliant.
   // However, if it is not compliant, do remove the improper trailing slash!
   return !flagToUse;
}Copy to clipboardErrorCopied

decodeMatrixVariables

  • 编码修改方法
public MultiValueMap<String, String> decodeMatrixVariables(
      HttpServletRequest request, MultiValueMap<String, String> vars) {
   // 判断是否需要重写编码
   if (this.urlDecode) {
      return vars;
   }
   else {
      // 需要重写编码的情况
      MultiValueMap<String, String> decodedVars = new LinkedMultiValueMap<>(vars.size());
      // 循环, 将 value 调用decodeInternal写到结果map返回
      vars.forEach((key, values) -> {
         for (String value : values) {
            decodedVars.add(key, decodeInternal(request, value));
         }
      });
      return decodedVars;
   }
}Copy to clipboardErrorCopied
  • 与这个方法对应的还有decodePathVariables

decodePathVariables

public Map<String, String> decodePathVariables(HttpServletRequest request, Map<String, String> vars) {
   // 判断是否需要重写编码
   if (this.urlDecode) {
      return vars;
   }
   else {
      Map<String, String> decodedVars = new LinkedHashMap<>(vars.size());
      // 虚幻 decoding
      vars.forEach((key, value) -> decodedVars.put(key, decodeInternal(request, value)));
      return decodedVars;
   }
}Copy to clipboardErrorCopied
  • 回到org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);Copy to clipboardErrorCopied
  • 设置属性上锁开锁就不具体展开了.

lookupHandlerMethod

  • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod 方法
  • 第一部分
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   // 从 MultiValueMap 获取
   List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
   // 如果不为空
   if (directPathMatches != null) {
      // 添加匹配映射
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      // No choice but to go through all mappings...
      // 添加匹配映射
      addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
   }
    //...
}Copy to clipboardErrorCopied
  • 创建一个匹配 list,将匹配结果放入
List<Match> matches = new ArrayList<>();Copy to clipboardErrorCopied
  • 从 map 中获取数据
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);Copy to clipboardErrorCopied
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
   return this.urlLookup.get(urlPath);
}Copy to clipboardErrorCopied
  • urlLookup 是MultiValueMap接口.
    key:url value:mapping
  • addMatchingMappings 方法
if (directPathMatches != null) {
   // 添加匹配映射
   addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
   // No choice but to go through all mappings...
   // 添加匹配映射
   addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}Copy to clipboardErrorCopied
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
   for (T mapping : mappings) {
      // 抽象方法
      // 通过抽象方法获取 match 结果
      T match = getMatchingMapping(mapping, request);
      // 是否为空
      if (match != null) {
         // 从 mappingLookup 获取结果并且插入到matches中
         matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
      }
   }
}Copy to clipboardErrorCopied
  • getMatchingMapping 方法是一个抽象方法
protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);Copy to clipboardErrorCopied
  • org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#getMatchingMapping
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
   return info.getMatchingCondition(request);
}Copy to clipboardErrorCopied
  • 第二部分
if (!matches.isEmpty()) {
   // 比较对象
   Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
   // 排序
   matches.sort(comparator);
   // 获取第一个 match 对象
   Match bestMatch = matches.get(0);
   if (matches.size() > 1) {
      if (logger.isTraceEnabled()) {
         logger.trace(matches.size() + " matching mappings: " + matches);
      }
      if (CorsUtils.isPreFlightRequest(request)) {
         return PREFLIGHT_AMBIGUOUS_MATCH;
      }
      Match secondBestMatch = matches.get(1);
      if (comparator.compare(bestMatch, secondBestMatch) == 0) {
         // 拿出 handlerMethod 进行比较
         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 + "}");
      }
   }
   request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
   handleMatch(bestMatch.mapping, lookupPath, request);
   return bestMatch.handlerMethod;
}
else {
   return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}Copy to clipboardErrorCopied
  • 一行行开始分析
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));Copy to clipboardErrorCopied
  • 抽象方法getMappingComparator
protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);Copy to clipboardErrorCopied
  • 实现方法
@Override
protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
   return (info1, info2) -> info1.compareTo(info2, request);
}Copy to clipboardErrorCopied
  • 内部定义了 compareTo 方法
  • 执行完成比较方法后创建对象MatchComparator
  • 对象创建后进行排序,排序后取出第一个元素作为后续操作的基准对象
// 排序
matches.sort(comparator);
// 获取第一个 match 对象
Match bestMatch = matches.get(0);Copy to clipboardErrorCopied
if (matches.size() > 1) {
   if (logger.isTraceEnabled()) {
      logger.trace(matches.size() + " matching mappings: " + matches);
   }
   // 是否跨域请求
   if (CorsUtils.isPreFlightRequest(request)) {
      return PREFLIGHT_AMBIGUOUS_MATCH;
   }
   // 取出第二个元素.
   Match secondBestMatch = matches.get(1);
   // 如果比较结果相同
   if (comparator.compare(bestMatch, secondBestMatch) == 0) {
      // 第二个元素和第一个元素的比较过程
      // 拿出 handlerMethod 进行比较
      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 + "}");
   }
}Copy to clipboardErrorCopied
  • 取出第一个元素和第二个元素进行比较. 如果两个 match 相同, 出现异常

最后两个方法

// 设置属性
   request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
   // 处理匹配的结果
   handleMatch(bestMatch.mapping, lookupPath, request);
   return bestMatch.handlerMethod;
}
else {
   // 处理没有匹配的结果
   return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}Copy to clipboardErrorCopied
  • handleMatch
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
   request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}Copy to clipboardErrorCopied
  • 设置一次属性这个方法子类会继续实现
  • org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
   super.handleMatch(info, lookupPath, request);
   String bestPattern;
   Map<String, String> uriVariables;
   // 匹配器
   Set<String> patterns = info.getPatternsCondition().getPatterns();
   // 如果空设置基本数据
   if (patterns.isEmpty()) {
      bestPattern = lookupPath;
      uriVariables = Collections.emptyMap();
   }
   else {
      // 取出一个匹配器
      bestPattern = patterns.iterator().next();
      // 地址匹配器比较 路由地址和匹配器比较
      uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
   }
   request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
   if (isMatrixVariableContentAvailable()) {
      // 处理多层参数, 带有;分号的处理
      Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
      request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
   }
   // 编码url参数
   Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
   request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
   if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
      // 获取 media type
      Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
      request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
   }
}Copy to clipboardErrorCopied
  • handleNoMatch也是同类型操作
  • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#handleNoMatch
  • org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleNoMatch
@Override
protected HandlerMethod handleNoMatch(
      Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
   // 创建对象 PartialMatchHelper
   PartialMatchHelper helper = new PartialMatchHelper(infos, request);
   if (helper.isEmpty()) {
      return null;
   }
   // 函数是否匹配
   if (helper.hasMethodsMismatch()) {
      Set<String> methods = helper.getAllowedMethods();
      // 请求方式比较
      if (HttpMethod.OPTIONS.matches(request.getMethod())) {
         // handler 转换
         HttpOptionsHandler handler = new HttpOptionsHandler(methods);
         // 构建 handler method
         return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
      }
      throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
   }
   if (helper.hasConsumesMismatch()) {
      Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
      MediaType contentType = null;
      if (StringUtils.hasLength(request.getContentType())) {
         try {
            // 字符串转换成对象
            contentType = MediaType.parseMediaType(request.getContentType());
         }
         catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotSupportedException(ex.getMessage());
         }
      }
      throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));
   }
   if (helper.hasProducesMismatch()) {
      Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
      throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes));
   }
   if (helper.hasParamsMismatch()) {
      List<String[]> conditions = helper.getParamConditions();
      throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
   }
   return null;
}
相关文章
|
18天前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
52 2
|
5月前
|
XML Java UED
使用 Spring Boot 实现重试和补偿功能:从理论到实践
【6月更文挑战第17天】在分布式系统中,服务之间的调用可能会因为网络故障、服务器负载等原因偶尔失败。为了提高系统的可靠性和稳定性,我们经常需要实现重试和补偿功能。
118 6
|
21天前
|
SQL Java 数据库
Spring Boot与Flyway:数据库版本控制的自动化实践
【10月更文挑战第19天】 在软件开发中,数据库的版本控制是一个至关重要的环节,它确保了数据库结构的一致性和项目的顺利迭代。Spring Boot结合Flyway提供了一种自动化的数据库版本控制解决方案,极大地简化了数据库迁移管理。本文将详细介绍如何使用Spring Boot和Flyway实现数据库版本的自动化控制。
18 2
|
2月前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
42 2
|
3月前
|
Java 开发工具 Spring
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
|
3月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
|
3月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
|
3月前
|
前端开发 JavaScript Java
Spring Boot应用中的资源分离与高效打包实践
通过实施资源分离和高效打包策略,不仅可以提升Spring Boot应用的开发和部署效率,还能显著提高用户体验。在实际项目中,根据项目的实际情况和团队的技术栈选择合适的工具和方案是关键。希望本文能为读者在Spring Boot项目中实现资源分离和高效打包提供一些有价值的参考。
|
5月前
|
XML 监控 Java
Spring框架的核心原理与应用实践
Spring框架的核心原理与应用实践
|
5月前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【6月更文挑战第30天】Spring Cloud是Java微服务治理明星框架,整合Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(断路器)、Zuul(API网关)和Config Server(配置中心),提供完整服务治理解决方案。通过Eureka实现服务注册与发现,Ribbon进行负载均衡,Hystrix确保服务容错,Config Server集中管理配置,Zuul则作为API入口统一处理请求。理解和使用Spring Cloud是现代Java开发者的关键技能。
132 2