首页 技术 正文
技术 2022年11月9日
0 收藏 928 点赞 2,875 浏览 19915 个字

RequestMapping 注解的解析、匹配、注册

1)创建 RequestMappingHandlerMapping 实例时会触发 afterPropertiesSet 调用。
2)读取容器中所有带有 Controller 或 RequestMapping 注解的类。
3)读取此类中所有满足过滤器 ReflectionUtils.USER_DECLARED_METHODS 的方法,
读取处理方法上的 RequestMapping 注解信息,
将其解析并封装为 RequestMappingInfo 注册到 RequestMappingHandlerMapping#mappingRegistry 中。RequestMappingHandlerMapping#
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 1)从处理方法中读取 RequestMapping 信息并创建 RequestMappingInfo
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 2)从处理器类中读取 RequestMapping 信息并创建 RequestMappingInfo
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 如果存在,则合并
info = typeInfo.combine(info);
}
// 3)如果处理类上配置了前缀路径
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
// 则完成路径拼接
info = RequestMappingInfo.paths(prefix).build().combine(info);
}
}
return info;
} @Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// 读取注解元素上的 RequestMapping 注解信息
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
if (requestMapping == null) {
return null;
} /**
* 1)如果是 class,则通过 getCustomTypeCondition 读取 RequestCondition
* 2)如果是 method,则通过 getCustomMethodCondition 读取 RequestCondition
* 特性未实现,都返回 null
*/
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return createRequestMappingInfo(requestMapping, condition);
} protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) { RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}

解析 RequestMapping#path

1)解析 path/value 参数中指定的所有路径
2)如果合并的 path 参数不以 / 开头,则添加前置的 /【最佳实践:编写的每个请求路径都以 / 开头,避免不必要的调用】
3)注入 UrlPathHelper 用于读取 request 的请求路径,注入 AntPathMatcher 用于完成路径匹配【如果未指定】。PatternsRequestCondition#
/**
* 指定的所有请求路径
*/
private final Set<String> patterns;
/**
* 用于读取请求路径的工具类
*/
private final UrlPathHelper pathHelper;
/**
* 用于执行路径匹配的 AntPathMatcher
*/
private final PathMatcher pathMatcher;
/**
* 是否启用后缀模式,默认为 false
*/
private final boolean useSuffixPatternMatch;
/**
* 是否自动添加尾部 /,默认为 true
*/
private final boolean useTrailingSlashMatch; public PatternsRequestCondition(String[] patterns, @Nullable UrlPathHelper urlPathHelper,
@Nullable PathMatcher pathMatcher, boolean useSuffixPatternMatch,
boolean useTrailingSlashMatch, @Nullable List<String> fileExtensions) { this(Arrays.asList(patterns), urlPathHelper, pathMatcher, useSuffixPatternMatch,
useTrailingSlashMatch, fileExtensions);
} private PatternsRequestCondition(Collection<String> patterns, @Nullable UrlPathHelper urlPathHelper,
@Nullable PathMatcher pathMatcher, boolean useSuffixPatternMatch,
boolean useTrailingSlashMatch, @Nullable List<String> fileExtensions) { this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.pathHelper = (urlPathHelper != null ? urlPathHelper : new UrlPathHelper());
this.pathMatcher = (pathMatcher != null ? pathMatcher : new AntPathMatcher());
this.useSuffixPatternMatch = useSuffixPatternMatch;
this.useTrailingSlashMatch = useTrailingSlashMatch; if (fileExtensions != null) {
for (String fileExtension : fileExtensions) {
if (fileExtension.charAt(0) != '.') {
fileExtension = "." + fileExtension;
}
this.fileExtensions.add(fileExtension);
}
}
} private static Set<String> prependLeadingSlash(Collection<String> patterns) {
Set<String> result = new LinkedHashSet<>(patterns.size());
for (String pattern : patterns) {
// 如果请求路径不是以 / 开头,则添加 / 前缀
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
  • RequestMapping#path 的匹配过程
PatternsRequestCondition#
/**
* Checks if any of the patterns match the given request and returns an instance
* that is guaranteed to contain matching patterns, sorted via
* {@link PathMatcher#getPatternComparator(String)}.
* <p>A matching pattern is obtained by making checks in the following order:
* <ul>
* <li>Direct match
* <li>Pattern match with ".*" appended if the pattern doesn't already contain a "."
* <li>Pattern match
* <li>Pattern match with "/" appended if the pattern doesn't already end in "/"
* </ul>
*/
@Override
@Nullable
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
// 1)如果未指定请求路径,则默认匹配
if (patterns.isEmpty()) {
return this;
}
// 读取请求路径
final String lookupPath = pathHelper.getLookupPathForRequest(request);
// 读取匹配的所有路径
final List<String> matches = getMatchingPatterns(lookupPath);
return !matches.isEmpty() ?
new PatternsRequestCondition(matches, pathHelper, pathMatcher,
useSuffixPatternMatch, useTrailingSlashMatch, fileExtensions) : null;
} /**
* Find the patterns matching the given lookup path.
*/
public List<String> getMatchingPatterns(String lookupPath) {
final List<String> matches = new ArrayList<>();
for (final String pattern : patterns) {
final String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches.add(match);
}
}
if (matches.size() > 1) {
matches.sort(pathMatcher.getPatternComparator(lookupPath));
}
return matches;
} @Nullable
private String getMatchingPattern(String pattern, String lookupPath) {
// 1)pattern 和请求路径相等
if (pattern.equals(lookupPath)) {
return pattern;
}
// 2)是否使用后缀模式,默认为 false
if (useSuffixPatternMatch) {
if (!fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
for (final String extension : fileExtensions) {
if (pathMatcher.match(pattern + extension, lookupPath)) {
return pattern + extension;
}
}
}
else {
final boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
}
}
// 3)使用 pathMatcher 指定路径匹配,默认是 AntPathMatcher
if (pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
// 4)默认为 true
if (useTrailingSlashMatch) {
// 给 pattern 添加 / 后缀之后再次进行匹配
if (!pattern.endsWith("/") && pathMatcher.match(pattern + "/", lookupPath)) {
return pattern +"/";
}
}
return null;
}

解析 RequestMapping#method

1)写入所有支持的 HttpMethod
RequestMethodsRequestCondition#
/**
* 支持的所有请求方法
*/
private final Set<RequestMethod> methods; public RequestMethodsRequestCondition(RequestMethod... requestMethods) {
this(Arrays.asList(requestMethods));
} private RequestMethodsRequestCondition(Collection<RequestMethod> requestMethods) {
this.methods = Collections.unmodifiableSet(new LinkedHashSet<>(requestMethods));
}
  • RequestMapping#method 的匹配过程
