springmvc–@RequestMapping
注解解析原理
文章目录
- springmvc--`@RequestMapping`注解解析原理
- 1 前言
- 2 大概原理
- 3 具体原理
- 3.1 `RequestMappingHandlerMapping`类的`afterPropertiesSet()`方法
- 3.2 `AbstractHandlerMethodMapping`类的`afterPropertiesSet()`方法
- 3.3 `selectMethods(Class<?> targetType, final MetadataLookup metadataLookup)`方法,反射遍历某个类及其父接口的所有的方法,得到符合条件的方法信息集合
- 3.4 `getMappingForMethod(method, userType)`方法,创建标注`@RequestMapping`注解方法的请求映射信息
- 4 `RequestMappingInfo`
- 5 `BuilderConfiguration`
- 5.1 `setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper)`方法,配置`UrlPathHelper`
- 5.2 `setPathMatcher(@Nullable PathMatcher pathMatcher)`方法, 配置`PathMatcher`
- 5.3 `setTrailingSlashMatch(boolean trailingSlashMatch)`方法, 设置是否与URL匹配,无论是否存在斜杠
- 5.4 `setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager)`方法, 配置内容协商管理器
- 6 `DefaultBuilder`
- 7 `MappingRegistry`
- 7.1 `register(T mapping, Object handler, Method method)`方法, 注册标注`@RequestMapping`注解方法的所有映射
- 7.2 `validateMethodMapping(HandlerMethod handlerMethod, T mapping)`方法, 判断是否已经存在到该标注`@RequestMapping`注解方法的映射
- 7.3 `addMappingName(String name, HandlerMethod handlerMethod)`方法, 缓存映射名和处理器方法
- 7.4 外部类`AbstractHandlerMethodMapping`
- 8 `MappingRegistration`
1 前言
之前我在springmvc–HandlerMapping处理器映射器这篇文章中解释过如何从RequestMappingHandlerMapping
处理器映射器中得到处理对应请求的HandlerExecutionChain
处理器执行器链。当时并没有解释springmvc
是什么时候解析@RequestMapping
注解的,所以本文就补充一下这部分内容。
2 大概原理
先来看一下RequestMappingHandlerMapping
的类图
InitializingBean
接口是解析@RequestMapping
注解的核心,它会在RequestMappingHandlerMapping
初始化阶段扫描所有的@Controller
注解的类,并解析类中标有@RequestMapping
注解的方法。
3 具体原理
那么接下来的篇幅呢,我们就重点看一下InitializingBean
接口的afterPropertiesSet()
方法的具体实现。
3.1 RequestMappingHandlerMapping
类的afterPropertiesSet()
方法
直接定位到RequestMappingHandlerMapping
类的afterPropertiesSet()
方法
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
//创建请求映射的配置容器对象,见5
this.config = new RequestMappingInfo.BuilderConfiguration();
//将处理器映射器持有的UrlPathHelper对象赋值给配置容器,见5.1
this.config.setUrlPathHelper(getUrlPathHelper());
//将处理器映射器持有的PathMatcher对象赋值给配置容器,见5.2
this.config.setPathMatcher(getPathMatcher());
//是否使用注册的后缀路径进行匹配,默认为true,已过时,直接忽略
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
//是否与URL匹配,无论是否存在斜杠,默认为true,见5.3
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
//是否使用注册的后缀路径进行匹配,默认为true,已过时,直接忽略
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
//将处理器映射器持有的内容协商管理器对象赋值给配置容器,见5.4
this.config.setContentNegotiationManager(getContentNegotiationManager());
//回调了父类的afterPropertiesSet()方法,见3.2
super.afterPropertiesSet();
}
重写的
afterPropertiesSet()
方法只是创建一个BuilderConfiguration
配置对象,用来保存所有请求映射信息对象的公有配置。
3.2 AbstractHandlerMethodMapping
类的afterPropertiesSet()
方法
它也实现了InitializingBean
接口的afterPropertiesSet()
方法,实际上RequestMappingHandlerMapping
就是再次重写该类的afterPropertiesSet()
方法
/**
* Detects handler methods at initialization.
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
方法内部定义了一个
initHandlerMethods()
方法,这个方法用来初始化处理器方法。
接下来我们就看看AbstractHandlerMethodMapping
类的initHandlerMethods()
方法干了啥事?
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
//获取容器中所有的beanName,并遍历,见3.2.1
for (String beanName : getCandidateBeanNames()) {
/**
* "scopedTarget."开头的beanName实际上是原始bean
* 真实的beanName对应于spring创建的作用域代理BeanDefinition
* @Scope注解指定作用域代理
*/
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
/**
* 处理候选bean,见3.2.2
* 解析bean中的@RequestMapping注解,并生成对应RequestMappingInfo
*/
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
3.2.1 获取spring
容器中所有的beanName
//是否获取父容器中的beanName,默认不获取
private boolean detectHandlerMethodsInAncestorContexts = false;
/**
* Determine the names of candidate beans in the application context.
* @since 5.1
* @see #setDetectHandlerMethodsInAncestorContexts
* @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors
*/
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
这里默认获取当前容器中所有的
beanName
,如果想获取父容器的,可以修改detectHandlerMethodsInAncestorContexts
字段值为true
,它就会调用BeanFactoryUtils.beanNamesForTypeIncludingAncestors()
方法去父容器中匹配对应类型bean
的beanName
3.2.2 处理候选bean
/**
* Determine the type of the specified candidate bean and call
* {@link #detectHandlerMethods} if identified as a handler type.
* <p>This implementation avoids bean creation through checking
* {@link org.springframework.beans.factory.BeanFactory#getType}
* and calling {@link #detectHandlerMethods} with the bean name.
* @param beanName the name of the candidate bean
* @since 5.1
* @see #isHandler
* @see #detectHandlerMethods
*/
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
//获取beanName对应bean的类型
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
/**
* isHandler(beanType)方法,判断beanType对应的bean是不是一个处理器类
* ,即有@Controller注解,见3.2.2.1
*/
if (beanType != null && isHandler(beanType)) {
//解析处理器类中的处理器方法(@RequestMapping注解),见3.2.2.2
detectHandlerMethods(beanName);
}
}
3.2.2.1 判断beanType
对应的bean
是不是一个处理器类
/**
* Whether the given type is a handler with handler methods.
* @param beanType the type of the bean being checked
* @return "true" if this a handler type, "false" otherwise.
*/
protected abstract boolean isHandler(Class<?> beanType);
这是
AbstractHandlerMethodMapping
类的抽象方法, 具体的判断逻辑由子类RequestMappingHandlerMapping
实现
/**
* {@inheritDoc}
* <p>Expects a handler to have either a type-level @{@link Controller}
* annotation or a type-level @{@link RequestMapping} annotation.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
只要类上标注了
@Controller
注解或@RequestMapping
注解,就认为该类就是一个处理器类
3.2.2.2 解析处理器类中的@RequestMapping
注解
/**
* Look for handler methods in the specified handler bean.
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {
//得到处理器类的clazz对象
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//获取用户类,也就是使用了代理类的目标类
Class<?> userType = ClassUtils.getUserClass(handlerType);
/**
* 反射遍历userType类及其父接口的所有的方法,得到符合条件的方法信息,见3.3
* 此处会收集到所有标注了@RequestMapping注解的方法->对应的请求映射信息对象
*/
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
/**
* 过滤条件,见3.4
* 解析方法上@RequestMapping注解,并生成对应的请求映射信息对象
*/
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
/**
* 对方法进行验证
* 如果方法是私有的、且非静态的、且方法所在类实现了SpringProxy接口的
* 就抛出异常,表明该方法不符合条件
*/
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//将处理器方法和RequestMappingInfo之间的唯一映射注册到MappingRegistry对象中
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
下面是处理器方法和
RequestMappingInfo
之间唯一映射的注册过程,其实就是注册到MappingRegistry
对象中
//一个注册表,用来保存所有处理器方法和RequestMappingInfo之间的唯一映射关系
private final MappingRegistry mappingRegistry = new MappingRegistry();
/**
* Register a handler method and its unique mapping. Invoked at startup for
* each detected handler method.
* @param handler the bean name of the handler or the handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already registered
* under the same mapping
*/
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//将映射关系保存到注册表中,见7.1
this.mappingRegistry.register(mapping, handler, method);
}
可以发现,这个方法就是最核心的方法, 它会遍历容器中所有的
BeanDefinition
,反射解析这些BeanDefinition
对应类方法上的@RequestMapping
注解,并生成RequestMappingInfo
对象。最后将标注@RequestMapping
注解的方法和RequestMappingInfo
之间的唯一映射注册到MappingRegistry
对象中。
3.3 selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup)
方法,反射遍历某个类及其父接口的所有的方法,得到符合条件的方法信息集合
/**
* Select methods on the given target type based on the lookup of associated metadata.
* <p>Callers define methods of interest through the {@link MetadataLookup} parameter,
* allowing to collect the associated metadata into the result map.
* @param targetType the target type to search methods on
* @param metadataLookup a {@link MetadataLookup} callback to inspect methods of interest,
* returning non-null metadata to be associated with a given method if there is a match,
* or {@code null} for no match
* @return the selected methods associated with their metadata (in the order of retrieval),
* or an empty map in case of no match
*/
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = new LinkedHashMap<>();
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Class<?> specificHandlerType = null;
if (!Proxy.isProxyClass(targetType)) {
specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
//得到该类及其所有的父接口
handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
//遍历
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
/**
* 使用反射工具类遍历指定类的所有方法
* ReflectionUtils.USER_DECLARED_METHODS是一个方法过滤器,
* 过滤得到所有非桥接且非Synthetic的方法
*/
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
/**
* 调用MetadataLookup接口的inspect()方法
* 实际上就是调用getMappingForMethod(method, userType)方法,
* java8的函数式编程
*/
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
//获取当前方法的桥接方法
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
//桥接方法和原来的方法是同一个,那么就将该方法保存下来
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
该方法是
MethodIntrospector
方法内省工具类的一个静态方法,用户通过指定一个MetadataLookup
函数式接口过滤得到指定条件的方法对象。
3.4 getMappingForMethod(method, userType)
方法,创建标注@RequestMapping
注解方法的请求映射信息
该方法最早定义在AbstractHandlerMethodMapping
类中,是一个抽象方法
/**
* Provide the mapping for a handler method. A method for which no
* mapping can be provided is not a handler method.
* @param method the method to provide a mapping for
* @param handlerType the handler type, possibly a sub-type of the method's
* declaring class
* @return the mapping, or {@code null} if the method is not mapped
*/
@Nullable
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
具体的逻辑由子类RequestMappingHandlerMapping
实现
/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//为该方法创建请求映射信息。见3.4.1
RequestMappingInfo info = createRequestMappingInfo(method);
//方法上有@RequestMapping注解,才会去解析类上的@RequestMapping注解
if (info != null) {
//为该类创建请求映射信息。见3.4.2
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
//合并方法级别和类级别@RequestMapping注解对应的RequestMappingInfo,见4.3
info = typeInfo.combine(info);
}
//获取路径前缀,见3.4.4
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
//将路径前缀拼接到@RequestMapping注解指定路径的前面
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
总结一下方法流程:
- 首先创建方法级别的
@RequestMapping
注解对应的RequestMappingInfo
请求映射信息- 随后创建类级别的
@RequestMapping
注解对应的RequestMappingInfo
请求映射信息,并将这两个对象进行合并- 最后检查用户是否设置了路径前缀,设置路径前缀,则根据这个路径前缀再次构建一个
RequestMappingInfo
请求映射信息,并与上面的RequestMappingInfo
对象进行合并得到一个新的RequestMappingInfo
请求映射信息
3.4.1 解析方法级别的@RequestMapping
注解
/**
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* supplying the appropriate custom {@link RequestCondition} depending on whether
* the supplied {@code annotatedElement} is a class or method.
* @see #getCustomTypeCondition(Class)
* @see #getCustomMethodCondition(Method)
*/
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
//获取方法上标注的@RequestMapping注解信息
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
/**
* 此处element是一个Method,执行getCustomMethodCondition()方法,
* 方法直接返回null,没有实现
*/
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
//方法上标注了注解@RequestMapping注解,就解析注解创建请求映射信息,见3.4.3
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
该方法主要做一件事,获取方法上标注的
@RequestMapping
注解信息,然后把创建请求映射信息的过程委派给createRequestMappingInfo(requestMapping, condition)
方法完成。本章节和
3.4.2
章节中的方法是同一个方法。
3.4.2 解析类级别的@RequestMapping
注解
/**
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* supplying the appropriate custom {@link RequestCondition} depending on whether
* the supplied {@code annotatedElement} is a class or method.
* @see #getCustomTypeCondition(Class)
* @see #getCustomMethodCondition(Method)
*/
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
//获取类上标注的@RequestMapping注解信息
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
/**
* 此处element是一个Class,执行getCustomTypeCondition()方法,
* 方法直接返回null,没有实现
*/
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
//类上标注了注解@RequestMapping注解,就解析注解创建请求映射信息,见3.4.3
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
该方法主要做一件事,获取类上标注的
@RequestMapping
注解信息,然后把创建请求映射信息的过程委派给createRequestMappingInfo(requestMapping, condition)
方法完成。本章节和
3.4.1
章节中的方法是同一个方法。
3.4.3 根据@RequestMapping
注解创建请求映射信息
/**
* Create a {@link RequestMappingInfo} from the supplied
* {@link RequestMapping @RequestMapping} annotation, which is either
* a directly declared annotation, a meta-annotation, or the synthesized
* result of merging annotation attributes within an annotation hierarchy.
*/
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
//建造者模式,根据@RequestMapping注解属性值构建一个建造器,见6
RequestMappingInfo.Builder builder = RequestMappingInfo
//resolveEmbeddedValuesInPatterns()方法,解析占位符,见3.4.3.1
.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);
}
//构建一个RequestMappingInfo对象,6.2
return builder.options(this.config).build();
}
这里使用建造者模式将
RequestMappingInfo
对象复杂的创建过程封装起来。
3.4.3.1 解析占位符
/**
* Resolve placeholder values in the given array of patterns.
* @return a new array with updated patterns
*/
protected String[] resolveEmbeddedValuesInPatterns(String[] patterns) {
if (this.embeddedValueResolver == null) {
return patterns;
}
else {
String[] resolvedPatterns = new String[patterns.length];
for (int i = 0; i < patterns.length; i++) {
resolvedPatterns[i] = this.embeddedValueResolver.resolveStringValue(patterns[i]);
}
return resolvedPatterns;
}
}
映射路径作为一个整体去解析,也就是说占位符不能是映射路径的一部分。
/test/${xxx}
:这种不符合要求,不能解析占位符${xxx}
:这种符合要求,可以解析占位符
3.4.4 获取路径前缀
//这个集合默认是没有数据的,用户可使用对应的setter方法向里面添加路径前缀
private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
@Nullable
String getPathPrefix(Class<?> handlerType) {
//遍历
for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) {
//断言该类是否符合条件
if (entry.getValue().test(handlerType)) {
String prefix = entry.getKey();
//路径前缀先解析占位符
if (this.embeddedValueResolver != null) {
prefix = this.embeddedValueResolver.resolveStringValue(prefix);
}
return prefix;
}
}
return null;
}
用户可以配置多个
路径前缀->断言
的映射关系,那么在解析@RequestMapping
注解的时候,就会先断言这个类是否符合条件,符合条件,则解析路径前缀的占位符,然后拼接到@RequestMapping
注解指定路径的前面。
4 RequestMappingInfo
该类封装了用户定义的请求映射信息,实际上就是用来保存一个@RequestMapping
注解的解析结果
先来看一下它的类图
它实现了RequestCondition
接口,那么这个接口是干嘛的呢?该接口提供了三个方法,分别如下所示:
public interface RequestCondition<T> {
/**
* 这个方法用来合并两个RequestMappingInfo,
* 我们在解析@RequestMapping注解的时候,会分别解析方法上的@RequestMapping注解和
* 类上的@RequestMapping注解,生成两个RequestMappingInfo对象,此时就需要调用这个方法
* 将两个RequestMappingInfo对象的信息合并起来
*/
T combine(T other);
/**
* 检查RequestMappingInfo对象对应的处理器方法是否能够处理本次请求
* 返回null,表示不能处理
*/
@Nullable
T getMatchingCondition(HttpServletRequest request);
/**
* Compare this condition to another condition in the context of
* a specific request. This method assumes both instances have
* been obtained via {@link #getMatchingCondition(HttpServletRequest)}
* to ensure they have content relevant to current request only.
*/
int compareTo(T other, HttpServletRequest request);
}
这个接口提供了一个泛型约束,而
RequestMappingInfo
类的泛型约束就是它自己,如下所示:public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo>{...}
接下来我们看一下几个比较常用的方法
4.1 paths(String... paths)
方法,创建一个指定路径的Builder
对象
/**
* Create a new {@code RequestMappingInfo.Builder} with the given paths.
* @param paths the paths to use
* @since 4.2
*/
public static Builder paths(String... paths) {
return new DefaultBuilder(paths);
}
Builder
的类型为DefaultBuilder
,见6.1
4.2 getMatchingCondition(HttpServletRequest request)
方法,检查当前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());
}
其实这些条件类也都是
RequestCondition
接口的实现类,该方法会一个一个的去调用RequestMappingInfo
类持有的条件的getMatchingCondition(request)
方法, 判断是否匹配本次请求。只要有一个条件不匹配,就返回null
,否则使用这些条件的对应值重新构建一个RequestMappingInfo
对象。
4.3 combine(RequestMappingInfo other)
方法, 合并两个RequestMappingInfo
对象
/**
* Combine "this" request mapping info (i.e. the current instance) with another request mapping info instance.
* <p>Example: combine type- and method-level request mappings.
* @return a new request mapping info instance; never {@code null}
*/
@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
String name = combineNames(other);
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
return new RequestMappingInfo(name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
该方法会一个一个的去调用
RequestMappingInfo
类持有的条件的combine(other)
方法, 合并每一个条件。最后使用合并后条件的对应值重新构建一个RequestMappingInfo
对象。
5 BuilderConfiguration
该类是RequestMappingInfo
类的一个嵌套类(静态内部类),它只有默认的无参构造方法。
它代表所有请求映射的建造器配置,当扫描到@RequestMapping
注解方法后,解析生成RequestMappingInfo
请求映射信息的时候,需要将这个配置信息覆盖到生成RequestMappingInfo
对象中。
下面是一些被用到的方法的源码
5.1 setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper)
方法,配置UrlPathHelper
@Nullable
private UrlPathHelper urlPathHelper;
/**
* Set a custom UrlPathHelper to use for the PatternsRequestCondition.
* <p>By default this is not set.
* @since 4.2.8
*/
public void setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
}
urlPathHelper
字段对应的setter
方法
5.2 setPathMatcher(@Nullable PathMatcher pathMatcher)
方法, 配置PathMatcher
@Nullable
private PathMatcher pathMatcher;
/**
* Set a custom PathMatcher to use for the PatternsRequestCondition.
* <p>By default this is not set.
*/
public void setPathMatcher(@Nullable PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
}
pathMatcher
字段对应的setter
方法
5.3 setTrailingSlashMatch(boolean trailingSlashMatch)
方法, 设置是否与URL匹配,无论是否存在斜杠
private boolean trailingSlashMatch = true;
/**
* Set whether to apply trailing slash matching in PatternsRequestCondition.
* <p>By default this is set to 'true'.
*/
public void setTrailingSlashMatch(boolean trailingSlashMatch) {
this.trailingSlashMatch = trailingSlashMatch;
}
trailingSlashMatch
字段对应的setter
方法,默认开启总是与URL
匹配
5.4 setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager)
方法, 配置内容协商管理器
@Nullable
private ContentNegotiationManager contentNegotiationManager;
/**
* Set the ContentNegotiationManager to use for the ProducesRequestCondition.
* <p>By default this is not set.
*/
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
this.contentNegotiationManager = contentNegotiationManager;
}
contentNegotiationManager
字段对应的setter
方法,默认使用处理器映射器的内容协商管理器
6 DefaultBuilder
该类是RequestMappingInfo
类的一个嵌套类(静态内部类),它只提供一个有参构造方法。它代表了一个请求映射信息的建造器。
6.1 建造方法的源码
private static class DefaultBuilder implements Builder {
//映射路径
private String[] paths;
//请求方式
private RequestMethod[] methods = new RequestMethod[0];
//请求参数
private String[] params = new String[0];
//请求头
private String[] headers = new String[0];
private String[] consumes = new String[0];
private String[] produces = new String[0];
//内容类型
private boolean hasContentType;
//接收类型
private boolean hasAccept;
//映射名
@Nullable
private String mappingName;
@Nullable
private RequestCondition<?> customCondition;
//请求映射信息的建造器配置
private BuilderConfiguration options = new BuilderConfiguration();
public DefaultBuilder(String... paths) {
this.paths = paths;
}
@Override
public Builder paths(String... paths) {
this.paths = paths;
return this;
}
@Override
public DefaultBuilder methods(RequestMethod... methods) {
this.methods = methods;
return this;
}
@Override
public DefaultBuilder params(String... params) {
this.params = params;
return this;
}
@Override
public DefaultBuilder headers(String... headers) {
for (String header : headers) {
this.hasContentType = this.hasContentType ||
header.contains("Content-Type") || header.contains("content-type");
this.hasAccept = this.hasAccept ||
header.contains("Accept") || header.contains("accept");
}
this.headers = headers;
return this;
}
@Override
public DefaultBuilder consumes(String... consumes) {
this.consumes = consumes;
return this;
}
@Override
public DefaultBuilder produces(String... produces) {
this.produces = produces;
return this;
}
@Override
public DefaultBuilder mappingName(String name) {
this.mappingName = name;
return this;
}
@Override
public DefaultBuilder customCondition(RequestCondition<?> condition) {
this.customCondition = condition;
return this;
}
@Override
public Builder options(BuilderConfiguration options) {
this.options = options;
return this;
}
}
上面这些方法用来保存建造
RequestMappingInfo
对象的配置
6.2 build()
方法,创建RequestMappingInfo
请求映射信息
@Override
@SuppressWarnings("deprecation")
public RequestMappingInfo build() {
PatternsRequestCondition patternsCondition = ObjectUtils.isEmpty(this.paths) ? null :
new PatternsRequestCondition(
this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions());
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
return new RequestMappingInfo(this.mappingName, patternsCondition,
ObjectUtils.isEmpty(this.methods) ?
null : new RequestMethodsRequestCondition(this.methods),
ObjectUtils.isEmpty(this.params) ?
null : new ParamsRequestCondition(this.params),
ObjectUtils.isEmpty(this.headers) ?
null : new HeadersRequestCondition(this.headers),
ObjectUtils.isEmpty(this.consumes) && !this.hasContentType ?
null : new ConsumesRequestCondition(this.consumes, this.headers),
ObjectUtils.isEmpty(this.produces) && !this.hasAccept ?
null : new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition);
}
创建
RequestMappingInfo
对象是一个复杂的过程,它将@RequestMapping
注解属性值封装为一个个的RequestCondition
。
RequestCondition
是请求条件,HandlerMapping
获取处理请求的处理器方法时,必须满足所有的RequestCondition
,当前RequestMappingInfo
对应的标注@RequestMapping
注解的方法才会被认为可处理本次请求。
7 MappingRegistry
这是AbstractHandlerMethodMapping
的一个内部类。它是一个注册表,用来维护所有标注@RequestMapping
注解方法的映射关系。
7.1 register(T mapping, Object handler, Method method)
方法, 注册标注@RequestMapping
注解方法的所有映射
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//RequestMappingInfo到处理器方法之间的映射关系
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
//请求路径到RequestMappingInfo之间的映射关系
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
//处理器方法和跨域配置之间的映射关系
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
//MappingRegistration保存着标注@RequestMapping注解方法的所有映射关系,见8
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
/**
* @param mapping 实际上就是RequestMappingInfo
* @param handler 处理器方法所在类的beanName
* @param method 标注@RequestMapping注解的方法
*/
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] parameterTypes = method.getParameterTypes();
if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
//加锁,注册过程涉及到多个集合,必须加锁来保证线程安全
this.readWriteLock.writeLock().lock();
try {
//初见处理器方法对象,见7.4.1
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//校验映射关系,判断注册表中是否已经存在这个映射关系,已经存在就不能向里面注册了,见7.2
validateMethodMapping(handlerMethod, mapping);
//注册表维护的第1种映射关系
this.mappingLookup.put(mapping, handlerMethod);
//获取该方法对应的请求路径
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//注册表维护的第2种映射关系
this.urlLookup.add(url, mapping);
}
String name = null;
//获取映射关系的命名策略,见7.4.2
if (getNamingStrategy() != null) {
//生成名字
name = getNamingStrategy().getName(handlerMethod, mapping);
//将名字和处理器方法添加到注册表中
addMappingName(name, handlerMethod);
}
//跨域配置,会去解析方法和类上的@CrossOrigin注解,生成对应的跨域配置,见7.4.3
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
/**
* 注册表维护的第3种映射关系
* 注册跨域配置和处理器方法之间的映射关系
*/
this.corsLookup.put(handlerMethod, corsConfig);
}
/**
* 注册表维护的第4种映射关系
* MappingRegistration保存着标注@RequestMapping注解方法的所有映射关系,见8
*/
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
//释放锁
this.readWriteLock.writeLock().unlock();
}
}
注册表的
register(T mapping, Object handler, Method method)
方法, 一共会注册4
种映射关系
RequestMappingInfo
->HandlerMethod
url
->RequestMappingInfo
HandlerMethod
->CorsConfiguration
RequestMappingInfo
->MappingRegistration
7.2 validateMethodMapping(HandlerMethod handlerMethod, T mapping)
方法, 判断是否已经存在到该标注@RequestMapping
注解方法的映射
private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) {
// Assert that the supplied mapping is unique.
HandlerMethod existingHandlerMethod = this.mappingLookup.get(mapping);
if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException(
"Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" +
handlerMethod + "\nto " + mapping + ": There is already '" +
existingHandlerMethod.getBean() + "' bean method\n" + existingHandlerMethod + " mapped.");
}
}
校验映射的唯一性
7.3 addMappingName(String name, HandlerMethod handlerMethod)
方法, 缓存映射名和处理器方法
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private void addMappingName(String name, HandlerMethod handlerMethod) {
//先从缓存中取
List<HandlerMethod> oldList = this.nameLookup.get(name);
if (oldList == null) {
oldList = Collections.emptyList();
}
//处理器方法和缓存中的相同,直接返回
for (HandlerMethod current : oldList) {
if (handlerMethod.equals(current)) {
return;
}
}
//否则重新创建一个list集合,将原来的处理器方法和现在的这个都添加进去
List<HandlerMethod> newList = new ArrayList<>(oldList.size() + 1);
newList.addAll(oldList);
newList.add(handlerMethod);
this.nameLookup.put(name, newList);
}
这个方法用来注册映射名,将命名策略生成的名字和处理器方法保存到注册表中
7.4 外部类AbstractHandlerMethodMapping
7.1
章节的register(T mapping, Object handler, Method method)
方法会调用很多外部类的方法,接下来我们就它外部类这些方法的源码
7.4.1 createHandlerMethod(Object handler, Method method)
方法, 创建一个处理器方法对象
/**
* Create the HandlerMethod instance.
* @param handler either a bean name or an actual handler instance
* @param method the target method
* @return the created HandlerMethod
*/
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
//传入工厂对象,在构造方法中通过getBean()方法得到handler对应的对象
if (handler instanceof String) {
return new HandlerMethod((String) handler,
obtainApplicationContext().getAutowireCapableBeanFactory(), method);
}
return new HandlerMethod(handler, method);
}
这个方法将标注
@RequestMapping
注解的方法和方法所在类对象封装为一个处理器方法对象,方便处理器方法对象直接反射调用这个方法。
7.4.2 getNamingStrategy()
方法,获取处理器方法映射命名策略
@Nullable
private HandlerMethodMappingNamingStrategy<T> namingStrategy;
/**
* Return the configured naming strategy or {@code null}.
*/
@Nullable
public HandlerMethodMappingNamingStrategy<T> getNamingStrategy() {
return this.namingStrategy;
}
在
RequestMappingHandlerMapping
构造方法上初始化的命名策略类型为RequestMappingInfoHandlerMethodMappingNamingStrategy
7.4.3 initCorsConfiguration(Object handler, Method method, T mapping)
方法, 解析方法和类上的@CrossOrigin
注解,生成对应的跨域配置
/**
* Extract and return the CORS configuration for the mapping.
*/
@Nullable
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, T mapping) {
return null;
}
抽象方法,具体的逻辑由子类
RequestMappingHandlerMapping
实现
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
//创建处理器方法对象,见7.4.1
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//获取处理器方法所在类的类型
Class<?> beanType = handlerMethod.getBeanType();
//获取类上的@CrossOrigin注解信息
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
//获取方法上的@CrossOrigin注解信息
CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
//类上和方法上都没有@CrossOrigin注解信息,就不创建跨域配置,直接返回null
if (typeAnnotation == null && methodAnnotation == null) {
return null;
}
//创建一个跨域配置
CorsConfiguration config = new CorsConfiguration();
//将类上的@CrossOrigin注解指定的跨域配置保存到跨域配置对象中,见7.4.3.1
updateCorsConfig(config, typeAnnotation);
//将方法上的@CrossOrigin注解指定的跨域配置保存到跨域配置对象中,见7.4.3.1
updateCorsConfig(config, methodAnnotation);
//用户未在@CrossOrigin注解上指定允许的请求方式
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
//将用户在@RequestMapping注解上配置的请求方式注册到跨域配置中
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
//设置允许的请求方式
config.addAllowedMethod(allowedMethod.name());
}
}
//应用默认值,不会覆盖@CrossOrigin注解指定的配置
return config.applyPermitDefaultValues();
}
该方法用来解析方法和类上的
@CrossOrigin
注解,并生成对应的跨域配置
7.4.3.1 将@CrossOrigin
注解指定的跨域配置保存到跨域配置对象中
private void updateCorsConfig(CorsConfiguration config, @Nullable CrossOrigin annotation) {
if (annotation == null) {
return;
}
for (String origin : annotation.origins()) {
config.addAllowedOrigin(resolveCorsAnnotationValue(origin));
}
//允许访问的请求方式
for (RequestMethod method : annotation.methods()) {
config.addAllowedMethod(method.name());
}
//允许请求的请求头列表
for (String header : annotation.allowedHeaders()) {
config.addAllowedHeader(resolveCorsAnnotationValue(header));
}
for (String header : annotation.exposedHeaders()) {
config.addExposedHeader(resolveCorsAnnotationValue(header));
}
String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials());
if ("true".equalsIgnoreCase(allowCredentials)) {
config.setAllowCredentials(true);
}
else if ("false".equalsIgnoreCase(allowCredentials)) {
config.setAllowCredentials(false);
}
else if (!allowCredentials.isEmpty()) {
throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " +
"or an empty string (\"\"): current value is [" + allowCredentials + "]");
}
if (annotation.maxAge() >= 0 && config.getMaxAge() == null) {
config.setMaxAge(annotation.maxAge());
}
}
解析
@CrossOrigin
注解的属性,并将属性值保存到跨域配置对象中
8 MappingRegistration
和MappingRegistry
类差不多,它是AbstractHandlerMethodMapping
的一个嵌套类。它用来维护某个标注@RequestMapping
注解方法的所有映射关系。
private static class MappingRegistration<T> {
//RequestMappingInfo`
private final T mapping;
//处理器方法
private final HandlerMethod handlerMethod;
//对应的url路径
private final List<String> directUrls;
//映射名
@Nullable
private final String mappingName;
public MappingRegistration(T mapping, HandlerMethod handlerMethod,
@Nullable List<String> directUrls, @Nullable String mappingName) {
Assert.notNull(mapping, "Mapping must not be null");
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
this.mapping = mapping;
this.handlerMethod = handlerMethod;
this.directUrls = (directUrls != null ? directUrls : Collections.emptyList());
this.mappingName = mappingName;
}
public T getMapping() {
return this.mapping;
}
public HandlerMethod getHandlerMethod() {
return this.handlerMethod;
}
public List<String> getDirectUrls() {
return this.directUrls;
}
@Nullable
public String getMappingName() {
return this.mappingName;
}
}
它维护某个处理器方法的所有映射关系