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;
}

RequestMapping 注解的解析、匹配、注册的更多相关文章

  1. Spring源码解析(三)BeanDefinition的载入、解析和注册

    通过上一篇源码的分析已经完成了BeanDefinition资源文件的定位,本篇继续分析BeanDefinition资源文件的载入和解析. AbstractBeanDefinitionReader的lo ...

  2. SpringMVC源码解读 - RequestMapping注解实现解读 - RequestCondition体系

    一般我们开发时,使用最多的还是@RequestMapping注解方式. @RequestMapping(value = "/", param = "role=guest& ...

  3. SpringMVC源码解读 - RequestMapping注解实现解读

    SpringMVC源码解读 - RequestMapping注解实现解读 - RequestCondition体系  https://www.cnblogs.com/leftthen/p/520840 ...

  4. springmvc 中RequestMapping注解的使用

    1.RequestMapping注解既可以修饰方法,又可以修饰类型,类型指定的url相对于web跟路径,而方法修饰的url相对于类url: 2.RequestMapping的几个属性: value:用 ...

  5. Spring源码情操陶冶-AnnotationConfigBeanDefinitionParser注解配置解析器

    本文承接前文Spring源码情操陶冶-自定义节点的解析,分析spring中的context:annotation-config节点如何被解析 源码概览 对BeanDefinitionParser接口的 ...

  6. Spring MVC @RequestMapping注解详解

    @RequestMapping 参数说明 value:定义处理方法的请求的 URL 地址.(重点) method:定义处理方法的 http method 类型,如 GET.POST 等.(重点) pa ...

  7. 超详细 Spring @RequestMapping 注解使用技巧

    @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一.这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上. 在这篇文章中,你将会看到 @R ...

  8. Spring @RequestMapping 注解使用技巧

    @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一.这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上. 在这篇文章中,你将会看到 @R ...

  9. 超详细 Spring @RequestMapping 注解使用技巧 (转)

    @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一.这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上. 在这篇文章中,你将会看到 @R ...

随机推荐

  1. Python操作Redis,你要的都在这了!

    Redis是一个基于内存的高效的键值型非关系型数据库,存取效率极高,而且支持多种存储数据结构,使用也非常简单.本节中,我们就来介绍一下Python的Redis操作,主要介绍RedisPy这个库的用法. ...

  2. ansible 的file 模块

    创建.修改.删除文件或者目录: file模块 file模块常用的几个参数:state.path.src.dest.mode.owner.group.name.recurse state后面跟的参数:  ...

  3. C#实现Base64处理加解密

    using System;using System.Text; namespace Common{    /// <summary>    /// 实现Base64加密解密    ///  ...

  4. Django学习系列5:为视图编写单元测试

    打开lists/tests.py编写 """向浏览器返回真正的HTML响应,添加一个新的测试方法""" from django.test i ...

  5. 隐马尔可夫模型的前向算法(java实现),今天奉上

    隐马尔可夫模型的前向算法(手动实现),今天奉上,由于研究生期间,实现的时候没有多加注释,这里为了让更好的人进入自然语言处理领域,特此,将前向算法奉上,具体公式可参考52nlp的HMN系列博客. 参考了 ...

  6. Maven搭建简单的SPring+SpringMVC+Hibernate框架

    公司的项目用到的框架是Spring+SpringMVC+Hibernate 以前没有用过,所以要系统的学习一下,首先要学会怎么搭建 第一步  创建一个Maven的web项目  创建方法以前的博客中有提 ...

  7. git初始化命令行指引

    Git 全局设置 git config --global user.name "陈耿聪" git config --global user.email "swain@di ...

  8. C++为什么不可以把一个数组直接赋值给另一个数组

    今天好奇一个问题, int a[3] = {1,2,3]; int b[3]; b=a; 编译器报错, 网上找了一圈, 大概明白: C++就是如此设定的, 数组不能直接赋值, 可以使用std::cop ...

  9. java实现网页验证码功能_java - JAVA

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 本文实例为大家分享了java网页验证码的实现代码,供大家参考,具体内容如下 Servlet: package cn.bd ...

  10. ORACLE复杂查询之子查询

    子查询分为两类:标准子查询和相关子查询. 一.标准子查询:子查询先于主查询独立执行,返回明确结果供主查询使用. 子查询只执行一次,不依赖于主查询. 例如: 其中子查询能够返回结果:2450.所以断定其 ...