springmvc--3--HandlerMapping处理器映射器

系统管理员 2022-10-29 01:49 294阅读 0赞

springmvc— HandlerMapping处理器映射器

文章目录

  • springmvc— `HandlerMapping`处理器映射器
  • 1 用途
  • 2 `springmvc`默认的`HandlerMapping`
  • 3 `HandlerMapping`接口
  • 4 `getHandler()`方法,`HandlerMapping`接口的唯一方法
  • 5 `getHandlerInternal(request)`方法,获取指定请求的`HandlerMethod`处理器方法
    • 5.1 根据`url`路径匹配处理器方法
      • 5.1.1 `MappingRegistry`类,映射信息的注册表
      • 5.1.2 `RequestMappingInfo`类,请求映射信息
      • 5.1.3 `Match`类,包装`RequestMappingInfo`->`HandlerMethod`的映射关系
      • 5.1.4 `addMatchingMappings()`方法,初步得到所有匹配的`RequestMappingInfo->HandlerMethod`映射
      • 5.1.5 `handleMatch()`方法,解析请求路径的模板变量,矩阵变量和请求的媒体类型
    • 5.2 `UrlPathHelper`类,`url`路径匹配的帮助类
      • 5.2.1 `getContextPath()`方法,获取上下文路径
      • 5.2.2 `getRequestUri(HttpServletRequest request)`方法,获取`uri`路径
      • 5.2.3 `getRemainingPath()`方法,获取`剩余路径`
      • 5.2.4 截取掉剩余路径中的`Servlet`映射路径
      • 5.2.5 `decodePathVariables()`方法,对指定请求变量进行解码
      • 5.2.6 `getLookupPathForRequest(HttpServletRequest request)`方法,获取用户请求路径
      • 5.2.7 `getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName)`方法,获取用户请求路径(缓存)
    • 5.3 `createWithResolvedBean()`方法,重新解析处理器方法
  • 6 `getHandlerExecutionChain(handler, request)`方法,获取`HandlerExecutionChain`处理器执行链
    • 6.1 `HandlerExecutionChain`类,处理器执行链
    • 6.2 `MappedInterceptor` 拦截器适配器

1 用途

将请求与拦截器列表一起映射到处理程序,以进行预处理和后期处理。映射基于某些条件,具体标准因HandlerMapping 实现而异。

2 springmvc默认的HandlerMapping

springmvc会自动的注册4种类型的HandlerMapping,它们之间的关系如下面类图所示

在这里插入图片描述

目前springmvc都是使用RequestMappingHandlerMapping(支持@RequestMapping注解),另外3种基本上被淘汰了。所以这里只看RequestMappingHandlerMapping

3 HandlerMapping接口

  1. public interface HandlerMapping {
  2. String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
  3. String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
  4. String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
  5. String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
  6. String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
  7. String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
  8. String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
  9. String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
  10. @Nullable
  11. HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
  12. }

该接口中只有一个方法getHandler(HttpServletRequest request),获取指定请求的HandlerExecutionChain处理器执行链

4 getHandler()方法,HandlerMapping接口的唯一方法

该方法在AbstractHandlerMapping类中实现

  1. /** * Look up a handler for the given request, falling back to the default * handler if no specific one is found. * @param request current HTTP request * @return the corresponding handler instance, or the default handler * @see #getHandlerInternal */
  2. @Override
  3. @Nullable
  4. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  5. //获取当前请求最匹配的处理器方法,见5
  6. Object handler = getHandlerInternal(request);
  7. if (handler == null) {
  8. handler = getDefaultHandler();
  9. }
  10. if (handler == null) {
  11. return null;
  12. }
  13. // Bean name or resolved handler?
  14. if (handler instanceof String) {
  15. String handlerName = (String) handler;
  16. handler = obtainApplicationContext().getBean(handlerName);
  17. }
  18. //获取该请求对应的处理器执行链,见6
  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. //cors相关处理,以后再说
  27. if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
  28. CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
  29. CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
  30. config = (config != null ? config.combine(handlerConfig) : handlerConfig);
  31. executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  32. }
  33. return executionChain;
  34. }

流程:

  • 找到处理本次请求的最合适的处理器方法
  • 找到可以拦截本次请求的拦截器
  • 将处理器方法和拦截器封装为HandlerExecutionChain处理器执行链

5 getHandlerInternal(request)方法,获取指定请求的HandlerMethod处理器方法

  1. //org.springframework.web.servlet.HandlerMapping.producibleMediaTypes
  2. String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
  3. @Override
  4. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  5. //移除掉request域属性中的可生产的媒体属性,因为并非所有的场景下都支持它
  6. request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
  7. try {
  8. //父类获取处理器方法
  9. return super.getHandlerInternal(request);
  10. }
  11. finally {
  12. //清除request域属性中的媒体属性
  13. ProducesRequestCondition.clearMediaTypesAttribute(request);
  14. }
  15. }