RequestMethodsRequestCondition#
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
if (CorsUtils.isPreFlightRequest(request)) {
return matchPreFlight(request);
} // 1)如果未指定 RequestMapping#method
if (getMethods().isEmpty()) {
// 请求方法为 OPTIONS && 请求的分派类型不是 DispatcherType.ERROR
if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&
!DispatcherType.ERROR.equals(request.getDispatcherType())) {
return null; // No implicit match for OPTIONS (we handle it)
}
return this;
} return matchRequestMethod(request.getMethod());
} @Nullable
private RequestMethodsRequestCondition matchRequestMethod(String httpMethodValue) {
HttpMethod httpMethod = HttpMethod.resolve(httpMethodValue);
if (httpMethod != null) {
// 1)支持的请求方法列表中存在此 HttpMethod
for (RequestMethod method : getMethods()) {
if (httpMethod.matches(method.name())) {
return new RequestMethodsRequestCondition(method);
}
}
/**
* 2)如果是 HttpMethod.HEAD 方式
* && 支持的请求方式列表中存在 RequestMethod.GET,则返回 GET
*/
if (httpMethod == HttpMethod.HEAD && getMethods().contains(RequestMethod.GET)) {
return GET_CONDITION;
}
}
return null;
}

解析 RequestMapping#params

1)将请求参数封装为 ParamExpression 后写入
ParamsRequestCondition
/**
* 参数表达式集合
*/
private final Set<ParamExpression> expressions; public ParamsRequestCondition(String... params) {
this(parseExpressions(params));
} private ParamsRequestCondition(Collection<ParamExpression> conditions) {
this.expressions = Collections.unmodifiableSet(new LinkedHashSet<>(conditions));
}
  • RequestMapping#params 匹配过程
