一、HandlerMapping类图
默认情况下,SpringMVC 将加载当前系统中所有实现了HandlerMapping 接口的bean。如果只期望SpringMVC加载指定的handlermapping 时,可以修改web.xml中的DispatcherServlet的初始参数,将detectAllHandlerMappings的值设置为false:
<init-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value> false</param-value>
</ init-param>
此时Spring MVC将查找名为“ handlerMapping”的bean, 并作为当前系统中唯一的handlermapping。如果没有定义handlerMapping 的话,则SpringMVC将按照org. Springframework.web.servlet.,DispatcherServlet所在目录下的DispatcherServlet.properties中所定义的org.Springframework.web.servlet.HandlerMapping的内容来加载默认的handlerMapping (用户没有自定义Strategies的情况下)。
二、初始化HandlerMethod
2.1 初始化HandlerMethod流程
(1) 获取容器中所有的Bean,筛选出类上有@Controller或@RequestMapping注解的类,作为候选Handler
(2) 遍历候选Bean所有方法,如果方法上没有@RequestMapping注解则忽略该方法,否则获取方法和类上的@RequestMapping注解并合并,创建RequestMappingInfo实例
(3) 注册RequestMappingInfo与HandlerMethod映射信息:
将Handler和Method封装到HandlerMethod中,建立RequestMappingInfo --> HandlerMethod映射关系
建立directUrl --> List<RequestMappingInfo>映射关系,同一个URL处理的请求方法不同:Get、Post等
建立name --> List<RequestMappingInfo>映射关系
建立HandlerMethod --> CorsConfiguration映射关系
建立RequestMappingInfo --> MappingRegistration映射关系,MappingRegistration封装了RequestMappingInfo、HandlerMethod、directUrls、name
2.2 RequestMappingHandlerMapping
2.2.1 afterPropertiesSet
从类图中可知RequestMappingHandlerMapping实现InitializingBean接口,下面看afterPropertiesSet方法
public void afterPropertiesSet() {
// 用于请求映射目的配置选项的容器。 创建RequestMappingInfo实例需要这种配置,但是通常在所有RequestMappingInfo实例中使用。
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
super.afterPropertiesSet();
}
2.2.2 getMappingForMethod
// 获取方法上的@RequestMapping注解,并创建RequestMappingInfo。调用者传送门:2.3.5
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
2.2.3 根据@RequestMapping创建RequestMappingInfo createRequestMappingInfo
根据@RequestMapping注解创建RequestMappingInfo时,有getCustomTypeCondition、getCustomMethodCondition这两个方法值得我们关注,可以扩展RequestMappingHandlerMapping实现@RequestMapping注解上的自定义RequestCondition。那么RequestCondition在哪里会用到呢?传送门:4.1
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);
}
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();
}
2.3 AbstractHandlerMethodMapping
2.3.1 afterPropertiesSet
public void afterPropertiesSet() {
initHandlerMethods();
}
2.3.2 initHandlerMethods
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
// 输出总计包括检测到的映射+通过registerMapping的显式注册
handlerMethodsInitialized(getHandlerMethods());
}
2.3.3 获取候选Bean名称 getCandidateBeanNames
// 在应用程序上下文中确定候选bean的名称
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
2.3.4 处理候选Bean processCandidateBean
// 确定指定的候选bean的类型,如果标识为handler类型,则调用 {@link #detectHandlerMethods}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// isHandler由子类实现,类上有@Controller或@RequestMapping注解
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
2.3.5 查找Handler中处理请求方法 detectHandlerMethods
// 在指定的handler bean中查找处理程序方法。{@link #detectHandlerMethods}避免了bean的创建。
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 子类实现获取方法或类上的@RequestMapping,封装为RequestMappingInfo。方法上有@RequestMapping时才会获取类上的@RequestMapping。传送门:2.2.2
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
// 在目标类型上选择一个可调用的方法:给定方法本身(如果实际在目标类型上公开),或者在目标类型的一个接口或目标类型本身上对应的方法。
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
2.3.6 注册Handler中处理请求方法 registerHandlerMethod
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
2.4 HandlerMethod注册容器 MappingRegistry
2.4.1 注册HandlerMethod register
// RequestMappingInfo --> MappingRegistration<RequestMappingInfo>
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
// RequestMappingInfo --> HandlerMethod
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
// url(@RequestMapping注解上配置的) --> RequestMappingInfo
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
// name(HandlerMethodMappingNamingStrategy生成) --> List<HandlerMethod>
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
// HandlerMethod --> CorsConfiguration
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// 创建HandlerMethod实例,封装了handler、method
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
// 获取@RequestMapping注解上配置的非正则表达式路径
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
// 方法或类上的@CrossOrigin注解,封装成CorsConfiguration,由子类实现
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
2.4.2 校验映射关系是否唯一 assertUniqueMethodMapping
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException(
"Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
newHandlerMethod + "\nto " + mapping + ": There is already '" +
handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
}
}