前言
是Java开发的小伙伴就一定会使用SpringMVC
,没有SpringBoot
的年代或许我们还需要配置一些xml
文件。但是到了SpringBoot
时代,Java程序员只需要使用@Controller
、@RequestMapping
等注解就OK了,一切都变得非常简单,你只需要专注业务代码就可以。但是专注业务代码的同时,我们还是需要了解这背后的@Controller
,@RequestMapping
背后的故事。
SpringMVC启动涉及解析Controller&RequestMapping的步骤
- 获取
Spring
工厂中所有的Bean
。 - 在所有的
Bean
中找出被@Controller
或@RequestMapping
注解的类,比如说找到A
类。 - 获取
A
类满足以下条件的方法:public
方法、被@RequestMapping
注解标记的方法,比如找到方法b
。(父类的方法也会被找出来)。 - 获取方法
b
的RequestMapping
信息,获取A
类上的RequestMapping
(如果有的话)信息,将两者信息进行合并新的RequestMapping
。(比如组合路径,类: /a, 方法:/b ->组合 /a/b) - 方法
b
和A
类的beanName
(或者A
类对象)构建HandlerMethod
,以合并的新RequestMapping
信息为key,以HandlerMethod
为value
存入内存中。
数据存储如下
{"requestMapping合并信息":"handlerMethod"}
介绍完基本步骤后,接下来就是在源码中把关键的代码找出来就可以了。
入口
这里先介绍下SpringMVC
加载这些步骤的入口-RequestMappingHandlerMapping
,这个类负责加载这些步骤,所以大家可以打开RequestMappingHandlerMapping
这个类一起跟看源码。
详解
入口方法是在RequestMappingHandlerMapping
父类AbstractHandlerMethodMapping
中,在文件中找到这个下面方法
protected void initHandlerMethods() {
// 这是第一步:获取所有beanName
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
// 这个是用来判断bean是不是由ScopedProxyUtils这个工具类生成的,可以不用管,全当这个判断不存在
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
}
}
// 这是第二步:找到只有包含Controller 和 RequestMapping的bean
if (beanType != null && isHandler(beanType)) {
//剩下步骤在这里处理
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
剩下步骤具体代码
protected void detectHandlerMethods(final Object handler) {
// 这个handler 就是被Controller或RequestMappering 标记的bean
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 第四步:找出符合条件的方法,并合并方法和类的RequestMapping信息。
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
}
}
});
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
// 第五步:放入内存
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
第四步骤:合并方法和类的RequestMapping信息
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 获取方法中的RequestMapping的信息
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 获取类的RequestMapping信息
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 合并RequestMapping信息
info = typeInfo.combine(info);
}
}
return info;
}
第五步骤:合并的信息存入内存
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// HandlerMethod是不是有点熟悉,就是SpringMVC拦截器中的那个参数。
//HandlerMethod 包含bean对象以及对应的方法
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
// ReqeustMapping合并信息放入内存
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//将相对路径与RequestMapping对应起来; /aa/dd
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null)
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
下面拦截器中的handler
参数就是HandlerMethod
其实在用户请求的时候,SpringMVC
会根据用户请求的相对路径在urlLookup
中找出RquestMapping
信息,然后根据RequestMapping
找出HandlerMethod
,所以关系就对应起来了。关系如下图。
SpringMVC的拦截器中的预处理和后处理就是在反射前后执行。关系就如下图所示。