Spring HandlerMapping

  • Author: HuiFer
  • 源码阅读仓库: SourceHot-spring
  • 源码路径: org.springframework.jms.annotation.EnableJms

  • org.springframework.web.servlet.HandlerMapping

  • HandlerMapping 处理映射关系, 通过请求转换成对象HandlerExecutionChain
  1. public interface HandlerMapping {
  2. HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
  3. // 其他静态变量省略
  4. }

image

  1. @Override
  2. @Nullable
  3. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  4. // 转换成handler
  5. Object handler = getHandlerInternal(request);
  6. if (handler == null) {
  7. // 获取默认的 handler
  8. handler = getDefaultHandler();
  9. }
  10. if (handler == null) {
  11. return null;
  12. }
  13. // Bean name or resolved handler?
  14. if (handler instanceof String) {
  15. // handler 是beanName 直接从容器中获取
  16. String handlerName = (String) handler;
  17. handler = obtainApplicationContext().getBean(handlerName);
  18. }
  19. HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  20. if (logger.isTraceEnabled()) {
  21. logger.trace("Mapped to " + handler);
  22. }
  23. else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
  24. logger.debug("Mapped to " + executionChain.getHandler());
  25. }
  26. if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
  27. CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
  28. CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
  29. config = (config != null ? config.combine(handlerConfig) : handlerConfig);
  30. executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  31. }
  32. return executionChain;
  33. }
  • getHandlerInternal方法是一个抽象方法

    1. @Nullable
    2. protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

    存在的实现方法

    image-20200915135933146

  • 先看org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法是怎么一回事.

  1. @Override
  2. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  3. // 获取当前请求路径
  4. String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
  5. // 设置属性
  6. request.setAttribute(LOOKUP_PATH, lookupPath);
  7. // 上锁
  8. this.mappingRegistry.acquireReadLock();
  9. try {
  10. // 寻找 handler method
  11. HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
  12. return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
  13. }
  14. finally {
  15. // 释放锁
  16. this.mappingRegistry.releaseReadLock();
  17. }
  18. }

UrlPathHelper

  • 全路径:org.springframework.web.util.UrlPathHelper

  • 几个属性

    1. /**
    2. * 是否全路径标记
    3. */
    4. private boolean alwaysUseFullPath = false;
    5. /**
    6. * 是否需要 decode
    7. */
    8. private boolean urlDecode = true;
    9. private boolean removeSemicolonContent = true;
    10. /**
    11. * 默认的encoding编码格式
    12. */
    13. private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;

getPathWithinApplication

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

getContextPath

  • 获取 context-path 地址
  1. public String getContextPath(HttpServletRequest request) {
  2. // 从 request 获取 context path
  3. String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE);
  4. if (contextPath == null) {
  5. contextPath = request.getContextPath();
  6. }
  7. if ("/".equals(contextPath)) {
  8. // Invalid case, but happens for includes on Jetty: silently adapt it.
  9. contextPath = "";
  10. }
  11. // decode context path
  12. return decodeRequestString(request, contextPath);
  13. }

decodeRequestString

  • 判断是否需要编码, 需要编码就做编码操作,不需要就直接返回
  1. public String decodeRequestString(HttpServletRequest request, String source) {
  2. // 判断是否需要编码
  3. if (this.urlDecode) {
  4. // 进行编码
  5. return decodeInternal(request, source);
  6. }
  7. return source;
  8. }

decodeInternal

  • 编码方法
  1. @SuppressWarnings("deprecation")
  2. private String decodeInternal(HttpServletRequest request, String source) {
  3. // 确定编码方式
  4. String enc = determineEncoding(request);
  5. try {
  6. // 将 source 编译成 enc 的编码方式
  7. return UriUtils.decode(source, enc);
  8. }
  9. catch (UnsupportedCharsetException ex) {
  10. if (logger.isWarnEnabled()) {
  11. logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
  12. "': falling back to platform default encoding; exception message: " + ex.getMessage());
  13. }
  14. // 直接编码,JDK底层编码
  15. return URLDecoder.decode(source);
  16. }
  17. }