ParamsRequestCondition#
@Override
@Nullable
public ParamsRequestCondition getMatchingCondition(HttpServletRequest request) {
// 只要有一个参数不匹配,则请求不匹配
for (final ParamExpression expression : expressions) {
if (!expression.match(request)) {
return null;
}
}
return this;
}AbstractNameValueExpression#
public final boolean match(HttpServletRequest request) {
boolean isMatch;
// 1)如果指定了参数值,则执行值匹配
if (this.value != null) {
isMatch = matchValue(request);
}
// 2)执行名称匹配
else {
isMatch = matchName(request);
}
return (this.isNegated ? !isMatch : isMatch);
}ParamExpression#
@Override
protected boolean matchName(HttpServletRequest request) {
// 表单参数中存在该参数 || 参数集合中存在该参数
return (WebUtils.hasSubmitParameter(request, this.name) ||
request.getParameterMap().containsKey(this.name));
} @Override
protected boolean matchValue(HttpServletRequest request) {
// 请求参数 name 的参数值和配置值相等
return ObjectUtils.nullSafeEquals(this.value, request.getParameter(this.name));
}

解析 RequestMapping#headers

HeadersRequestCondition#
/**
* 解析的 header 参数集合
*/
private final Set<HeaderExpression> expressions; public HeadersRequestCondition(String... headers) {
this(parseExpressions(headers));
} private HeadersRequestCondition(Collection<HeaderExpression> conditions) {
this.expressions = Collections.unmodifiableSet(new LinkedHashSet<>(conditions));
} private static Collection<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<>();
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
// 如果是 Accept 和 Content-Type 头,则忽略
if ("Accept".equalsIgnoreCase(expr.name) || "Content-Type".equalsIgnoreCase(expr.name)) {
continue;
}
expressions.add(expr);
}
return expressions;
}
  • RequestMapping#headers 匹配过程
HeadersRequestCondition#
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
if (CorsUtils.isPreFlightRequest(request)) {
return PRE_FLIGHT_MATCH;
}
// 只要有一个请求头不匹配,则该请求不匹配
for (final HeaderExpression expression : expressions) {
if (!expression.match(request)) {
return null;
}
}
return this;
}AbstractNameValueExpression#
public final boolean match(HttpServletRequest request) {
boolean isMatch;
// 1)如果配置了请求头的值,则执行值匹配
if (this.value != null) {
isMatch = matchValue(request);
}
// 2)执行请求头名称匹配
else {
isMatch = matchName(request);
}
return (this.isNegated ? !isMatch : isMatch);
}HeaderExpression#
// 存在目标请求头
@Override
protected boolean matchName(HttpServletRequest request) {
return request.getHeader(name) != null;
} // 请求头的值和配置值相等
@Override
protected boolean matchValue(HttpServletRequest request) {
return ObjectUtils.nullSafeEquals(value, request.getHeader(name));
}

解析 RequestMapping#consumes