清除媒体属性

  1. //org.springframework.web.servlet.mvc.condition.ProducesRequestCondition.MEDIA_TYPES
  2. private static final String MEDIA_TYPES_ATTRIBUTE = ProducesRequestCondition.class.getName() + ".MEDIA_TYPES";
  3. /** * Use this to clear {@link #MEDIA_TYPES_ATTRIBUTE} that contains the parsed, * requested media types. * @param request the current request * @since 5.2 */
  4. public static void clearMediaTypesAttribute(HttpServletRequest request) {
  5. request.removeAttribute(MEDIA_TYPES_ATTRIBUTE);
  6. }

调用父类getHandlerInternal()方法获取HandlerMethod处理器方法

  1. private UrlPathHelper urlPathHelper = new UrlPathHelper();
  2. //org.springframework.web.servlet.HandlerMapping.lookupPath
  3. String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
  4. /** * Look up a handler method for the given request. */
  5. @Override
  6. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  7. /** * UrlPathHelper是spring定义的一个帮助类 * 解析得到当前请求的url路径(映射@RequestMapping注解方法的路径),见5.2.6 */
  8. String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
  9. //将uri路径保存到请求域中
  10. request.setAttribute(LOOKUP_PATH, lookupPath);
  11. //添加读锁
  12. this.mappingRegistry.acquireReadLock();
  13. try {
  14. //根据url路径匹配处理器方法,见5.1
  15. HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
  16. //重新解析一下这个处理器方法,因为这个处理器方法所在的bean可能还没有实例化,见5.3
  17. return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
  18. }
  19. finally {
  20. //释放读锁
  21. this.mappingRegistry.releaseReadLock();
  22. }
  23. }

5.1 根据url路径匹配处理器方法

  1. //org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
  2. String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
  3. /** * Look up the best-matching handler method for the current request. * If multiple matches are found, the best match is selected. * @param lookupPath mapping lookup path within the current servlet mapping * @param request the current request * @return the best-matching handler method, or {@code null} if no match * @see #handleMatch(Object, String, HttpServletRequest) * @see #handleNoMatch(Set, String, HttpServletRequest) */
  4. @Nullable
  5. protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  6. List<Match> matches = new ArrayList<>();
  7. //获取urlPath对应的RequestMappingInfo请求映射信息,见5.1.1
  8. List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
  9. if (directPathMatches != null) {
  10. //初步得到所有匹配的RequestMappingInfo->HandlerMethod映射,见5.1.4
  11. addMatchingMappings(directPathMatches, matches, request);
  12. }
  13. if (matches.isEmpty()) {
  14. // No choice but to go through all mappings...
  15. addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
  16. }
  17. if (!matches.isEmpty()) {
  18. //获取第一个映射关系
  19. Match bestMatch = matches.get(0);
  20. //找出最符合条件的一个映射
  21. if (matches.size() > 1) {
  22. Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  23. matches.sort(comparator);
  24. bestMatch = matches.get(0);
  25. if (logger.isTraceEnabled()) {
  26. logger.trace(matches.size() + " matching mappings: " + matches);
  27. }
  28. if (CorsUtils.isPreFlightRequest(request)) {
  29. return PREFLIGHT_AMBIGUOUS_MATCH;
  30. }
  31. Match secondBestMatch = matches.get(1);
  32. if (comparator.compare(bestMatch, secondBestMatch) == 0) {
  33. Method m1 = bestMatch.handlerMethod.getMethod();
  34. Method m2 = secondBestMatch.handlerMethod.getMethod();
  35. String uri = request.getRequestURI();
  36. throw new IllegalStateException(
  37. "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
  38. }
  39. }
  40. //将处理该请求的处理器方法保存到请求域中
  41. request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
  42. //解析请求路径的模板变量,矩阵变量和请求的媒体类型,见5.1.5
  43. handleMatch(bestMatch.mapping, lookupPath, request);
  44. //返回最匹配的处理器方法
  45. return bestMatch.handlerMethod;
  46. }
  47. else {
  48. return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
  49. }
  50. }

5.1.1 MappingRegistry类,映射信息的注册表

该类是AbstractHandlerMethodMapping类的嵌套类

  1. /** * A registry that maintains all mappings to handler methods, exposing methods * to perform lookups and providing concurrent access. * <p>Package-private for testing purposes. */
  2. class MappingRegistry {
  3. private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
  4. //该集合中保存的是RequestMappingInfo->HandlerMethod的映射
  5. private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
  6. //该集合中保存的是urlPath->List<RequestMappingInfo>的映射
  7. private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
  8. private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
  9. private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
  10. private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  11. /** * Return matches for the given URL path. Not thread-safe. * @see #acquireReadLock() * 该方法在5.1中被用到 */
  12. @Nullable
  13. public List<T> getMappingsByUrl(String urlPath) {
  14. /** * 获取urlPath对应的RequestMappingInfo */
  15. return this.urlLookup.get(urlPath);
  16. }
  17. /** * Return all mappings and handler methods. Not thread-safe. * @see #acquireReadLock() * 获取RequestMappingInfo->HandlerMethod的映射关系的集合 */
  18. public Map<T, HandlerMethod> getMappings() {
  19. return this.mappingLookup;
  20. }
  21. }

