1.SpringMVC的基本请求流程
SpringMVC如果大家都用过的话,可能会有熟悉这个叫做DispatcherServlet
的类,那这个类是干嘛的呢?
其实就是所有请求的入口。所以先看里面的每个请求必定会经过的方法。然后引出我们这次要将的HandlerMapping以及HandlerMapping的作用
下面是doDispatch
的大致的调用流程,我这里只留下了关键的点
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
Exception dispatchException = null;
// 1.获取一个HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2.获取一个HandlerAdapter来处理这个请求
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3.处理请求的前置调用
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4.真正调用处理方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 5.后置请求的处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 6.处理返回的结果
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
}
看上面的代码就能知道大致的流程
1. 获取一个HandlerExecutionChain
2. 获取一个HandlerAdapter来处理这个请求
3. 处理请求的前置调用
4. 真正调用处理方法
5. 后置请求的处理
6. 处理返回的结果
2.HandlerExecutionChain的作用
如果去看源码,就会发现绕来绕去就是为了获取这个HandlerExecutionChain,那HandlerExecutionChain是个啥,我为啥要获取到它,他能干嘛,为什么还没有讲到HandlerMapping呢?别急先讲一下HandlerExecutionChain
首先先看下这个类的关键成员变量,以及关键的函数
public class HandlerExecutionChain {
// 这个对象一般情况下就是一个HandleMethod对象
private final Object handler;
// 拦截器,可以对一个请求进行前置,后置拦截
private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
// 拦截器的下标
private int interceptorIndex = -1;
// 前置拦截器,用于拦截请求
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
// 剩下的还有后置拦截器等等
}
所以上面的代码我就就能看到其实HandlerExecutionChain,就是HandleMethod
+ List<HandlerInterceptor>
的结合体,用来对HandleMethod
处理的时候进行拦截的,这里又引出了一个HandleMethod
, 这个HandleMethod
又是啥呢,这里直接说明,他其实就是这次请求对应的Method(处理方法)
。
所以我们就知道了只要是找到了这个HandlerExecutionChain,其实就相当于找到了这个请求是对应哪个Controller的哪个方法,以及哪些拦截器,有了这些我们后续是不是就可以根据这些信息就能进一步解析参数,调用Method
的了呢。
3.HandlerExecutionChain和HandlerMapping的联系
根据上面的流程,我们能看到第一步就是获取HandlerExecutionChain,那看一下获取方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历循环HandlerMapping来获取HandlerExecutionChain
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
可以看到这里HandlerMapping就是用于获取HandlerExecutionChain
,那我们看一下这个接口有啥
public interface HandlerMapping {
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
其实这个接口核心方法就这一个,那我就需要看下哪些地方实现了这个接口
引用网上的一张图
4.HandlerExecutionChain对象的生成
左半部分黄色框话用的比较少,右半部分红色的是我们常用的根据@Controller
,@RequestMapping
来处理请求的方式,那就对这个分析
可以看到继承关系:
HandlerMapping
-> AbstractHandlerMapping
---> AbstractHandlerMethodMapping
------> RequestMappingInfoHandlerMapping
---------> RequsetMappingHandlerMapping
既然是要看怎么获取的HandlerExecutionChain
,那根据调用关系,先到AbstractHandlerMapping
的getHandler
,这个也是HandlerMapping
需要实现的接口。
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 留给子类去实现
Object handler = getHandlerInternal(request);
// 组合成HandlerExecutionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
return executionChain;
}
这个getHandlerInternal
是个抽象方法,那就是会由子类去实现,那继续看子类,会发现最终是由AbstractHandlerMethodMapping
去实现的,后面的RequestMappingInfoHandlerMapping
虽然也实现了这个函数,但是最终也是调用AbstractHandlerMethodMapping
的getHandlerInternal
那我们看下面的AbstractHandlerMethodMapping
的getHandlerInternal
的实现
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 1.找请求的路径,比如请求的路径/test/getInfo,那这里就是这个/test/getInfo
String lookupPath = initLookupPath(request);
// 2.加锁
this.mappingRegistry.acquireReadLock();
try {
// 3.根据请求的路径找HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
1.可以看到主要是根据请求的路径去找对应的HandlerMethod
(处理方法),这里就和前面HandlerExecutionChain的作用里面的HandlerMethod
是对应起来了的,这个HandlerMethod
会被拿来存到HandlerExecutionChain
中,然后被调用。
2.那我们继续看是如何根据路径找对应的HandlerMethod
,看lookupHandlerMethod
方法。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 1.获取匹配上的路径的信息(RequestMappingInfo)
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
// 2.将匹配上的路径信息和对应HandleMethod添加到matches里面
addMatchingMappings(directPathMatches, matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
// 大于两个可以匹配的路径的时候,进行排序
}
// 3.返回HandleMethod
return bestMatch.getHandlerMethod();
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
这里将关键步骤单独取出来
1.获取匹配上的路径信息
2.将匹配上的路径信息和对应HandleMethod添加到matches里面
3.返回HandleMethod
2.1 this.mappingRegistry.getMappingsByDirectPath(lookupPath);
那这里一个最为关键的就是mappingRegistry
这个对象,这个里面是干啥的呢,我先解释下基本的,后面我们再来分析。
这类看名字就知道是映射的注册,是什么映射的注册呢?其实是这个类会保存我们定义的@Controller
里面的处理方法的信息,比如我写了下面的代码:
@RestController
@RequestMapping(value = "/test")
public class TestController {
@ResponseBody
@PostMapping(value = "/map")
public ResponseDto map(@RequestBody RequestVo vo) {
ResponseDto dto = new ResponseDto();
dto.setName("jiang");
return dto;
}
}
那这个mapping就会将这个Controller的信息提取出来,包括他匹配的路径/test/map
,以及这个路径对应的TestController.map
方法。
这些信息提取出来后,就注册到这个mappingRegistery
,我们可以简单的把它当作一个Map,Key是/test/map
, Value是TestController.map
方法。 后面我们会分析这个是如何注册的,这里先跳过。
这里就是将/test/map
对应的注册的信息取出来,这里是最简单的根据路径匹配,只要匹配上就会返回
2.2 addMatchingMappings(directPathMatches, matches, request)
这个方法也不算复杂,就是对上面2.1的再次进行匹配筛选,其中包含:
1.匹配POST和GET方法是否一致
2.匹配要求的请求头是否一致
3...
根据上面的条件筛选完后,就会组装得到一个新的请求路径、请求头、请求方式等都完成符合这次请求的RequestMappingInfo信息
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
// 判断是否请求的POST或者GET这种方式匹配
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
// ...省略别的判断
// 返回一个新的请求路径、请求头、请求方式等都完成符合这次请求的RequestMappingInfo信息
return new RequestMappingInfo(this.name, pathPatterns, patterns,
methods, params, headers, consumes, produces, custom, this.options);
}
,就将这个加到matches
matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
2.3 return bestMatch.getHandlerMethod();
这里就是直接返回HandlerMethod
了,然后将也会调用HandlerMethod.createWithResolvedBean
将这个Controller通过this.beanFactory.getBean(beanName)
获取到,再重新组装个HandlerMethod
返回给AbstractHandlerMapping.getHandler
生成HandlerExecutionChain总结:
那么这里就已经生成了HandlerExecutionChain
,我们总结一下流程:
1.调用AbstractHandlerMapping的getHandler
2.调用到子类的AbstractHandlerMethodMapping的getHandlerInternal
3.调用AbstractHandlerMethodMapping.lookupHandlerMethod
,通过mappingRegistry
获取到这次请求路径对应的Controller的方法信息(RequestMappingInfo)
4.校验上面第3步获取的RequestMappingInfo
信息的请求方式(POST,GET),请求头是否符合要求,选出最合适的返回
5.取出第4步的RequestMappingInfo
,将里面HandlerMethod(可以简单理解为就是Controller的方法)和这次请求的Request
一起组合成HandlerExecutionChain
5.请求注册表(mappingRegistry)是如何注册的
前面我们提到了这个mappingRegistry
是很关键的,把@Controller这些的类的方法注册到里面,那是如何注册的呢,我们下面分析一下
1.AbstractHandlerMethodMapping.InitializingBean
mappingRegistry
的加载注册是在AbstractHandlerMethodMapping这个类的实现的,那我们先看这个类
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
这个类实现了InitializingBean ,所以到里面看一下主要是这个方法initHandlerMethods
protected void initHandlerMethods() {
// 获取所有的bean
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 对这些bean处理
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
这里就是把所有Spring的Bean都取出来,然后挨个去调用processCandidateBean
2.processCandidateBean
protected void processCandidateBean(String beanName) {
// 判断是否是匹配当去处理器的,也就是`AbstractHandlerMethodMapping`的子类的`isHandler`是true
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
这里有一个重点,isHandler
,这个是个抽象函数,在这里只有RequestMappingHandlerMapping
实现了。这个函数是用于判断这个Bean是否是能被RequestMappingHandlerMapping
处理的。
我们看一下它的实现:
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
这里就很明显了吧,那些被打算了@Controller,或者RequestMapping注解的类就会通过,进入detectHandlerMethods
,然后被扫描,分析。注册到mappingRegistery
3.检测那些@Controller或者@RequestMapping的类对应的请求
我们继续进入到detectHandlerMethods
protected void detectHandlerMethods(Object handler) {
// 1.获取这个类的信息,这里就是一个Controller的Class
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 2.获取用户自定义的Class,有些是cglib的,
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 3.获取有RequestMapping注解的方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
// 4.循环方法,注入到mappingRegistry
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
梳理一下主要流程:
1.获取这个类的信息,这里就是一个Controller的Class
2.获取用户自定义的Class,有些是cglib的
3.获取有RequestMapping注解的方法
4.循环第三步的方法,注入到mappingRegistry
其中最关键的就是第三步,他是怎么去获取由RequstMapping的注解的方法的呢
4.获取RequstMapping的注解的方法
先把代码再复制一遍看下:
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
这里MethodIntrospector.selectMethods
这个方法是由Spring-Core提供的,并不是SpringMVC独有的,那我们这先说下这个是干嘛的.
其实就是他会遍历传入的类的所有方法,然后每次遍历一个方法都会调用传入的MetadataLookup
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup)
具体这个函数大家可以跟进去看,其实他就是把这个targetType
的类、父类以及实现的接口,都找出来,然后再一个一个的遍历他们的方法。
下面是伪代码:
// 1.找出所有的类,包括类和接口
List<Class<?>> clzs = findAllClassAndInterface();
// 2.遍历类和接口
for (Class clz : clzs) {
// 3.找出所有的方法
List<Method> methods = clz.getAllMethods();
// 4.遍历方法
for (Method method : methods) {
// 5.调用回调函数
metadataLookup.inspect(method);
}
}
···
那这里我们就需要看一下我们传入的这个MetadataLookup是啥。
```java
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);
}
}
这里调用了getMappingForMethod
,这个函数最终由RequestMappingHandlerMapping
实现了。
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 1.创建方法请求映射信息
RequestMappingInfo info = createRequestMappingInfo(method);
// 2.如果创建成功,走下面
if (info != null) {
// 2.创建对象(Controller)请求映射信息
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 3.两个结合成一个完整的新的信息
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
// 4.返回请求映射信息
return info;
}
这里看一下createRequestMappingInfo
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
这里很清晰了把,就是获取方法上面的@RequestMapping
注解,没有的话就不创建了,只有打上了这个注解的才能被创建注册。
再进入到createRequestMappingInfo
,这里是真正创建的地方:
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
RequestMappingInfo.Builder builder = RequestMappingInfo
.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);
}
return builder.options(this.config).build();
}
这里用了RequestMappingInfo
的builder,看一下就知道,其实就是把注解上面的路径、参数、请求头这些设置进去然后返回。
这里就创建好了Method
的RequestMappingInfo
,接下面就将类的RequestMappingInfo
也创建好了。
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
最终将方法的和类的RequestMappingInfo
组合一下,返回给methods
。注入到mappingRegistry
。
这里再说一下为什么要组合一下。
因为注入到mappingRegistry
,是要全的信息。比如再@Controller的@RequestMapping的是路径/test
,然后里面的方法@RquestMapping注解是路径/map
,那他们组合一下就是全的请求路径/test/map
。 不仅这些路径,还有些别的属性也是需要整合,然后封装到一个RequestMappingInfo
返回给mappingRegistry
。
这样我们就知道了一个@Controller是如何注册到mappingRegistry
的了。
总结
1.AbstractHandlerMethodMapping.InitializingBean
2.processCandidateBean里面判断是否有@Controller或者@RequestMapping
3.将第二步检测通过的类的方法进行遍历,包括它的父类的,接口的方法
4.遍历方法的时候获取到@RequestMapping上面的信息,生成RequstMappingInfo
5.将类和方法的RequstMappingInfo合并成新的,返回给mappingRegistry去注册