1)如果 headers 中指定了 Content-Type 属性,则将其解析并加入到 ConsumesRequestCondition#expressions 中。
2)解析 consumes 参数中配置的所有 MediaType,并将其加入到 ConsumesRequestCondition#expressions 中。ConsumesRequestCondition#
public ConsumesRequestCondition(String[] consumes, @Nullable String[] headers) {
this(parseExpressions(consumes, headers));
} private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
} private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, @Nullable String[] headers) {
final Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>();
// 1)如果 headers 参数不为 null && headers 中存在 Content-Type 配置,则将其加入到 result 中。
if (headers != null) {
for (final String header : headers) {
final HeaderExpression expr = new HeaderExpression(header);
if ("Content-Type".equalsIgnoreCase(expr.name) && expr.value != null) {
for (final MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ConsumeMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
// 2)解析 consumes 参数中配置的所有 MediaType,将其加入到 result 中。
for (final String consume : consumes) {
result.add(new ConsumeMediaTypeExpression(consume));
}
return result;
}AbstractMediaTypeExpression#
/**
* 解析完成的 MediaType 类型
*/
private final MediaType mediaType;
/**
* 是否是反向匹配
*/
private final boolean isNegated; AbstractMediaTypeExpression(String expression) {
/**
* 如果表达式以 ! 开头,则表示反向匹配
*/
if (expression.startsWith("!")) {
this.isNegated = true;
expression = expression.substring(1);
}
else {
this.isNegated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}MediaType#
public static MediaType parseMediaType(String mediaType) {
MimeType type;
try {
type = MimeTypeUtils.parseMimeType(mediaType);
}
catch (InvalidMimeTypeException ex) {
throw new InvalidMediaTypeException(ex);
}
try {
return new MediaType(type.getType(), type.getSubtype(), type.getParameters());
}
catch (IllegalArgumentException ex) {
throw new InvalidMediaTypeException(mediaType, ex.getMessage());
}
}
  • RequestMapping#consumes 匹配过程
ConsumesRequestCondition#
@Override
@Nullable
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (CorsUtils.isPreFlightRequest(request)) {
return PRE_FLIGHT_MATCH;
} // 1)如果未指定 consumes 参数则默认匹配
if (isEmpty()) {
return this;
} // 2)RequestMapping 指定了 consumes 参数,则执行匹配过程
MediaType contentType;
try {
// 读取请求的 Content-Type 属性并将其转换为 MediaType
contentType = StringUtils.hasLength(request.getContentType()) ?
MediaType.parseMediaType(request.getContentType()) :
MediaType.APPLICATION_OCTET_STREAM;
}
catch (final InvalidMediaTypeException ex) {
// 3)如果请求的 Content-Type 非法,则不匹配
return null;
} // 3)读取所有指定的 consumes 参数表达式,进行逐个匹配
final Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>(expressions);
return result.stream()
.anyMatch(expression->expression.match(contentType)) ? new ConsumesRequestCondition(result) : null;
}单个 MediaType 的匹配过程
ConsumeMediaTypeExpression#
public final boolean match(MediaType contentType) {
// 当前 MediaType 是否匹配目标 contentType
final boolean match = getMediaType().includes(contentType);
// 是否是反向匹配 && 读取匹配结果
return !isNegated() ? match : !match;
}

解析 RequestMapping#produces

ProducesRequestCondition#
/**
* 支持的结果类型 MediaType
*/
private final List<ProduceMediaTypeExpression> expressions; public ProducesRequestCondition(String[] produces, @Nullable String[] headers,
@Nullable ContentNegotiationManager manager) { expressions = new ArrayList<>(parseExpressions(produces, headers));
Collections.sort(expressions);
contentNegotiationManager = manager != null ? manager : new ContentNegotiationManager();
} private Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, @Nullable String[] headers) {
final Set<ProduceMediaTypeExpression> result = new LinkedHashSet<>();
// 1)如果存在 headers 配置 && 将 Accept 头配置加入到 result 中
if (headers != null) {
for (final String header : headers) {
final HeaderExpression expr = new HeaderExpression(header);
if ("Accept".equalsIgnoreCase(expr.name) && expr.value != null) {
for (final MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ProduceMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
// 2)将所有配置的 MediaType 加入到 result 中
for (final String produce : produces) {
result.add(new ProduceMediaTypeExpression(produce));
}
return result;
}
  • RequestMapping#produces 的匹配过程
ProducesRequestCondition#
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (CorsUtils.isPreFlightRequest(request)) {
return PRE_FLIGHT_MATCH;
}
// 1)如果未配置 produces 则匹配
if (isEmpty()) {
return this;
}
// 2)解析客户端能接受的所有 MediaType
List<MediaType> acceptedMediaTypes;
try {
acceptedMediaTypes = getAcceptedMediaTypes(request);
}
catch (final HttpMediaTypeException ex) {
return null;
} // 3)配置的 MediaType 列表中存在请求能接受的 MediaType
final Set<ProduceMediaTypeExpression> result = new LinkedHashSet<>(expressions);
result.removeIf(expression -> !expression.match(acceptedMediaTypes));
if (!result.isEmpty()) {
return new ProducesRequestCondition(result, contentNegotiationManager);
}
// 4)如果客户端能接受所有结果类型 */*
else if (acceptedMediaTypes.contains(MediaType.ALL)) {
return EMPTY_CONDITION;
}
else {
return null;
}
}ProduceMediaTypeExpression#
public final boolean match(List<MediaType> acceptedMediaTypes) {
final boolean match = matchMediaType(acceptedMediaTypes);
return !isNegated() ? match : !match;
} private boolean matchMediaType(List<MediaType> acceptedMediaTypes) {
for (final MediaType acceptedMediaType : acceptedMediaTypes) {
// 当前 MediaType 和目标 MediaType 匹配
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
}
}
return false;
}

RequestMappingInfo 的匹配过程

RequestMappingInfo#
/**
* @RequestMapping 的 name 属性值
*/
@Nullable
private final String name; /**
* @RequestMapping path 参数匹配条件
*/
private final PatternsRequestCondition patternsCondition;
/**
* @RequestMapping method 参数匹配条件
*/
private final RequestMethodsRequestCondition methodsCondition;
/**
* @RequestMapping params 参数匹配条件
*/
private final ParamsRequestCondition paramsCondition;
/**
* @RequestMapping headers 参数匹配条件
*/
private final HeadersRequestCondition headersCondition;
/**
* @RequestMapping consumers 参数匹配条件
*/
private final ConsumesRequestCondition consumesCondition;
/**
* @RequestMapping produces 参数匹配条件
*/
private final ProducesRequestCondition producesCondition; private final RequestConditionHolder customConditionHolder; /**
* 使用此 RequestMappingInfo 中的所有条件来匹配目标请求,如果匹配,
* 则返回一个新的 RequestMappingInfo,否则返回 null。
*/
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
final RequestMethodsRequestCondition methods = methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
} final ParamsRequestCondition params = paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
} final HeadersRequestCondition headers = headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
} final ConsumesRequestCondition consumes = consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
} final ProducesRequestCondition produces = producesCondition.getMatchingCondition(request);
if (produces == null) {
return null;
} final PatternsRequestCondition patterns = patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
} final RequestConditionHolder custom = customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
} return new RequestMappingInfo(name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}