这个类统一维护所有处理器方法的映射信息

5.1.2 RequestMappingInfo类,请求映射信息

  1. /** * Checks if all conditions in this request mapping info match the provided request and returns * a potentially new request mapping info with conditions tailored to the current request. * <p>For example the returned instance may contain the subset of URL patterns that match to * the current request, sorted with best matching patterns on top. * @return a new instance in case all conditions match; or {@code null} otherwise */
  2. @Override
  3. @Nullable
  4. public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
  5. RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
  6. if (methods == null) {
  7. return null;
  8. }
  9. ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
  10. if (params == null) {
  11. return null;
  12. }
  13. HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
  14. if (headers == null) {
  15. return null;
  16. }
  17. ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
  18. if (consumes == null) {
  19. return null;
  20. }
  21. ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
  22. if (produces == null) {
  23. return null;
  24. }
  25. PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
  26. if (patterns == null) {
  27. return null;
  28. }
  29. RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
  30. if (custom == null) {
  31. return null;
  32. }
  33. //封装了所有的请求匹配对象
  34. return new RequestMappingInfo(this.name, patterns,
  35. methods, params, headers, consumes, produces, custom.getCondition());
  36. }

5.1.3 Match类,包装RequestMappingInfo->HandlerMethod的映射关系

该类是AbstractHandlerMethodMapping类的嵌套类

  1. /** * A thin wrapper around a matched HandlerMethod and its mapping, for the purpose of * comparing the best match with a comparator in the context of the current request. */
  2. private class Match {
  3. //一般是一个RequestMappingInfo
  4. private final T mapping;
  5. private final HandlerMethod handlerMethod;
  6. public Match(T mapping, HandlerMethod handlerMethod) {
  7. this.mapping = mapping;
  8. this.handlerMethod = handlerMethod;
  9. }
  10. @Override
  11. public String toString() {
  12. return this.mapping.toString();
  13. }
  14. }

这个类没什么方法,它只是用来包装RequestMappingInfo->HandlerMethod的映射关系

5.1.4 addMatchingMappings()方法,初步得到所有匹配的RequestMappingInfo->HandlerMethod映射

  1. private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
  2. for (T mapping : mappings) {
  3. T match = getMatchingMapping(mapping, request);
  4. if (match != null) {
  5. /** * 创建了一个Match对象放入集合matches中 * Match中保存了RequestMappingInfo->HandlerMethod的映射关系 */
  6. matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
  7. }
  8. }
  9. }
  10. /** * Check if the given RequestMappingInfo matches the current request and * return a (potentially new) instance with conditions that match the * current request -- for example with a subset of URL patterns. * @return an info in case of a match; or {@code null} otherwise. */
  11. @Override
  12. protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
  13. /** * 获取匹配当前请求的RequestMappingInfo,见5.1.2 * 可能是原来的RequestMappingInfo,也可能是一个新的RequestMappingInfo对象 */
  14. return info.getMatchingCondition(request);
  15. }

方法遍历该请求对应的RequestMappingInfo请求映射信息,通过RequestMappingInfo找到对应的HandlerMethod,最后将RequestMappingInfo->HandlerMethod的映射关系封装为一个Match对象

5.1.5 handleMatch()方法,解析请求路径的模板变量,矩阵变量和请求的媒体类型

  1. //路径匹配器
  2. private PathMatcher pathMatcher = new AntPathMatcher();
  3. //org.springframework.web.servlet.HandlerMapping.bestMatchingPattern
  4. String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
  5. /** * Expose URI template variables, matrix variables, and producible media types in the request. * @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE * @see HandlerMapping#MATRIX_VARIABLES_ATTRIBUTE * @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE */
  6. @Override
  7. protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
  8. super.handleMatch(info, lookupPath, request);
  9. String bestPattern;
  10. Map<String, String> uriVariables;
  11. Set<String> patterns = info.getPatternsCondition().getPatterns();
  12. if (patterns.isEmpty()) {
  13. bestPattern = lookupPath;
  14. uriVariables = Collections.emptyMap();
  15. }
  16. else {
  17. bestPattern = patterns.iterator().next();
  18. /** * 找到uri模板变量 * 对于/hotels/{hotel},真实请求/hotels/1,此时该方法就会返回hotel->1映射关系 */
  19. uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
  20. }
  21. //请求路径保存到请求域中
  22. request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
  23. //矩阵变量处理
  24. if (isMatrixVariableContentAvailable()) {
  25. Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
  26. request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
  27. }
  28. //解码指定的模板变量,见5.2.5
  29. Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
  30. /** * 解码后的模板变量保存到请求域中 * org.springframework.web.servlet.HandlerMapping.uriTemplateVariables */
  31. request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
  32. //解析媒体类型
  33. if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
  34. Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
  35. request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
  36. }
  37. }