determineEncoding

  • 确认编码
  1. protected String determineEncoding(HttpServletRequest request) {
  2. // 从 request 中获取编码方式
  3. String enc = request.getCharacterEncoding();
  4. if (enc == null) {
  5. // 默认编码
  6. enc = getDefaultEncoding();
  7. }
  8. return enc;
  9. }

getRequestUri

  • 获取 uri 地址
  1. public String getRequestUri(HttpServletRequest request) {
  2. // 从属性中获取
  3. String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
  4. if (uri == null) {
  5. // 调用方法获取
  6. uri = request.getRequestURI();
  7. }
  8. //编码和清理数据
  9. return decodeAndCleanUriString(request, uri);
  10. }

decodeAndCleanUriString

  • 编码和清理数据
  1. private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
  2. // 去掉分号
  3. uri = removeSemicolonContent(uri);
  4. // decoding
  5. uri = decodeRequestString(request, uri);
  6. // 去掉 // 双斜杠
  7. uri = getSanitizedPath(uri);
  8. return uri;
  9. }

shouldRemoveTrailingServletPathSlash

  • 是否删除 servlet path 后的斜杠

  • 默认是 false .

  • 代码流程
    1. 通过 classLoader 加载 "com.ibm.ws.webcontainer.WebContainer"
    2. 调用方法 "getWebContainerProperties"
    3. 从方法结果中取"getWebContainerProperties"
  1. private boolean shouldRemoveTrailingServletPathSlash(HttpServletRequest request) {
  2. if (request.getAttribute(WEBSPHERE_URI_ATTRIBUTE) == null) {
  3. // Regular servlet container: behaves as expected in any case,
  4. // so the trailing slash is the result of a "/" url-pattern mapping.
  5. // Don't remove that slash.
  6. return false;
  7. }
  8. Boolean flagToUse = websphereComplianceFlag;
  9. if (flagToUse == null) {
  10. ClassLoader classLoader = UrlPathHelper.class.getClassLoader();
  11. String className = "com.ibm.ws.webcontainer.WebContainer";
  12. String methodName = "getWebContainerProperties";
  13. String propName = "com.ibm.ws.webcontainer.removetrailingservletpathslash";
  14. boolean flag = false;
  15. try {
  16. Class<?> cl = classLoader.loadClass(className);
  17. Properties prop = (Properties) cl.getMethod(methodName).invoke(null);
  18. flag = Boolean.parseBoolean(prop.getProperty(propName));
  19. }
  20. catch (Throwable ex) {
  21. if (logger.isDebugEnabled()) {
  22. logger.debug("Could not introspect WebSphere web container properties: " + ex);
  23. }
  24. }
  25. flagToUse = flag;
  26. websphereComplianceFlag = flag;
  27. }
  28. // Don't bother if WebSphere is configured to be fully Servlet compliant.
  29. // However, if it is not compliant, do remove the improper trailing slash!
  30. return !flagToUse;
  31. }

decodeMatrixVariables

  • 编码修改方法
  1. public MultiValueMap<String, String> decodeMatrixVariables(
  2. HttpServletRequest request, MultiValueMap<String, String> vars) {
  3. // 判断是否需要重写编码
  4. if (this.urlDecode) {
  5. return vars;
  6. }
  7. else {
  8. // 需要重写编码的情况
  9. MultiValueMap<String, String> decodedVars = new LinkedMultiValueMap<>(vars.size());
  10. // 循环, 将 value 调用decodeInternal写到结果map返回
  11. vars.forEach((key, values) -> {
  12. for (String value : values) {
  13. decodedVars.add(key, decodeInternal(request, value));
  14. }
  15. });
  16. return decodedVars;
  17. }
  18. }
  • 与这个方法对应的还有decodePathVariables

