springmvc 如何通过请求url找到具体的controller方法
1.前言
第一次写博客,有点小紧张,本博客主要用来记录平时在工作中遇到的各种问题和解决办法,同时也记录自己学习的点点滴滴,内容如果有瑕疵,请在评论区告知,本人会尽快修改,谢谢。
2.实例代码
先上实例代码:
@RestController
public class DomeController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
直接在浏览器输入http://127.0.0.1:8080/hello 就可得到我们预期的界面。
示例程序虽然很简单,但不禁有疑问,Springmvc到底使用的哪种魔法,能根据简单的 "/hello"请求就能确定我们的具体的处理器,同时也能定位调用哪个具体的方法?带着这个疑问,我们继续往下看。
3.根据url获取具体的处理方法
Springmvc使用中央控制器DispatcherServlet进行请求的处理,当用户在浏览器发送请求时,会被DispatcherServlet拦截,会调用父类的doGet()或者doPost()方法进行请求的处理,但这两个方法经过层层跳转,都会到达DispatcherServlet的doDispatch()方法,所有我们的分析中doDispatch()方法开始。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//保存原始请求
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
try {
ModelAndView mv = null;
try {
// Determine handler for the current request.
// 标记1.0 根据request寻找handle
mappedHandler = getHandler(processedRequest);
//根据handle寻找HandlerAdapter
//所有这里使用了适配器模式,对调用方法的接收参数和响应参数进行处理
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//应用preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//调用真正的handle
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//应用视图解析器
applyDefaultViewName(processedRequest, mv);
//应用postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
本次分析的主要代码就在标记1.0处,getHandler()会根据请求request的url来获取一个处理器执行链HandlerExecutionChain,HandlerExecutionChain内部包含HandlerMethod对象和一系列的拦截器,要知晓获取HandlerExecutionChain的具体过程,我们必然要进入DispatcherServlet的getHandler()内部看看。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
//request 包括请求路径
//handlerMappings 包括url 和controller&method的对应关系
for (HandlerMapping hm : this.handlerMappings) {
//获取处理器执行链
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
可以看到getHandler()方法通过遍历this.handlerMappings对象,依次调用HandlerMapping 的getHandler()方法尝试获取处理器执行链,如果遍历完HandlerMapping 还没有获取到处理器链则返回null。
但HandlerMapping 对象具体有什么用?如何通过HandlerMapping 的getHandler()方法获取处理器执行链?
Springmvc默认会为我们加载5个HandlerMapping ,但项目中Controller一般会和@RequestMapping注解代码使用,这里直接进入AbstractHandlerMapping类所在的getHandler()方法内部查看。
HandlerMapping接口的抽象类AbstractHandlerMapping类的getHandler()方法源码
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//标记2. 获取处理器
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//用HandlerExecutionChain 封装handler
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
return executionChain;
}
这里先说明一下HandlerMethod对象,这个对象主要封装@requestMapping标记处理器方法,也就是我们具体的请求方法。
逻辑很清晰,getHandlerInternal()方法获取一个HandlerMethod,使用getHandlerExecutionChain()方法对请求和HandlerMethod进行封装,返回处理器执行链,这里我们只简单分析获取处理器的函数getHandlerInternal()。
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//获取处理url
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
//获取处理方法 controller&method
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
//获取具体的处理器方法,handlerMethod包含一个处理器
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
首先获取request请求的url,然后使用url去获取HandlerMethod
lookupHandlerMethod()方法是HandlerMethod的关键
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//根据请求url获取候选处理方法 可能存在多个处理方法,应该有get/ post /put ... 从urlLookup获取
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
//获取准确的处理方法 将获取到的处理器方法和处理器包装起来放入matches中
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
//对处理器进行排序
matches.sort(comparator);
//获取第一个处理器&method
Match bestMatch = matches.get(0);
bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
可以通过这里发现,springmvc在底层缓存了url和HandlerMethod之间的对应关系,该映射关系被mappingRegistry所保存,当有请求来时,直接通过url获取HandlerMethod对象。
那url和HandlerMethod之间的映射关系是何时建立的喃?springmvc是如何解析@requestMapping注解的,等候下次分析吧。