父类handleMatch()方法

  1. /** * Invoked when a matching mapping is found. * @param mapping the matching mapping * @param lookupPath mapping lookup path within the current servlet mapping * @param request the current request */
  2. protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
  3. /** * 可以看到这个方法只是将映射路径(url)保存到请求域中 * org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping */
  4. request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
  5. }

这个方法主要做3件事情

  • 获取请求路径上的模板变量,解码并保存到请求域中
  • 获取请求路径上的矩阵变量,解码并保存到请求域中
  • 获取请求的媒体类型,并保存到请求域中

5.2 UrlPathHelper类,url路径匹配的帮助类

这个类帮助解析请求的url路径,然后springmvc就可以根据它找到对应@RequestMapping注解映射的处理器方法

5.2.1 getContextPath()方法,获取上下文路径

  1. //include请求
  2. public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
  3. //默认开启了url解码
  4. private boolean urlDecode = true;
  5. //默认的编码方式"ISO-8859-1"
  6. private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
  7. /** * Return the context path for the given request, detecting an include request * URL if called within a RequestDispatcher include. * <p>As the value returned by {@code request.getContextPath()} is <i>not</i> * decoded by the servlet container, this method will decode it. * @param request current HTTP request * @return the context path */
  8. public String getContextPath(HttpServletRequest request) {
  9. //include请求上下文路径值
  10. String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE);
  11. if (contextPath == null) {
  12. //调用原生的getContextPath()方法,这个方法应该很熟悉
  13. contextPath = request.getContextPath();
  14. }
  15. /** * 实际上就是检查contextPath对应的字符串是不是'/' * 这种情况它的上下文路径应该为空,避免后面拼接字符串有两个/ */
  16. if (StringUtils.matchesCharacter(contextPath, '/')) {
  17. // Invalid case, but happens for includes on Jetty: silently adapt it.
  18. contextPath = "";
  19. }
  20. //路径解码后返回
  21. return decodeRequestString(request, contextPath);
  22. }
  23. /** * Decode the given source string with a URLDecoder. The encoding will be taken * from the request, falling back to the default "ISO-8859-1". * <p>The default implementation uses {@code URLDecoder.decode(input, enc)}. * @param request current HTTP request * @param source the String to decode * @return the decoded String * @see WebUtils#DEFAULT_CHARACTER_ENCODING * @see javax.servlet.ServletRequest#getCharacterEncoding * @see java.net.URLDecoder#decode(String, String) * @see java.net.URLDecoder#decode(String) */
  24. public String decodeRequestString(HttpServletRequest request, String source) {
  25. //url解码
  26. if (this.urlDecode) {
  27. return decodeInternal(request, source);
  28. }
  29. return source;
  30. }
  31. private String decodeInternal(HttpServletRequest request, String source) {
  32. //获取当前请求的编码方式
  33. String enc = determineEncoding(request);
  34. try {
  35. //按照指定的方式进行解码
  36. return UriUtils.decode(source, enc);
  37. }
  38. catch (UnsupportedCharsetException ex) {
  39. if (logger.isWarnEnabled()) {
  40. logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
  41. "': falling back to platform default encoding; exception message: " + ex.getMessage());
  42. }
  43. //解析发生异常就使用URLDecoder解析,最终办法
  44. return URLDecoder.decode(source);
  45. }
  46. }
  47. /** * Determine the encoding for the given request. * Can be overridden in subclasses. * <p>The default implementation checks the request encoding, * falling back to the default encoding specified for this resolver. * @param request current HTTP request * @return the encoding for the request (never {@code null}) * @see javax.servlet.ServletRequest#getCharacterEncoding() * @see #setDefaultEncoding */
  48. protected String determineEncoding(HttpServletRequest request) {
  49. /** * request原生方法获取当前请求的编码方式("ISO-8859-1"、"UTF8") * 如果用户未指定编码方式,则使用默认的编码方式 */
  50. String enc = request.getCharacterEncoding();
  51. if (enc == null) {
  52. //获取默认的编码方式
  53. enc = getDefaultEncoding();
  54. }
  55. return enc;
  56. }
  57. /** * Return the default character encoding to use for URL decoding. */
  58. protected String getDefaultEncoding() {
  59. //"ISO-8859-1"
  60. return this.defaultEncoding;
  61. }

