1. 背景
在工作中大量使用到了@RestController 和 @RequestMapping等注解,通过注解定义好接口url,然后请求这个地址就能够调用我们注解下的具体方法,那么springmvc是怎么将url转换为调用具体的bean的方法的呢?
2. 基础概念
- DispatcherServlet#doDispatch springmvc 处理请求的核心类,包括找到对应方法,调用对应的bean的方法后,进行响应处理。
- RequestMappingInfo springmvc在启动的时候会将@RequestMapping注解的信息放到RequestMappingInfo中,例如url信息会放到patternsCondition这个属性中。
- RequestMappingInfoHandlerMapping doDispatch方法会遍历HandlerMapping的所有子类,然后调用各自的getHandlerInternal方法进行寻找匹配的bean的方法。
- AbstractHandlerMethodMapping RequestMappingInfoHandlerMapping的模板方法,调用lookupHandlerMethod来寻找对应bean和方法。
3. 流程
-
DispatcherServlet#doDispatch
调用getHandler获取对应的handler
java
mappedHandler = getHandler(processedRequest);
-
DispatcherServlet#getHandler
遍历handlerMappings,调用list里的各个实现类的getHandler方法;
java
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//初始化的时候会将HandlerMapping 的所有bean放到handlerMappings中
if (this.handlerMappings != null) {
//遍历
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
//找不到继续调用下一个mapping的getHandler方法,责任链模式
if (handler != null) {
return handler;
}
}
}
return null;
}
具体的handlerMapping如下:
-
RequestMappingInfoHandlerMapping.getHandlerInternal
目前项目中使用的都是@RequestMapping注解,所以我们重点关注RequestMappingInfoHandlerMapping就好,可以看到最终又回去调用父类的getHandlerInternal方法了
java
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
//调用父类的方法
return super.getHandlerInternal(request);
}
finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
- AbstractHandlerMethodMapping#getHandlerInternal
这里可以看到父类的这个方法,没啥核心代码,主要是调用lookupHandlerMethod
java
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//提取出url,例如我现在请求的http://127.0.0.1:8080/pre/test,最终拿到的lookupPath为/pre/test
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
//根据url和请求获取HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
-
AbstractHandlerMethodMapping#lookupHandlerMethod
这一段就是比较核心的代码了,这里会根据url先进行查找,然后再根据相关条件进行筛选
java
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//根据url获取到所有的RequestMappingInfo
//底层维护了一个pathLookup,维护了url和List<RequestMappingInfo> 的映射
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
//通过条件进行筛选,并把结果放到matches里
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
//兜底遍历一下所有接口
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
//排序一下
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
for (Match match : matches) {
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
}
//不允许存在有两个权重相等的情况
else {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
else {
//返回对应的bean和方法信息
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
- AbstractHandlerMethodMapping#addMatchingMappings
java
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
//条件筛选
T match = getMatchingMapping(mapping, request);
if (match != null) {
//mappingRegistry将RequestMappingInfo作为key,bean和方法信息作为value,维护了一个map
matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
}
}
}
4. 总结
在实际项目过程中,我们使用到了很多类似这种注解,但是都只是停留在了用的层面,作为一个搞技术的,我们其实需要有一些技术热情,可能在做项目的时候没时间,但是空闲时间来看看原理,也是很有收获的。