decodePathVariables

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

lookupHandlerMethod

  • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod 方法

  • 第一部分

  1. @Nullable
  2. protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  3. List<Match> matches = new ArrayList<>();
  4. // 从 MultiValueMap 获取
  5. List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
  6. // 如果不为空
  7. if (directPathMatches != null) {
  8. // 添加匹配映射
  9. addMatchingMappings(directPathMatches, matches, request);
  10. }
  11. if (matches.isEmpty()) {
  12. // No choice but to go through all mappings...
  13. // 添加匹配映射
  14. addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
  15. }
  16. //...
  17. }
  • 创建一个匹配 list,将匹配结果放入

    1. List<Match> matches = new ArrayList<>();
  • 从 map 中获取数据

    1. List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    1. @Nullable
    2. public List<T> getMappingsByUrl(String urlPath) {
    3. return this.urlLookup.get(urlPath);
    4. }

    urlLookup 是MultiValueMap接口.

    key:url value:mapping

  • addMatchingMappings 方法

    1. if (directPathMatches != null) {
    2. // 添加匹配映射
    3. addMatchingMappings(directPathMatches, matches, request);
    4. }
    5. if (matches.isEmpty()) {
    6. // No choice but to go through all mappings...
    7. // 添加匹配映射
    8. addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    9. }
    1. private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    2. for (T mapping : mappings) {
    3. // 抽象方法
    4. // 通过抽象方法获取 match 结果
    5. T match = getMatchingMapping(mapping, request);
    6. // 是否为空
    7. if (match != null) {
    8. // 从 mappingLookup 获取结果并且插入到matches中
    9. matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
    10. }
    11. }
    12. }
  • getMatchingMapping 方法是一个抽象方法

    1. protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
  • org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#getMatchingMapping

    1. @Override
    2. protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
    3. return info.getMatchingCondition(request);
    4. }
  • 第二部分

  1. if (!matches.isEmpty()) {
  2. // 比较对象
  3. Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  4. // 排序
  5. matches.sort(comparator);
  6. // 获取第一个 match 对象
  7. Match bestMatch = matches.get(0);
  8. if (matches.size() > 1) {
  9. if (logger.isTraceEnabled()) {
  10. logger.trace(matches.size() + " matching mappings: " + matches);
  11. }
  12. if (CorsUtils.isPreFlightRequest(request)) {
  13. return PREFLIGHT_AMBIGUOUS_MATCH;
  14. }
  15. Match secondBestMatch = matches.get(1);
  16. if (comparator.compare(bestMatch, secondBestMatch) == 0) {
  17. // 拿出 handlerMethod 进行比较
  18. Method m1 = bestMatch.handlerMethod.getMethod();
  19. Method m2 = secondBestMatch.handlerMethod.getMethod();
  20. String uri = request.getRequestURI();
  21. throw new IllegalStateException(
  22. "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
  23. }
  24. }
  25. request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
  26. handleMatch(bestMatch.mapping, lookupPath, request);
  27. return bestMatch.handlerMethod;
  28. }
  29. else {
  30. return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
  31. }
  • 一行行开始分析
  1. Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  • 抽象方法getMappingComparator
  1. protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
  • 实现方法

    1. @Override
    2. protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
    3. return (info1, info2) -> info1.compareTo(info2, request);
    4. }

    内部定义了 compareTo 方法

  • 执行完成比较方法后创建对象MatchComparator

  • 对象创建后进行排序,排序后取出第一个元素作为后续操作的基准对象
  1. // 排序
  2. matches.sort(comparator);
  3. // 获取第一个 match 对象
  4. Match bestMatch = matches.get(0);
  1. if (matches.size() > 1) {
  2. if (logger.isTraceEnabled()) {
  3. logger.trace(matches.size() + " matching mappings: " + matches);
  4. }
  5. // 是否跨域请求
  6. if (CorsUtils.isPreFlightRequest(request)) {
  7. return PREFLIGHT_AMBIGUOUS_MATCH;
  8. }
  9. // 取出第二个元素.
  10. Match secondBestMatch = matches.get(1);
  11. // 如果比较结果相同
  12. if (comparator.compare(bestMatch, secondBestMatch) == 0) {
  13. // 第二个元素和第一个元素的比较过程
  14. // 拿出 handlerMethod 进行比较
  15. Method m1 = bestMatch.handlerMethod.getMethod();
  16. Method m2 = secondBestMatch.handlerMethod.getMethod();
  17. String uri = request.getRequestURI();
  18. throw new IllegalStateException(
  19. "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
  20. }
  21. }
  • 取出第一个元素和第二个元素进行比较. 如果两个 match 相同, 出现异常

最后两个方法

  1. // 设置属性
  2. request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
  3. // 处理匹配的结果
  4. handleMatch(bestMatch.mapping, lookupPath, request);
  5. return bestMatch.handlerMethod;
  6. }
  7. else {
  8. // 处理没有匹配的结果
  9. return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
  10. }
  • handleMatch

    1. protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
    2. request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
    3. }

    设置一次属性

    这个方法子类会继续实现

    • org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch
  1. @Override
  2. protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
  3. super.handleMatch(info, lookupPath, request);
  4. String bestPattern;
  5. Map<String, String> uriVariables;
  6. // 匹配器
  7. Set<String> patterns = info.getPatternsCondition().getPatterns();
  8. // 如果空设置基本数据
  9. if (patterns.isEmpty()) {
  10. bestPattern = lookupPath;
  11. uriVariables = Collections.emptyMap();
  12. }
  13. else {
  14. // 取出一个匹配器
  15. bestPattern = patterns.iterator().next();
  16. // 地址匹配器比较 路由地址和匹配器比较
  17. uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
  18. }
  19. request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
  20. if (isMatrixVariableContentAvailable()) {
  21. // 处理多层参数, 带有;分号的处理
  22. Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
  23. request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
  24. }
  25. // 编码url参数
  26. Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
  27. request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
  28. if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
  29. // 获取 media type
  30. Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
  31. request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
  32. }
  33. }
  • handleNoMatch 也是同类型操作
    • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#handleNoMatch
      • org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleNoMatch
  1. @Override
  2. protected HandlerMethod handleNoMatch(
  3. Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
  4. // 创建对象 PartialMatchHelper
  5. PartialMatchHelper helper = new PartialMatchHelper(infos, request);
  6. if (helper.isEmpty()) {
  7. return null;
  8. }
  9. // 函数是否匹配
  10. if (helper.hasMethodsMismatch()) {
  11. Set<String> methods = helper.getAllowedMethods();
  12. // 请求方式比较
  13. if (HttpMethod.OPTIONS.matches(request.getMethod())) {
  14. // handler 转换
  15. HttpOptionsHandler handler = new HttpOptionsHandler(methods);
  16. // 构建 handler method
  17. return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
  18. }
  19. throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
  20. }
  21. if (helper.hasConsumesMismatch()) {
  22. Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
  23. MediaType contentType = null;
  24. if (StringUtils.hasLength(request.getContentType())) {
  25. try {
  26. // 字符串转换成对象
  27. contentType = MediaType.parseMediaType(request.getContentType());
  28. }
  29. catch (InvalidMediaTypeException ex) {
  30. throw new HttpMediaTypeNotSupportedException(ex.getMessage());
  31. }
  32. }
  33. throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));
  34. }
  35. if (helper.hasProducesMismatch()) {
  36. Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
  37. throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes));
  38. }
  39. if (helper.hasParamsMismatch()) {
  40. List<String[]> conditions = helper.getParamConditions();
  41. throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
  42. }
  43. return null;
  44. }