可以看到获取上下文路径的过程还是比较繁琐的,步骤大致如下:

  1. 使用原生的request.getContextPath()方法获取上下文路径contextPath
  2. 使用原生的request.getCharacterEncoding()方法获取请求的编码方式enc
  3. 如果用户未指定编码方式,则使用默认的编码方式ISO-8859-1
  4. 调用UriUtils.decode(contextPath, enc)方法解析上下文路径
  5. 解析过程发生异常就使用URLDecoder.decode(contextPath)方法解析,最终办法

5.2.2 getRequestUri(HttpServletRequest request)方法,获取uri路径

  1. public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
  2. private boolean removeSemicolonContent = true;
  3. /** * Return the request URI for the given request, detecting an include request * URL if called within a RequestDispatcher include. * <p>As the value returned by {@code request.getRequestURI()} is <i>not</i> * decoded by the servlet container, this method will decode it. * <p>The URI that the web container resolves <i>should</i> be correct, but some * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid" * in the URI. This method cuts off such incorrect appendices. * @param request current HTTP request * @return the request URI */
  4. public String getRequestUri(HttpServletRequest request) {
  5. //include请求uri路径值
  6. String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
  7. if (uri == null) {
  8. /** * 调用原生的getRequestURI()方法获取uri路径 * uri路径中包括上下文路径 */
  9. uri = request.getRequestURI();
  10. }
  11. //对url进行解码,并去掉;后面无关的部分
  12. return decodeAndCleanUriString(request, uri);
  13. }
  14. /** * Decode the supplied URI string and strips any extraneous portion after a ';'. */
  15. private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
  16. //删除uri中分号后面的内容
  17. uri = removeSemicolonContent(uri);
  18. //uri路径解码
  19. uri = decodeRequestString(request, uri);
  20. //将uri路径中所有的//替换为/
  21. uri = getSanitizedPath(uri);
  22. return uri;
  23. }
  24. /** * Remove ";" (semicolon) content from the given request URI if the * {@linkplain #setRemoveSemicolonContent removeSemicolonContent} * property is set to "true". Note that "jsessionid" is always removed. * @param requestUri the request URI string to remove ";" content from * @return the updated URI string */
  25. public String removeSemicolonContent(String requestUri) {
  26. /** * removeSemicolonContent属性默认true,表示去掉;后面的内容 * 为false表示只去掉Jsessionid * 不管是true还是false,Jsessionid总是被删掉 */
  27. return (this.removeSemicolonContent ?
  28. removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri));
  29. }
  30. private String removeSemicolonContentInternal(String requestUri) {
  31. //第一个;的索引
  32. int semicolonIndex = requestUri.indexOf(';');
  33. //while循环删掉所有;后面的内容
  34. while (semicolonIndex != -1) {
  35. int slashIndex = requestUri.indexOf('/', semicolonIndex);
  36. String start = requestUri.substring(0, semicolonIndex);
  37. requestUri = (slashIndex != -1) ? start + requestUri.substring(slashIndex) : start;
  38. semicolonIndex = requestUri.indexOf(';', semicolonIndex);
  39. }
  40. return requestUri;
  41. }
  42. //删掉Jsessionid的内容
  43. private String removeJsessionid(String requestUri) {
  44. String key = ";jsessionid=";
  45. int index = requestUri.toLowerCase().indexOf(key);
  46. if (index == -1) {
  47. return requestUri;
  48. }
  49. String start = requestUri.substring(0, index);
  50. for (int i = key.length(); i < requestUri.length(); i++) {
  51. char c = requestUri.charAt(i);
  52. if (c == ';' || c == '/') {
  53. return start + requestUri.substring(i);
  54. }
  55. }
  56. return start;
  57. }

简单总结一下流程如下:

  1. 使用原生的request.getRequestURI()方法获取uri路径
  2. 去掉;后面的内容,总是去掉jsessionid
  3. uri路径解码,和上下文路径解码过程一样
  4. uri路径中所有的//替换为/

5.2.3 getRemainingPath()方法,获取剩余路径

  1. /** * Match the given "mapping" to the start of the "requestUri" and if there * is a match return the extra part. This method is needed because the * context path and the servlet path returned by the HttpServletRequest are * stripped of semicolon content unlike the requestUri. */
  2. @Nullable
  3. private String getRemainingPath(String requestUri, String mapping, boolean ignoreCase) {
  4. int index1 = 0;
  5. int index2 = 0;
  6. for (; (index1 < requestUri.length()) && (index2 < mapping.length()); index1++, index2++) {
  7. char c1 = requestUri.charAt(index1);
  8. char c2 = mapping.charAt(index2);
  9. if (c1 == ';') {
  10. index1 = requestUri.indexOf('/', index1);
  11. if (index1 == -1) {
  12. return null;
  13. }
  14. c1 = requestUri.charAt(index1);
  15. }
  16. if (c1 == c2 || (ignoreCase && (Character.toLowerCase(c1) == Character.toLowerCase(c2)))) {
  17. continue;
  18. }
  19. return null;
  20. }
  21. if (index2 != mapping.length()) {
  22. return null;
  23. }
  24. else if (index1 == requestUri.length()) {
  25. return "";
  26. }
  27. else if (requestUri.charAt(index1) == ';') {
  28. index1 = requestUri.indexOf('/', index1);
  29. }
  30. return (index1 != -1 ? requestUri.substring(index1) : "");
  31. }

