当我们在实际开发中会给类加注解@Controller,给方法加注解@RequestMapping,那么在请求的时候他是怎么知道对应的方法的呢。在介绍前,先了解下spring启动的时候,怎么把映射路劲加载的。
一、启动的时候
在Spring MVC里,有一专门处理请求映射的接口HandlerMapping,
HandlerMapping是一个接口,他有很多子类,下面介绍个很重要的类。
RequestMappingHandlerMapping我们首先看一下RequestMappingHandlerMapping的抽象父类AbstractHandlerMethodMapping,省略其他方法,先关注这两个相关核心方法:
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
@Override
public void afterPropertiesSet() {
initHandlerMethods();
int total = this.getHandlerMethods().size();
if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {
logger.debug(total + " mappings in " + formatMappingName());
}
}
protected void initHandlerMethods() {
String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}catch (Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
}
1、可以看到AbstractHandlerMethodMapping实现了InitializingBean接口,在Spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法
2、 initHandlerMethods方法,顾名思义是初始化HandlerMethods,查看它是被afterPropertiesSet方法调用,这个方法代表bean在容器中被初始化的时候,会去执行initHandlerMethods方法。
那initHandlerMethods方法具体做了什么事情?大概看一下方法内部的业务,首先拿到容器里的所有bean名称放进数组beanNames中;然后遍历数组,拿到每一个bean的类型beanType,对每一个beanType做了一个判断isHandler(beanType),查看此方法的实现,即进入RequestMappingHandlerMapping中:
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements EmbeddedValueResolverAware {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
}
上面代码主要是去判断该类是否加了注解@Controller或@RequestMapping。点进去查看AnnotatedElementUtils.hasAnnotation方法的实现:
public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
if (element.isAnnotationPresent(annotationType)) {
return true;
}
return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
}
判断完继续执行了detectHandlerMethods方法
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//这里有点绕,userType就是我们要处理的某个Controller,下面这段代码就是解析这个Controller里面方法,并把带有RequestMapping的方法返回封装成Mapper
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
//methods就是userType这个类下面所有带有RequestMapping的方法。
//对查找到的HandlerMethod进行注册,保存至内部类mappingRegistry对象中
methods.forEach((key, mapping) -> {
//作下判断,method是否从属于userType
Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
这个方法筛选出了类中加了注解@RequestMapping的方法,放进Map集合methods中,紧接着去遍历每一个method,进入registerHandlerMethod方法,注册到映射注册表mappingRegistry中,这些Map很重要,请求的时候,就是从里面判断的。
至此,我们就明白了在Spring初始化bean的时候,就把所有的加了@Controller和加了@RequestMapping的类,在类里面找到加了@RequestMapping的方法放进了map中,
当http请求来时,直接去map中迅速拿到对应信息。
二、请求进来是找到对应的Controller
1、启动完,请求进入DispatcherServlet的doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
......
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
.....
}
doDispatch会调用一个getHandler方法获得一个HandlerExecutionChain对象
1.1、HandlerExecutionChain是什么
public class HandlerExecutionChain {
//handler是一个HandlerMethod对象
private final Object handler;
//interceptors,表示这个请求的拦截器
private HandlerInterceptor[] interceptors;
//....省略部分代码
}
HandlerExecutionChain主要包含了handler和interceptors两个属性。一个是HandlerMethod对象,一个是拦截器链。dispatcherServlet会对handler和interceptors进行顺序调用,从而实现拦截功能。
1.2、HandlerMethod是什么
public class HandlerMethod {
private final Object bean;
private final BeanFactory beanFactory;
private final Class<?> beanType;
private final Method method;
private final Method bridgedMethod;
private final MethodParameter[] parameters;
private HttpStatus responseStatus;
private String responseStatusReason;
private HandlerMethod resolvedFromHandlerMethod;
//...省略部分代码
}
HandlerMethod包含了要请求方法的方法名,参数,请求类型等。
还保存了这次请求对应的Controller中对应的方法的Method反射对象,只要拿到这个反射对象,就可以反射调用,从而实现从url到对应的Controller中对应的方法的对应关系。
2、getHandler(processedRequest)做了什么
现在我们来分析下他是如何拿到这个HandlerExecutionChain对象的。执行getHandler方法,看下getHandler做了什么。
protected HandlerExecutionChain getHandler(HttpServletRequest request){
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
我们看到DispatcherServlet是通过遍历自身的HandlerMapping的对象数组,然后找到一个合适的HandlerMapping,通过这个个HandlerMapping的getHandler()方法去获得一个HandlerExecutionChain
看一下这个HandlerMapping的getHandler()方法具体怎么实现,这个方法是在AbstractHandlerMapping里面实现的
3、HandlerMapping的getHandler做了什么
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
可以看到在第一行Object handler = getHandlerInternal(request)获得了一个handler对象,而这个handler对象就是HandlerMethod
4、getHandlerInternal是怎么做到的。
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//先拿到请求路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();//使用读取锁
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}finally {
this.mappingRegistry.releaseReadLock();
}
}
请求的链接lookupPath,然后根据这个lookupPath和request对象调用了这个lookupHandlerMethod()方法,得到了handlerMethod对象。再看到这个lookupHandlerMethod()方法
5、lookupHandlerMethod做了什么
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
Match bestMatch = matches.get(0);
...........
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
这里看到关键代码
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath)
1、他根据url获取了一个directPathMatches对象,而这个directPathMatches对象是一个集合,里面装的是RequestMappingInfo对象
2、而这个RequestMappingInfo对象就是我们的关键,后面他会根据这个RequestMappingInfo对象去得到HandlerMethod对象。我们看到这个RequestMappingInfo保存了这次请求的许多信息。
现在看一下他是如何获取这个RequestMappingInfo对象的。
public List<T> getMappingsByUrl(String urlPath) {
return this.urlLookup.get(urlPath);
}
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();
}
可以看到所有的RequestMappingInfo对象,是存在内部类的一个属性中。而这个内部类就是MappingRegistry,他就是在bstractHandlerMethodMapping内部类中。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request){
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
..........
}
这里的directPathMatches是一个数组,所以说可能会有多个RequestMappingInfo对象,在通过addMatchingMappings来找到最合适的HandlerMethod。
我们也终于得到了HandlerMethod对象,然后DispatcherServlet会将它交给HandlerAdater对象,然后执行反射调用。从而完成这个controller方法的调用。
本文转自:https://blog.csdn.net/songzehao/article/details/84979847