springmvc--3--HandlerMapping处理器映射器
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
接口
public interface HandlerMapping {
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
该接口中只有一个方法
getHandler(HttpServletRequest request)
,获取指定请求的HandlerExecutionChain
处理器执行链
4 getHandler()
方法,HandlerMapping
接口的唯一方法
该方法在
AbstractHandlerMapping
类中实现
/** * 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 */
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取当前请求最匹配的处理器方法,见5
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//获取该请求对应的处理器执行链,见6
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());
}
//cors相关处理,以后再说
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;
}
流程:
- 找到处理本次请求的最合适的处理器方法
- 找到可以拦截本次请求的拦截器
- 将处理器方法和拦截器封装为
HandlerExecutionChain
处理器执行链
5 getHandlerInternal(request)
方法,获取指定请求的HandlerMethod
处理器方法
//org.springframework.web.servlet.HandlerMapping.producibleMediaTypes
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//移除掉request域属性中的可生产的媒体属性,因为并非所有的场景下都支持它
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
//父类获取处理器方法
return super.getHandlerInternal(request);
}
finally {
//清除request域属性中的媒体属性
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
清除媒体属性
//org.springframework.web.servlet.mvc.condition.ProducesRequestCondition.MEDIA_TYPES
private static final String MEDIA_TYPES_ATTRIBUTE = ProducesRequestCondition.class.getName() + ".MEDIA_TYPES";
/** * Use this to clear {@link #MEDIA_TYPES_ATTRIBUTE} that contains the parsed, * requested media types. * @param request the current request * @since 5.2 */
public static void clearMediaTypesAttribute(HttpServletRequest request) {
request.removeAttribute(MEDIA_TYPES_ATTRIBUTE);
}
调用父类getHandlerInternal()
方法获取HandlerMethod
处理器方法
private UrlPathHelper urlPathHelper = new UrlPathHelper();
//org.springframework.web.servlet.HandlerMapping.lookupPath
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
/** * Look up a handler method for the given request. */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
/** * UrlPathHelper是spring定义的一个帮助类 * 解析得到当前请求的url路径(映射@RequestMapping注解方法的路径),见5.2.6 */
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//将uri路径保存到请求域中
request.setAttribute(LOOKUP_PATH, lookupPath);
//添加读锁
this.mappingRegistry.acquireReadLock();
try {
//根据url路径匹配处理器方法,见5.1
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
//重新解析一下这个处理器方法,因为这个处理器方法所在的bean可能还没有实例化,见5.3
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
//释放读锁
this.mappingRegistry.releaseReadLock();
}
}
5.1 根据url
路径匹配处理器方法
//org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
/** * 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) */
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//获取urlPath对应的RequestMappingInfo请求映射信息,见5.1.1
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
//初步得到所有匹配的RequestMappingInfo->HandlerMethod映射,见5.1.4
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
//获取第一个映射关系
Match bestMatch = matches.get(0);
//找出最符合条件的一个映射
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
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) {
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);
//解析请求路径的模板变量,矩阵变量和请求的媒体类型,见5.1.5
handleMatch(bestMatch.mapping, lookupPath, request);
//返回最匹配的处理器方法
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
5.1.1 MappingRegistry
类,映射信息的注册表
该类是
AbstractHandlerMethodMapping
类的嵌套类
/** * A registry that maintains all mappings to handler methods, exposing methods * to perform lookups and providing concurrent access. * <p>Package-private for testing purposes. */
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//该集合中保存的是RequestMappingInfo->HandlerMethod的映射
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
//该集合中保存的是urlPath->List<RequestMappingInfo>的映射
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/** * Return matches for the given URL path. Not thread-safe. * @see #acquireReadLock() * 该方法在5.1中被用到 */
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
/** * 获取urlPath对应的RequestMappingInfo */
return this.urlLookup.get(urlPath);
}
/** * Return all mappings and handler methods. Not thread-safe. * @see #acquireReadLock() * 获取RequestMappingInfo->HandlerMethod的映射关系的集合 */
public Map<T, HandlerMethod> getMappings() {
return this.mappingLookup;
}
}
这个类统一维护所有处理器方法的映射信息
5.1.2 RequestMappingInfo
类,请求映射信息
/** * 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 */
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
}
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
}
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
}
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (produces == null) {
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
//封装了所有的请求匹配对象
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
5.1.3 Match
类,包装RequestMappingInfo
->HandlerMethod
的映射关系
该类是
AbstractHandlerMethodMapping
类的嵌套类
/** * 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. */
private class Match {
//一般是一个RequestMappingInfo
private final T mapping;
private final HandlerMethod handlerMethod;
public Match(T mapping, HandlerMethod handlerMethod) {
this.mapping = mapping;
this.handlerMethod = handlerMethod;
}
@Override
public String toString() {
return this.mapping.toString();
}
}
这个类没什么方法,它只是用来包装
RequestMappingInfo
->HandlerMethod
的映射关系
5.1.4 addMatchingMappings()
方法,初步得到所有匹配的RequestMappingInfo->HandlerMethod
映射
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
/** * 创建了一个Match对象放入集合matches中 * Match中保存了RequestMappingInfo->HandlerMethod的映射关系 */
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
/** * 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. */
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
/** * 获取匹配当前请求的RequestMappingInfo,见5.1.2 * 可能是原来的RequestMappingInfo,也可能是一个新的RequestMappingInfo对象 */
return info.getMatchingCondition(request);
}
方法遍历该请求对应的
RequestMappingInfo
请求映射信息,通过RequestMappingInfo
找到对应的HandlerMethod
,最后将RequestMappingInfo->HandlerMethod
的映射关系封装为一个Match
对象
5.1.5 handleMatch()
方法,解析请求路径的模板变量,矩阵变量和请求的媒体类型
//路径匹配器
private PathMatcher pathMatcher = new AntPathMatcher();
//org.springframework.web.servlet.HandlerMapping.bestMatchingPattern
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
/** * 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 */
@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();
/** * 找到uri模板变量 * 对于/hotels/{hotel},真实请求/hotels/1,此时该方法就会返回hotel->1映射关系 */
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);
}
//解码指定的模板变量,见5.2.5
Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
/** * 解码后的模板变量保存到请求域中 * org.springframework.web.servlet.HandlerMapping.uriTemplateVariables */
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
//解析媒体类型
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
父类handleMatch()
方法
/** * 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 */
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
/** * 可以看到这个方法只是将映射路径(url)保存到请求域中 * org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping */
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
这个方法主要做
3
件事情
- 获取请求路径上的模板变量,解码并保存到请求域中
- 获取请求路径上的矩阵变量,解码并保存到请求域中
- 获取请求的媒体类型,并保存到请求域中
5.2 UrlPathHelper
类,url
路径匹配的帮助类
这个类帮助解析请求的
url
路径,然后springmvc
就可以根据它找到对应@RequestMapping
注解映射的处理器方法
5.2.1 getContextPath()
方法,获取上下文路径
//include请求
public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
//默认开启了url解码
private boolean urlDecode = true;
//默认的编码方式"ISO-8859-1"
private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
/** * 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 */
public String getContextPath(HttpServletRequest request) {
//include请求上下文路径值
String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE);
if (contextPath == null) {
//调用原生的getContextPath()方法,这个方法应该很熟悉
contextPath = request.getContextPath();
}
/** * 实际上就是检查contextPath对应的字符串是不是'/' * 这种情况它的上下文路径应该为空,避免后面拼接字符串有两个/ */
if (StringUtils.matchesCharacter(contextPath, '/')) {
// Invalid case, but happens for includes on Jetty: silently adapt it.
contextPath = "";
}
//路径解码后返回
return decodeRequestString(request, contextPath);
}
/** * 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) */
public String decodeRequestString(HttpServletRequest request, String source) {
//url解码
if (this.urlDecode) {
return decodeInternal(request, source);
}
return source;
}
private String decodeInternal(HttpServletRequest request, String source) {
//获取当前请求的编码方式
String enc = determineEncoding(request);
try {
//按照指定的方式进行解码
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());
}
//解析发生异常就使用URLDecoder解析,最终办法
return URLDecoder.decode(source);
}
}
/** * 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 */
protected String determineEncoding(HttpServletRequest request) {
/** * request原生方法获取当前请求的编码方式("ISO-8859-1"、"UTF8") * 如果用户未指定编码方式,则使用默认的编码方式 */
String enc = request.getCharacterEncoding();
if (enc == null) {
//获取默认的编码方式
enc = getDefaultEncoding();
}
return enc;
}
/** * Return the default character encoding to use for URL decoding. */
protected String getDefaultEncoding() {
//"ISO-8859-1"
return this.defaultEncoding;
}
可以看到获取上下文路径的过程还是比较繁琐的,步骤大致如下:
- 使用原生的
request.getContextPath()
方法获取上下文路径contextPath
- 使用原生的
request.getCharacterEncoding()
方法获取请求的编码方式enc
- 如果用户未指定编码方式,则使用默认的编码方式
ISO-8859-1
- 调用
UriUtils.decode(contextPath, enc)
方法解析上下文路径- 解析过程发生异常就使用
URLDecoder.decode(contextPath)
方法解析,最终办法
5.2.2 getRequestUri(HttpServletRequest request)
方法,获取uri
路径
public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
private boolean removeSemicolonContent = true;
/** * 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 */
public String getRequestUri(HttpServletRequest request) {
//include请求uri路径值
String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == null) {
/** * 调用原生的getRequestURI()方法获取uri路径 * uri路径中包括上下文路径 */
uri = request.getRequestURI();
}
//对url进行解码,并去掉;后面无关的部分
return decodeAndCleanUriString(request, uri);
}
/** * Decode the supplied URI string and strips any extraneous portion after a ';'. */
private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
//删除uri中分号后面的内容
uri = removeSemicolonContent(uri);
//uri路径解码
uri = decodeRequestString(request, uri);
//将uri路径中所有的//替换为/
uri = getSanitizedPath(uri);
return uri;
}
/** * 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 */
public String removeSemicolonContent(String requestUri) {
/** * removeSemicolonContent属性默认true,表示去掉;后面的内容 * 为false表示只去掉Jsessionid * 不管是true还是false,Jsessionid总是被删掉 */
return (this.removeSemicolonContent ?
removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri));
}
private String removeSemicolonContentInternal(String requestUri) {
//第一个;的索引
int semicolonIndex = requestUri.indexOf(';');
//while循环删掉所有;后面的内容
while (semicolonIndex != -1) {
int slashIndex = requestUri.indexOf('/', semicolonIndex);
String start = requestUri.substring(0, semicolonIndex);
requestUri = (slashIndex != -1) ? start + requestUri.substring(slashIndex) : start;
semicolonIndex = requestUri.indexOf(';', semicolonIndex);
}
return requestUri;
}
//删掉Jsessionid的内容
private String removeJsessionid(String requestUri) {
String key = ";jsessionid=";
int index = requestUri.toLowerCase().indexOf(key);
if (index == -1) {
return requestUri;
}
String start = requestUri.substring(0, index);
for (int i = key.length(); i < requestUri.length(); i++) {
char c = requestUri.charAt(i);
if (c == ';' || c == '/') {
return start + requestUri.substring(i);
}
}
return start;
}
简单总结一下流程如下:
- 使用原生的
request.getRequestURI()
方法获取uri路径
- 去掉
;
后面的内容,总是去掉jsessionid
uri路径
解码,和上下文路径解码过程一样- 将
uri路径
中所有的//
替换为/
5.2.3 getRemainingPath()
方法,获取剩余路径
/** * 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. */
@Nullable
private String getRemainingPath(String requestUri, String mapping, boolean ignoreCase) {
int index1 = 0;
int index2 = 0;
for (; (index1 < requestUri.length()) && (index2 < mapping.length()); index1++, index2++) {
char c1 = requestUri.charAt(index1);
char c2 = mapping.charAt(index2);
if (c1 == ';') {
index1 = requestUri.indexOf('/', index1);
if (index1 == -1) {
return null;
}
c1 = requestUri.charAt(index1);
}
if (c1 == c2 || (ignoreCase && (Character.toLowerCase(c1) == Character.toLowerCase(c2)))) {
continue;
}
return null;
}
if (index2 != mapping.length()) {
return null;
}
else if (index1 == requestUri.length()) {
return "";
}
else if (requestUri.charAt(index1) == ';') {
index1 = requestUri.indexOf('/', index1);
}
return (index1 != -1 ? requestUri.substring(index1) : "");
}
可以看到,这个方法比较
uri路径
和上下文路径
- 如果
uri路径
是以上下文路径
开头的,则截取掉开头部分,获取到剩下的路径- 不以以
上下文路径
开头,要么返回null
,要么返回空串
5.2.4 截取掉剩余路径中的Servlet
映射路径
/** * 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 */
protected String getPathWithinServletMapping(HttpServletRequest request, String pathWithinApp) {
String servletPath = getServletPath(request);
String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
String path;
// If the app container sanitized the servletPath, check against the sanitized version
if (servletPath.contains(sanitizedPathWithinApp)) {
path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
}
else {
path = getRemainingPath(pathWithinApp, servletPath, false);
}
if (path != null) {
// Normal case: URI contains servlet path.
return path;
}
else {
// Special case: URI is different from servlet path.
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
// Use path info if available. Indicates index page within a servlet mapping?
// e.g. with index page: URI="/", servletPath="/index.html"
return pathInfo;
}
if (!this.urlDecode) {
// No path info... (not mapped by prefix, nor by extension, nor "/*")
// For the default servlet mapping (i.e. "/"), urlDecode=false can
// cause issues since getServletPath() returns a decoded path.
// If decoding pathWithinApp yields a match just use pathWithinApp.
path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
if (path != null) {
return pathWithinApp;
}
}
// Otherwise, use the full servlet path.
return servletPath;
}
}
方法具体如何完成截取逻辑,我们不关心。从注释可以看到
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()
方法,对指定请求变量进行解码
/** * 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 */
public Map<String, String> decodePathVariables(HttpServletRequest request, Map<String, String> vars) {
/** * true,那么之前获取uri和contextPath的时候就已经解码过了 * 此时直接返回,不用解码了 */
if (this.urlDecode) {
return vars;
}
//解码
else {
Map<String, String> decodedVars = new LinkedHashMap<>(vars.size());
//遍历,对每一个变量调用decodeInternal()方法进行解码,见5.2.1
vars.forEach((key, value) -> decodedVars.put(key, decodeInternal(request, value)));
return decodedVars;
}
}
5.2.6 getLookupPathForRequest(HttpServletRequest request)
方法,获取用户请求路径
//使用完整路径去映射@RequestMapping注解方法
private boolean alwaysUseFullPath = false;
/** * 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 */
public String getLookupPathForRequest(HttpServletRequest request) {
//获取用户实际的请求路径
String pathWithinApp = getPathWithinApplication(request);
// Always use full path within current servlet context?
//总是使用完整路径去映射@RequestMapping注解方法
if (this.alwaysUseFullPath) {
return pathWithinApp;
}
// Else, use path within current servlet mapping if applicable
//再截取掉Servlet映射路径,见5.2.4
String rest = getPathWithinServletMapping(request, pathWithinApp);
if (StringUtils.hasLength(rest)) {
return rest;
}
else {
return pathWithinApp;
}
}
获取用户实际的请求路径,该请求路径应该和某个@RequestMapping
注解方法映射一致
/** * 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 */
public String getPathWithinApplication(HttpServletRequest request) {
//获取上下文路径,实际上就是项目名,见5.2.1
String contextPath = getContextPath(request);
//获取uri路径,见5.2.2
String requestUri = getRequestUri(request);
//获取实际的@RequestMapping注解方法映射的路径,见5.2.3
String path = getRemainingPath(requestUri, contextPath, true);
if (path != null) {
// Normal case: URI contains context path.
return (StringUtils.hasText(path) ? path : "/");
}
else {
return requestUri;
}
}
该方法会获取用户请求的实际路径,什么叫实际路径?
也就是这个路径可以直接映射处理器方法,为了得到这个路径,
springmvc
做了3
次处理
- 调用
request.getRequestURI()
方法得到本次请求的uri
路径- 调用
request.getContextPath()
方法还得到上下文路径DispatcherServlet
的映射路径uri
路径去掉上下文路径和Servlet
的映射路径才是实际路径
5.2.7 getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName)
方法,获取用户请求路径(缓存)
/** * 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 */
public String getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName) {
/** * org.springframework.web.servlet.HandlerMapping.lookupPath * 先从请求域中取,因为之前调用getLookupPathForRequest(request)方法之后,将得到的实际路径 * 保存到了请求域中,见5 */
if (lookupPathAttributeName != null) {
String result = (String) request.getAttribute(lookupPathAttributeName);
if (result != null) {
return result;
}
}
//重新获取实际路径,见5.2.6
return getLookupPathForRequest(request);
}
相比于
5.2.6
只是增加了从缓存中取值的过程。
5.3 createWithResolvedBean()
方法,重新解析处理器方法
/** * 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. */
public HandlerMethod createWithResolvedBean() {
//处理器方法所在的bean
Object handler = this.bean;
//未实例化
if (this.bean instanceof String) {
Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
String beanName = (String) this.bean;
//实例化
handler = this.beanFactory.getBean(beanName);
}
//重新创建一个HandlerMethod对象,拷贝原对象的值
return new HandlerMethod(this, handler);
}
如果处理器方法所在的
bean
还未实例化,就先实例化,然后重新克隆一个HandlerMethod
对象
6 getHandlerExecutionChain(handler, request)
方法,获取HandlerExecutionChain
处理器执行链
//这个请求域属性在5中用到过
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
//拦截器对象
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
/** * 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() */
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
/** * 初始化一个HandlerExecutionChain,见6.1 * 此时只是向里面设置了处理器方法,还没有设置拦截器 * 下面的流程就是获取本次请求需要执行的拦截器,并设置到HandlerExecutionChain对象中 */
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//从缓存中获取用户请求的实际路径,见5.2.7
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
//遍历所有的拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
//MappedInterceptor类型的拦截器,见6.2
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
//拦截器拦截匹配路径的请求
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
//普通类型的拦截器,拦截所有请求
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
6.1 HandlerExecutionChain
类,处理器执行链
public class HandlerExecutionChain {
private final Object handler;
//拦截器
@Nullable
private HandlerInterceptor[] interceptors;
//处理器方法
@Nullable
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex = -1;
/** * Create a new HandlerExecutionChain. * @param handler the handler object to execute */
public HandlerExecutionChain(Object handler) {
this(handler, (HandlerInterceptor[]) null);
}
/** * 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 */
public HandlerExecutionChain(Object handler, @Nullable HandlerInterceptor... interceptors) {
//拷贝原来的HandlerExecutionChain到新对象中
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList<>();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}
}
封装了处理本次请求要执行的拦截器和处理器方法
6.2 MappedInterceptor
拦截器适配器
MappedInterceptor
是一个final
类,实现了HandlerInterceptor
接口
public final class MappedInterceptor implements HandlerInterceptor {
//拦截路径
@Nullable
private final String[] includePatterns;
//不拦截的路径
@Nullable
private final String[] excludePatterns;
//真正的拦截器
private final HandlerInterceptor interceptor;
//路径匹配器
@Nullable
private PathMatcher pathMatcher;
/** * 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 */
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
//不拦截路径匹配成功返回false
if (!ObjectUtils.isEmpty(this.excludePatterns)) {
for (String pattern : this.excludePatterns) {
if (pathMatcherToUse.match(pattern, lookupPath)) {
return false;
}
}
}
//拦截路径为空,返回true,表示全部拦截
if (ObjectUtils.isEmpty(this.includePatterns)) {
return true;
}
//拦截路径匹配成功返回true
for (String pattern : this.includePatterns) {
if (pathMatcherToUse.match(pattern, lookupPath)) {
return true;
}
}
return false;
}
}
普通的拦截器会拦截所有请求,而
MappedInterceptor
拦截器则是有目的拦截,用户配置的普通的拦截器最终会被适配为MappedInterceptor
,以实现选择性拦截。
还没有评论,来说两句吧...