可以看到,这个方法比较uri路径上下文路径

  • 如果uri路径是以上下文路径开头的,则截取掉开头部分,获取到剩下的路径
  • 不以以上下文路径开头,要么返回null,要么返回空串

5.2.4 截取掉剩余路径中的Servlet映射路径

  1. /** * Return the path within the servlet mapping for the given request, * i.e. the part of the request's URL beyond the part that called the servlet, * or "" if the whole URL has been used to identify the servlet. * <p>Detects include request URL if called within a RequestDispatcher include. * <p>E.g.: servlet mapping = "/*"; request URI = "/test/a" -> "/test/a". * <p>E.g.: servlet mapping = "/"; request URI = "/test/a" -> "/test/a". * <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a". * <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "". * <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "". * @param request current HTTP request * @param pathWithinApp a precomputed path within the application * @return the path within the servlet mapping, or "" * @since 5.2.9 * @see #getLookupPathForRequest */
  2. protected String getPathWithinServletMapping(HttpServletRequest request, String pathWithinApp) {
  3. String servletPath = getServletPath(request);
  4. String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
  5. String path;
  6. // If the app container sanitized the servletPath, check against the sanitized version
  7. if (servletPath.contains(sanitizedPathWithinApp)) {
  8. path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
  9. }
  10. else {
  11. path = getRemainingPath(pathWithinApp, servletPath, false);
  12. }
  13. if (path != null) {
  14. // Normal case: URI contains servlet path.
  15. return path;
  16. }
  17. else {
  18. // Special case: URI is different from servlet path.
  19. String pathInfo = request.getPathInfo();
  20. if (pathInfo != null) {
  21. // Use path info if available. Indicates index page within a servlet mapping?
  22. // e.g. with index page: URI="/", servletPath="/index.html"
  23. return pathInfo;
  24. }
  25. if (!this.urlDecode) {
  26. // No path info... (not mapped by prefix, nor by extension, nor "/*")
  27. // For the default servlet mapping (i.e. "/"), urlDecode=false can
  28. // cause issues since getServletPath() returns a decoded path.
  29. // If decoding pathWithinApp yields a match just use pathWithinApp.
  30. path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
  31. if (path != null) {
  32. return pathWithinApp;
  33. }
  34. }
  35. // Otherwise, use the full servlet path.
  36. return servletPath;
  37. }
  38. }

方法具体如何完成截取逻辑,我们不关心。从注释可以看到springmvc个我们的示例

  • servlet mapping = “/*”; request URI = “/test/a” -> “/test/a”
  • servlet mapping = “/”; request URI = “/test/a” -> “/test/a”
  • servlet mapping = “/test/*”; request URI = “/test/a” -> “/a”
  • servlet mapping = “/test”; request URI = “/test” -> “”
  • servlet mapping = “/*.test”; request URI = “/a.test” -> “”

所以我们以后在给DispatcherServlet设置拦截路径的时候需要注意一下

5.2.5 decodePathVariables()方法,对指定请求变量进行解码

  1. /** * Decode the given URI path variables via {@link #decodeRequestString} unless * {@link #setUrlDecode} is set to {@code true} in which case it is assumed * the URL path from which the variables were extracted is already decoded * through a call to {@link #getLookupPathForRequest(HttpServletRequest)}. * @param request current HTTP request * @param vars the URI variables extracted from the URL path * @return the same Map or a new Map instance */
  2. public Map<String, String> decodePathVariables(HttpServletRequest request, Map<String, String> vars) {
  3. /** * true,那么之前获取uri和contextPath的时候就已经解码过了 * 此时直接返回,不用解码了 */
  4. if (this.urlDecode) {
  5. return vars;
  6. }
  7. //解码
  8. else {
  9. Map<String, String> decodedVars = new LinkedHashMap<>(vars.size());
  10. //遍历,对每一个变量调用decodeInternal()方法进行解码,见5.2.1
  11. vars.forEach((key, value) -> decodedVars.put(key, decodeInternal(request, value)));
  12. return decodedVars;
  13. }
  14. }