RequestMappingInfo 的注册过程

AbstractHandlerMethodMapping#
private final MappingRegistry mappingRegistry = new MappingRegistry(); class MappingRegistry {
/**
* RequestMappingInfo 和 MappingRegistration 的注册缓存
*/
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
/**
* RequestMappingInfo 和 HandlerMethod 的注册缓存
*/
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
/**
* url 和 RequestMappingInfo 的注册缓存
*/
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
/**
* MappingName 和 List<HandlerMethod> 的注册缓存
*/
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
/**
* HandlerMethod 和 CorsConfiguration 的注册缓存
*/
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
/**
* 保障线程安全的读写锁
*/
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void register(T mapping, Object handler, Method method) {
// 获取读锁
this.readWriteLock.writeLock().lock();
try {
// 创建封装了 handler 和 method 的 HandlerMethod 实例,
final HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 确保映射是唯一的
assertUniqueMethodMapping(handlerMethod, mapping);
// 写入 RequestMappingInfo 和 handlerMethod 映射到 mappingLookup 缓存
this.mappingLookup.put(mapping, handlerMethod); final List<String> directUrls = getDirectUrls(mapping);
// 将配置的 url 和 RequestMappingInfo 映射写入 urlLookup 缓存
for (final String url : directUrls) {
this.urlLookup.add(url, mapping);
} String name = null;
if (getNamingStrategy() != null) {
/**
* 根据命名策略读取映射的名称
* HelloCont.hello() => HC#hello
*/
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
} // 读取控制器或处理方法上 @CrossOrigin 注解配置的跨域信息
final CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
// 写入 corsLookup 缓存中
this.corsLookup.put(handlerMethod, corsConfig);
} // 将 RequestMappingInfo 和 MappingRegistration 映射写入 registry 中
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
} private List<String> getDirectUrls(T mapping) {
final List<String> urls = new ArrayList<>(1);
// 从 RequestMappingInfo 中读取配置的请求映射集合
for (final String path : getMappingPathPatterns(mapping)) {
// 如果是直接的 Url【pattern 不包含 * 和 ?】
if (!getPathMatcher().isPattern(path)) {
urls.add(path);
}
}
return urls;
} private void addMappingName(String name, HandlerMethod handlerMethod) {
// 根据 MappingName 读取 HandlerMethod 列表
List<HandlerMethod> oldList = this.nameLookup.get(name);
if (oldList == null) {
oldList = Collections.emptyList();
} // 如果目标 HandlerMethod 已经存在,则直接返回
for (final HandlerMethod current : oldList) {
if (handlerMethod.equals(current)) {
return;
}
} // 将 MappingName 和 HandlerMethod 映射写入 nameLookup 中
final List<HandlerMethod> newList = new ArrayList<>(oldList.size() + 1);
newList.addAll(oldList);
newList.add(handlerMethod);
this.nameLookup.put(name, newList);
}
} private static class MappingRegistration<T> {
private final T mapping;
private final HandlerMethod handlerMethod;
private final List<String> directUrls;
@Nullable
private final String mappingName;
} private class Match {
private final T mapping;
private final HandlerMethod handlerMethod;
}
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,489
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,904
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,737
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,490
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,128
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,290