首先在请求过来时,会先进入DispatcherServlet进行请求分发,执行DispatcherServlet类中的doDispatch()方法。
-------------- spring MVC找Controller流程 ----------------:
1.扫描整个项目(spring已经做了)定义一个map集合
2.拿到所有加了@Controller注解的类
3.遍历类中所有的方法对象(把方法作为对象)
4.判断方法是否加了@RequestMapping注解
5.把@RequestMapping注解的value(/getEmp/byId),作为map的key给put进去,把方法对象作为value放进map的value中
6.根据用户发送的请求,拿到uri
url:http://localhost:8081/index/do
uri:/index/do
7.使用请求的uri作为map的key,去map里边,查看是否由对应的返回值
2.1请求过来时,会先到达DispatcherServlet类中,进行请求分发,看一下DispatcherServlet源码
主要功能:检查请求是否含有文件,调用getHandler()方法,使用handlerMappings映射器来处理业务,并没有直接去找controller,扩展性很强
public class DispatcherServlet extends FrameworkServlet {
.......
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//*****先检查请求是否带有文件
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//*******调用gethandler方法,框架就是框架,getHandler并没由直接去找controller,
//*******而使用handlerMappings映射器来处理业务,可扩展性很强!
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
........
}
2.2 进入getHandler()源码
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//******springmvc框架中新建了一个类 HandlerMapping ,以后去找controller的任务就交给他,
//******有什么需要修改的地方只需要动HandlerMapping 就可以了,代码解耦
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//******请求mapping.getHandler方法,在方法里拿到uri
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
如上图:spring mvc 提供了两个HandlerMapping 映射器去找Controller,为什么定义两个映射器呢?
首先要知道Controller的定义方式 有 两种类型和三种实现
两种类型: BeanName类型 和 @Controller类型
三种实现: 实现HttpRequestHandler、实现Controller、加@Controller注解
实例:
1.@Controller类型
@Controller
public class MenuController {}
2.BeanName类型
实现controller接口:
//------实现controller接口,并加@Component注解
@Component(/getEmp/byId) //括号内为拦截路径
public class MenuController implements Controller{}
实现HttpRequestHandler接口:
//------实现HttpRequestHandler接口,并加@Component注解
@Component(/getEmp/byId) //括号内为拦截路径
public class MenuController implements HttpRequestHandler{}
以上两种方式都可以定义controller,HandlerMapping 映射器去找controller时也要根据不同情况去找!
1.RequestMappingHandlerMapping:找所有以@Controller注解标注的对象
2.BeanNameUrlHandlerMapping:找所有实现Controller、HttpRequestHandler接口的对象,称为BeanName类型
如果项目中控制层都是用@Controller标注的,则BeanNameUrlHandlerMapping映射器为空!!
进入mapping.getHandler(request)源码
2.3 进入getHandlerInternal源码
根据用户发送的请求,拿到uri
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//*********getUrlPathHelper()获取uri路径 uri为/login
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
//******接下来执行lookupHandlerMethod()方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
根据用户发送的请求,拿到uri ,uri为/login,接下来执行lookupHandlerMethod()方法
2.4 进入lookupHandlerMethod()方法源码,
getMappingsByUrl()方法,传入了uri:/login
2.5 进入getMappingsByUrl()方法源码,
urlLookup:为最上边提到的spring扫描的map对象
urlPath:请求的uri
意义:根据uri去找对应的方法
@Nullable
public List getMappingsByUrl(String urlPath) {
//
return this.urlLookup.get(urlPath);
}
上边通过DispatchServlet中的doDispatch()方法中的getHandler()方法找到了Controller,接下来要调用getHandlerAdapter方法获取对应的HandlerAdapter
进入getHandlerAdapter()源码
适配器找到之后,使用适配器调用对应的方法
通过debug,可知进入的第一个handle方法
再进入invokeHandlerMethod中:
进入invokeForRequest中
调用完成结束!
注意:
在获取具体参数中的getMethodArgumentValues方法中,需要判断参数类型,(比如是不是string、integer之类的),spring mvc没有用普通的if判断,而是把封装了许多类,每一个类对应一个参数类型,针对不同的参数类型去不同的类中去处理,与业务代码解耦
在上边获取具体参数时,其实经过了spring mvc的参数处理器处理后才返回的
我们在实际项目可以使用参数处理器吗?当然可以
自定义参数处理器使用场景
1.原始版:当前端传来一个参数username,在Controller层需要获取这个username的详细信息,需要从redis或者mysql中根据username查询到这个人的详情。如果很多Controller层都需要获取详细信息的话,每一个Controller中都需要写一段查询代码。
2.使用mvc参数处理器:
2.1:自定义一个注解@UserParam,加在需要获取详细信息的Controller的参数之前。代表这个参数需要使用参数处理器处理
2.2:自定义类ArgumentResovel实现HandlerMethodArgumentResolver参数处理器类
2.3:判断你的参数是否需要当前参数处理器来处理(依据:是否含有之定义注解@UserParam)
2.3:如果supportsParameter返回true, resolveArgument执行处理逻辑
2.4:把自定义参数处理器配置进去