5.2.6 getLookupPathForRequest(HttpServletRequest request)方法,获取用户请求路径

  1. //使用完整路径去映射@RequestMapping注解方法
  2. private boolean alwaysUseFullPath = false;
  3. /** * Return the mapping lookup path for the given request, within the current * servlet mapping if applicable, else within the web application. * <p>Detects include request URL if called within a RequestDispatcher include. * @param request current HTTP request * @return the lookup path * @see #getPathWithinServletMapping * @see #getPathWithinApplication */
  4. public String getLookupPathForRequest(HttpServletRequest request) {
  5. //获取用户实际的请求路径
  6. String pathWithinApp = getPathWithinApplication(request);
  7. // Always use full path within current servlet context?
  8. //总是使用完整路径去映射@RequestMapping注解方法
  9. if (this.alwaysUseFullPath) {
  10. return pathWithinApp;
  11. }
  12. // Else, use path within current servlet mapping if applicable
  13. //再截取掉Servlet映射路径,见5.2.4
  14. String rest = getPathWithinServletMapping(request, pathWithinApp);
  15. if (StringUtils.hasLength(rest)) {
  16. return rest;
  17. }
  18. else {
  19. return pathWithinApp;
  20. }
  21. }

获取用户实际的请求路径,该请求路径应该和某个@RequestMapping注解方法映射一致

  1. /** * Return the path within the web application for the given request. * <p>Detects include request URL if called within a RequestDispatcher include. * @param request current HTTP request * @return the path within the web application * @see #getLookupPathForRequest */
  2. public String getPathWithinApplication(HttpServletRequest request) {
  3. //获取上下文路径,实际上就是项目名,见5.2.1
  4. String contextPath = getContextPath(request);
  5. //获取uri路径,见5.2.2
  6. String requestUri = getRequestUri(request);
  7. //获取实际的@RequestMapping注解方法映射的路径,见5.2.3
  8. String path = getRemainingPath(requestUri, contextPath, true);
  9. if (path != null) {
  10. // Normal case: URI contains context path.
  11. return (StringUtils.hasText(path) ? path : "/");
  12. }
  13. else {
  14. return requestUri;
  15. }
  16. }

该方法会获取用户请求的实际路径,什么叫实际路径?

也就是这个路径可以直接映射处理器方法,为了得到这个路径,springmvc做了3次处理

  • 调用request.getRequestURI()方法得到本次请求的uri路径
  • 调用request.getContextPath()方法还得到上下文路径
  • DispatcherServlet的映射路径
  • uri路径去掉上下文路径和Servlet的映射路径才是实际路径

5.2.7 getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName)方法,获取用户请求路径(缓存)

  1. /** * Variant of {@link #getLookupPathForRequest(HttpServletRequest)} that * automates checking for a previously computed lookupPath saved as a * request attribute. The attribute is only used for lookup purposes. * @param request current HTTP request * @param lookupPathAttributeName the request attribute to check * @return the lookup path * @since 5.2 * @see org.springframework.web.servlet.HandlerMapping#LOOKUP_PATH */
  2. public String getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName) {
  3. /** * org.springframework.web.servlet.HandlerMapping.lookupPath * 先从请求域中取,因为之前调用getLookupPathForRequest(request)方法之后,将得到的实际路径 * 保存到了请求域中,见5 */
  4. if (lookupPathAttributeName != null) {
  5. String result = (String) request.getAttribute(lookupPathAttributeName);
  6. if (result != null) {
  7. return result;
  8. }
  9. }
  10. //重新获取实际路径,见5.2.6
  11. return getLookupPathForRequest(request);
  12. }

相比于5.2.6只是增加了从缓存中取值的过程。

5.3 createWithResolvedBean()方法,重新解析处理器方法

  1. /** * If the provided instance contains a bean name rather than an object instance, * the bean name is resolved before a {@link HandlerMethod} is created and returned. */
  2. public HandlerMethod createWithResolvedBean() {
  3. //处理器方法所在的bean
  4. Object handler = this.bean;
  5. //未实例化
  6. if (this.bean instanceof String) {
  7. Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
  8. String beanName = (String) this.bean;
  9. //实例化
  10. handler = this.beanFactory.getBean(beanName);
  11. }
  12. //重新创建一个HandlerMethod对象,拷贝原对象的值
  13. return new HandlerMethod(this, handler);
  14. }

如果处理器方法所在的bean还未实例化,就先实例化,然后重新克隆一个HandlerMethod对象

