springmvc源码面试题Spring源码之请求路径匹配路由⽅式
⽬录
请求路径匹配路由
⼊⼝
进⼊上⾯⽅法
SpringMVC 将请求到匹配的处理
初始化映射关系
从映射关系中寻匹配⽅法
请求路径匹配路由
在spring中,当⼀个请求过来的时候会做路径匹配,下⾯我们就从源码层⾯分析⼀下路径匹配。
⽰例:
@RequestMapping(value = "/user/{aid}/online/**", method = RequestMethod.GET)
我们⼀起看看这个⽅法是如何寻的,和⼀些相应的⼯具类
⼊⼝
我的项⽬使⽤的是⾃动配置的RequestMappingHandlerMapping类,在getHandlerInternal()⽅法中:
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
上⾯这⾏是根据你的请求path和request去查合适的method了。在项⽬启动的时候,Spring就把路径和对应的⽅法加载到了内存中。
进⼊上⾯⽅法
List<T> directPathMatches = MappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through
addMatchingMappings(Mappings().keySet(), matches, request);
}
可以看到如果根据lookupPath直接匹配上了,⾛第⼀个⽅法,如果没有,则需要根据规则匹配,⾛第⼆个⽅法。Mappings().keySer()这个⽅法获取的类型为RequestMappingInfo类型,后⾯进⼊了RequestMappingInfo 的getMatchingCondition()⽅法:
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = MatchingCondition(request);
ParamsRequestCondition params = MatchingCondition(request);
HeadersRequestCondition headers = MatchingCondition(request);
ConsumesRequestCondition consumes = MatchingCondition(request);
ProducesRequestCondition produces = MatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
return null;
}
PatternsRequestCondition patterns = MatchingCondition(request);
if (patterns == null) {
return null;
}
可以看到代码⾥⾯会查看各种条件是否匹配,包括,请求⽅法methods,参数params,请求头headers,还出⼊参类型等相关的consumers,produces等,最后⼀⾏就是我们要的路径匹配MatchingCondition(request)。
这个⽅法会⾛到PatternRequestCondition的getMatchingPattern⽅法,然后调⽤如下⽅法,获取pattern:
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
上⾯这个pathMatcher的类型就是AntPathMatcher类,就是通过调⽤AntPathMatcher类的match⽅法,查看是否匹配,然后返回pattern。
SpringMVC 将请求到匹配的处理
在SpringMVC的模式下,浏览器的⼀个请求是如何映射到指定的controller的呢?
初始化映射关系
在web服务器启动时,Spring容器中会保存⼀个map的数据结构,⾥边记录这controller和url请求中的对应关系。那么这个map 中的数据是如何来的呢?
⾸先来看AbstractHandlerMethodMapping的initHandlerMethods⽅法(⾄于为什么直接到这个⽅法,我也是⽹上搜索的,之前的调⽤链没去纠结)
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
//获取Spring容器装配的所有bean的名称
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
//遍历
for (String beanName : beanNames) {
//判断该bean是否有@controller或者@RequestMapping注解
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
//如果有上述注解,则需要保存对应关系
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
//获取传过来handler的类信息
Class<?> handlerType =
(handler instanceof String ? getApplicationContext().getType((String) handler) : Class());
// Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
//初始化⼀个保存映射信息的map
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = UserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
//获取该类⾥所有⽅法的映射信息 T为RequestMappingInfo
//mapping值的形式为{[/test/test1],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
//将信息加⼊map
mappings.put(method, mapping);
return true;
}
else {
return false;
}
}
});
for (Method method : methods) {
/
/注册HandlerMethod,在⾥边进⾏⼀些重复验证等
registerHandlerMethod(handler, method, (method));
}
}
上述⽅法中调⽤了⼀个⽐较重要的⽅法,getMappingForMethod,通过这个⽅法⽣成后续我们⼀直会⽤到的⼀个RequestMappingInfo对象。具体⽅法如下:
@Override
//该⽅法接收两个参数,⼀个是具体⽅法,⼀个是⽅法所在的类
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
//到⽅法的@RequestMapping注解
RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation != null) {
//这个⽅法返回null
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
//创建RequestMappingInfo对象
info = createRequestMappingInfo(methodAnnotation, methodCondition);
//到类的@RequestMapping注解
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
//该⽅法也返回⼀个null
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
//如果类和⽅法都有@RequestMapping注解,则进⾏combine操作
info = createRequestMappingInfo(typeAnnotation, typeCondition)bine(info);
}
}
return info;
}
那么上述⽅法中调⽤的createRequestMappingInfo⽅法有事如何真正的创建出⼀个RequestMappingInfo对象的呢?protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
//拿到@RequestMapping注解上的value值
String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
//创建⼀个RequestMappingInfo,参数为⼀堆condition,出了PatternsRequestCondition,其余全部使⽤@RequestMapping注解上的值
return new RequestMappingInfo(
annotation.name(),
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
new hod()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new sumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers(), tNegotiationManager),
customCondition);
}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//handler此处为带有@controller或者@RequestMapping的类的名称
//初始化⼀个HandlerMethod,包含⼀些类的名称和⽅法等信息
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
HandlerMethod oldHandlerMethod = (mapping);
//判断是否有handlerMethods是否有重复数据,有则抛异常,没有则将其加⼊handlerMethods map中
if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + Bean() +
"' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
}
this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
}
//将没有*号和问好的pattern加⼊到urlMap中
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
/
/维护⼀个nameMap,key为名字,格式为congroller类⼤写字母+#+⽅法名
//⽐如TestBank类的getBank⽅法,可以为TB#getBank
if (this.namingStrategy != null) {
String name = Name(newHandlerMethod, mapping);
updateNameMap(name, newHandlerMethod);
}
}
由上述registerHandlerMethod⽅法我们可以看出,该⽅法共维护了三个map分别是:
handlermethods: key为RequestMappingInfo value为HandlerMethod
urlMap: key为没有*和?的pattern(⽐如/test/test1)value为RequestMappingInfo
nameMap: key为名字,格式为congroller类⼤写字母+#+⽅法名,⽐如TestBank类的getBank⽅法,key为TB#getBank
上述三个map在后续匹配浏览器请求⽤哪个⽅法来处理时会重点⽤到。
从映射关系中寻匹配⽅法
那么DispatcherServlet是如何处理⼀个请求的呢?
我们从DispatcherServlet的doService⽅法来看起,该⽅法中,最终会调⽤到AbstractHandlerMethodMapping类的lookupHandlerMethod⽅法来确定这个请求应该由哪个⽅法处理,代码如下:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
//从urlMap中寻能匹配的处理⽅法
List<T> directPathMatches = (lookupPath);
//如果从urlMap中到匹配的处理⽅法,则调⽤addMatchingMappings⽅法,将匹配的⽅法放⼊matches集合
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
//如果urlMap中没有到直接匹配的⽅法
if (matches.isEmpty()) {
// No choice but to go through
addMatchingMappings(this.handlerMethods.keySet(), matches, request);
}
if (!matches.isEmpty()) {
//如果到了匹配的⽅法,先获取⼀个⽐较器
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
/
/将匹配到的⽅法按照⽐较器排序
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
}
//如果成功匹配的⽅法只有⼀个,拿这个⽅法返回。如果匹配到多个⽅法,取最匹配的前两个进⾏⽐较。
//如果⽐较结果为0,则抛出没有到唯⼀合适处理⽅法的异常
Match bestMatch = (0);
if (matches.size() > 1) {
Match secondBestMatch = (1);
if (comparatorpare(bestMatch, secondBestMatch) == 0) {
Method m1 = Method();
Method m2 = Method();
throw new IllegalStateException(
"Ambiguous handler methods mapped for HTTP path '" + RequestURL() + "': {" +
m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
/
/没有到匹配的则返回null
return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
}
}
从上述代码可以看出,程序会先从this.urlMap中寻是否有匹配的⽅法,那么这个urlMap中的数据是从什么时候加载的呢?我们⽹上翻看registerHandlerMethod⽅法,在web服务器启动时,该⽅法初始化了urlMap中的数据。
通过上述分析,⼤致可以了解到Spring容器是如何维护url和⽅法之间的映射关系,以及当收到请求时⼜是如何将请求匹配到正确的⽅法的。
⾄于没有分析到的当类和⽅法都有@RequestMapping注解时触发的combine操作究竟做了什么,当到多个匹配⽅法是⼜是如何通过⽐较器进⾏排序的,我们下次再分析。
以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论