6 getHandlerExecutionChain(handler, request)方法,获取HandlerExecutionChain处理器执行链

  1. //这个请求域属性在5中用到过
  2. String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
  3. //拦截器对象
  4. private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
  5. /** * Build a {@link HandlerExecutionChain} for the given handler, including * applicable interceptors. * <p>The default implementation builds a standard {@link HandlerExecutionChain} * with the given handler, the common interceptors of the handler mapping, and any * {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors * are added in the order they were registered. Subclasses may override this * in order to extend/rearrange the list of interceptors. * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a * pre-built {@link HandlerExecutionChain}. This method should handle those * two cases explicitly, either building a new {@link HandlerExecutionChain} * or extending the existing chain. * <p>For simply adding an interceptor in a custom subclass, consider calling * {@code super.getHandlerExecutionChain(handler, request)} and invoking * {@link HandlerExecutionChain#addInterceptor} on the returned chain object. * @param handler the resolved handler instance (never {@code null}) * @param request current HTTP request * @return the HandlerExecutionChain (never {@code null}) * @see #getAdaptedInterceptors() */
  6. protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
  7. /** * 初始化一个HandlerExecutionChain,见6.1 * 此时只是向里面设置了处理器方法,还没有设置拦截器 * 下面的流程就是获取本次请求需要执行的拦截器,并设置到HandlerExecutionChain对象中 */
  8. HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
  9. (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
  10. //从缓存中获取用户请求的实际路径,见5.2.7
  11. String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
  12. //遍历所有的拦截器
  13. for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
  14. //MappedInterceptor类型的拦截器,见6.2
  15. if (interceptor instanceof MappedInterceptor) {
  16. MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
  17. //拦截器拦截匹配路径的请求
  18. if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
  19. chain.addInterceptor(mappedInterceptor.getInterceptor());
  20. }
  21. }
  22. //普通类型的拦截器,拦截所有请求
  23. else {
  24. chain.addInterceptor(interceptor);
  25. }
  26. }
  27. return chain;
  28. }

6.1 HandlerExecutionChain类,处理器执行链

  1. public class HandlerExecutionChain {
  2. private final Object handler;
  3. //拦截器
  4. @Nullable
  5. private HandlerInterceptor[] interceptors;
  6. //处理器方法
  7. @Nullable
  8. private List<HandlerInterceptor> interceptorList;
  9. private int interceptorIndex = -1;
  10. /** * Create a new HandlerExecutionChain. * @param handler the handler object to execute */
  11. public HandlerExecutionChain(Object handler) {
  12. this(handler, (HandlerInterceptor[]) null);
  13. }
  14. /** * Create a new HandlerExecutionChain. * @param handler the handler object to execute * @param interceptors the array of interceptors to apply * (in the given order) before the handler itself executes */
  15. public HandlerExecutionChain(Object handler, @Nullable HandlerInterceptor... interceptors) {
  16. //拷贝原来的HandlerExecutionChain到新对象中
  17. if (handler instanceof HandlerExecutionChain) {
  18. HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
  19. this.handler = originalChain.getHandler();
  20. this.interceptorList = new ArrayList<>();
  21. CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
  22. CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
  23. }
  24. else {
  25. this.handler = handler;
  26. this.interceptors = interceptors;
  27. }
  28. }
  29. }

封装了处理本次请求要执行的拦截器和处理器方法

6.2 MappedInterceptor 拦截器适配器

MappedInterceptor是一个final类,实现了HandlerInterceptor接口

  1. public final class MappedInterceptor implements HandlerInterceptor {
  2. //拦截路径
  3. @Nullable
  4. private final String[] includePatterns;
  5. //不拦截的路径
  6. @Nullable
  7. private final String[] excludePatterns;
  8. //真正的拦截器
  9. private final HandlerInterceptor interceptor;
  10. //路径匹配器
  11. @Nullable
  12. private PathMatcher pathMatcher;
  13. /** * Determine a match for the given lookup path. * @param lookupPath the current request path * @param pathMatcher a path matcher for path pattern matching * @return {@code true} if the interceptor applies to the given request path */
  14. public boolean matches(String lookupPath, PathMatcher pathMatcher) {
  15. PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
  16. //不拦截路径匹配成功返回false
  17. if (!ObjectUtils.isEmpty(this.excludePatterns)) {
  18. for (String pattern : this.excludePatterns) {
  19. if (pathMatcherToUse.match(pattern, lookupPath)) {
  20. return false;
  21. }
  22. }
  23. }
  24. //拦截路径为空,返回true,表示全部拦截
  25. if (ObjectUtils.isEmpty(this.includePatterns)) {
  26. return true;
  27. }
  28. //拦截路径匹配成功返回true
  29. for (String pattern : this.includePatterns) {
  30. if (pathMatcherToUse.match(pattern, lookupPath)) {
  31. return true;
  32. }
  33. }
  34. return false;
  35. }
  36. }

普通的拦截器会拦截所有请求,而MappedInterceptor 拦截器则是有目的拦截,用户配置的普通的拦截器最终会被适配为MappedInterceptor,以实现选择性拦截。

发表评论

表情:
评论列表 (有 0 条评论,294人围观)

还没有评论,来说两句吧...

相关阅读