Spring MVC請求地址對映詳解:HandlerMapping
1 HandlerMapping介紹
HandlerMapping
是Spring MVC的核心元件之一,用來儲存request
-handler
之間的對映。
簡單來說,request
指的是請求地址(還包括請求方法等),handler
指的是Controller中對應的方法。
例如,在日常開發時,我們會定義Controller來接收請求:
@RestController public class TestController { @RequestMapping("/hello") public String hello() { return "Hello HandlerMapping`"; } }
這裡的請求地址/hello
表示request
,而TestController#hello()
就是對應的handler
。HandlerMapping
在初始化的時候,會掃描整個專案,快取所有Controller的request
-handler
對映。
當接收到請求時,會根據請求地址等資訊從HandlerMapping
中找到對應的handler
,從而執行對應的業務邏輯。
2 DispatcherServlet中使用HandlerMapping
DispatcherServlet
是Spring MVC的核心,它持有HandlerMapping
作為成員變數:
private List<HandlerMapping> handlerMappings;
在初始化時會載入HandlerMapping
作為成員變數,在處理請求時會從HandlerMapping
的快取中找到對應的handler
。
2.1 初始化HandlerMapping
在專案啟動時,DispatcherServlet
會進行HandlerMapping
初始化。
載入HandlerMapping
可能存在三種情況:
- 從Spring容器中獲取所有型別為
HandlerMapping
的bean,作為handerMappings
成員變數。 - 從Spring容器中獲取名為
handlerMapping
的bean,作為handerMappings
成員變數。 - 從
DispatcherServlet.properties
HandlerMapping
實現類的全限定類名,例項化並作為handerMappings
成員變數。org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
原始碼:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 1、Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 2、從Spring容器中獲取名為`handlerMapping`的bean
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// 3、Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
this.detectAllHandlerMappings
預設為true
,所以預設會從Spring容器中獲取所有型別為HandlerMapping
的bean,作為handerMappings
成員變數。
如果通過前兩種方式都沒有新增請求對映器,會從DispatcherServlet.properties
檔案中新增預設請求對映器:
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
新增預設請求對映器會按照如下步驟進行:
- 讀取配置檔案中預設請求對映器的全限定類名
- 例項化請求對映器,作為
bean
物件交給Spring容器管理 - 賦值給
DispatcherServlet#handlerMappings
作為請求對映器org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies
原始碼如下:
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
if (defaultStrategies == null) {
try {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized // by application developers. ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return Collections.emptyList();
}
}
2.2 獲取HandlerExecutionChain
DispatcherServlet
會根據請求,從對應的HandlerMapping
中獲取HandlerExecutionChain
。org.springframework.web.servlet.DispatcherServlet#getHandler
:
/**
* Return the HandlerExecutionChain for this request. * <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
在這個過程中,org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
會完成以下幾個功能:
- 從
HandlerMapping
實現類中獲取對映的handler。 - 新增配置攔截器
- 新增跨域攔截器
/**
* Look up a handler for the given request, falling back to the default * handler if no specific one is found. * @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 1、從HandlerMapping實現類中獲取對映的handler
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}
// 2、新增配置的攔截器
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}
// 3、新增跨域攔截器
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerInternal
是一個抽象方法,由子類實現,並制定各自的對映規則:
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain
方法會新增配置好的攔截器:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
這裡的攔截器來Spring Boot新增的預設攔截器,以及使用者自定義攔截器:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 新增自定義攔截器
}
}
跨域攔截器會根據是否新增跨域配置進行判斷,這裡的跨域配置來自於WebMvcConfigurer
/@CrossOrigin
/CorsConfigurationSource
。
2.3 執行HandlerExecutionChain
HandlerExecutionChain
中包含handler
和interceptorList
。DispatcherServlet
會按順序依次執行:
-
interceptorList
的preHandle
方法 - 使用
handlerApdater
呼叫handler
-
interceptorList
的postHandle
方法 -
interceptorList
的afterCompletion
方法
3 RequestMappingHandlerMapping
RequestMappingHandlerMapping
是針對@Controller
和@RequestMapping
的實現類,是日常專案中使用最多的請求地址對映器。
3.1 初始化:註冊bean
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping
:
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.getPatternParser() != null) {
mapping.setPatternParser(pathConfig.getPatternParser());
}
else {
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
}
Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
if (pathConfig.getPathPrefixes() != null) {
mapping.setPathPrefixes(pathConfig.getPathPrefixes());
}
return mapping;
}
SpringMVC攔截器HandlerInterceptor
初始化:
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getInterceptors
:
/**
* Provide access to the shared handler interceptors used to configure * {@link HandlerMapping} instances with.
* <p>This method cannot be overridden; use {@link #addInterceptors} instead.
*/protected final Object[] getInterceptors(
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addInterceptors
作為擴充套件點,支援新增自定義攔截器:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 新增自定義攔截器
}
}
SpringMVC內容協商管理器ContentNegotiationManager
初始化:
mapping.setContentNegotiationManager(contentNegotiationManager);
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#mvcContentNegotiationManager
:
/**
* Return a {@link ContentNegotiationManager} instance to use to determine
* requested {@linkplain MediaType media types} in a given request.
*/@Bean
public ContentNegotiationManager mvcContentNegotiationManager() {
if (this.contentNegotiationManager == null) {
ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
configurer.mediaTypes(getDefaultMediaTypes());
configureContentNegotiation(configurer);
this.contentNegotiationManager = configurer.buildContentNegotiationManager();
}
return this.contentNegotiationManager;
}
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#configureContentNegotiation
作為擴充套件點,支援新增自定義內容協商管理器:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 新增自定義內容協商管理器
}
}
SpringMVC跨域配置CorsConfiguration
初始化:
mapping.setCorsConfigurations(getCorsConfigurations());
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getCorsConfigurations
:
/**
* Return the registered {@link CorsConfiguration} objects,
* keyed by path pattern. * @since 4.2 */protected final Map<String, CorsConfiguration> getCorsConfigurations() {
if (this.corsConfigurations == null) {
CorsRegistry registry = new CorsRegistry();
addCorsMappings(registry);
this.corsConfigurations = registry.getCorsConfigurations();
}
return this.corsConfigurations;
}
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addCorsMappings
作為擴充套件點,支援新增自定義跨域配置:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 新增自定義跨域配置
}
}
SpringMVC地址解析器相關初始化:
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.getPatternParser() != null) {
mapping.setPatternParser(pathConfig.getPatternParser());
}
else {
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
}
Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
if (pathConfig.getPathPrefixes() != null) {
mapping.setPathPrefixes(pathConfig.getPathPrefixes());
}
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getPathMatchConfigurer
:
/**
* Callback for building the {@link PathMatchConfigurer}.
* Delegates to {@link #configurePathMatch}. * @since 4.1 */protected PathMatchConfigurer getPathMatchConfigurer() {
if (this.pathMatchConfigurer == null) {
this.pathMatchConfigurer = new PathMatchConfigurer();
configurePathMatch(this.pathMatchConfigurer);
}
return this.pathMatchConfigurer;
}
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#configurePathMatch
作為擴充套件點,支援新增自定義地址解析器:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 新增自定義地址解析器
}
}
3.2 初始化:請求地址對映掃描
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet
,實現InitializingBean
介面:
/**
* Detects handler methods at initialization. * @see #initHandlerMethods */@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods
:
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
// 1、遍歷所有bean
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 2、請求地址對映處理
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#processCandidateBean
:
/**
* Determine the type of the specified candidate bean and call
* {@link #detectHandlerMethods} if identified as a handler type.
* <p>This implementation avoids bean creation through checking
* {@link org.springframework.beans.factory.BeanFactory#getType}
* and calling {@link #detectHandlerMethods} with the bean name.
* @param beanName the name of the candidate bean
* @since 5.1
* @see #isHandler
* @see #detectHandlerMethods
*/
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
// 1、獲取bean的類物件
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);
}
}
// 2、判斷當前HandlerMapping實現類能否處理當前bean
if (beanType != null && isHandler(beanType)) {
// 3、解析&對映請求地址
detectHandlerMethods(beanName);
}
}
isHandler()
由子類實現,用來判斷該類物件是否需要進行請求地址解析。例如,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler
:
/**
* {@inheritDoc}
* <p>Expects a handler to have either a type-level @{@link Controller}
* annotation or a type-level @{@link RequestMapping} annotation.
*/@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
:
/**
* Look for handler methods in the specified handler bean. * @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod */protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 1、遍歷Handler類的方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 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));
}
else if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
// 3、獲取實際能夠執行的方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 4、儲存請求地址對映資訊
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
org.springframework.core.MethodIntrospector#selectMethods(java.lang.Class<?>, org.springframework.core.MethodIntrospector.MetadataLookup<T>)
:
/**
* Select methods on the given target type based on the lookup of associated metadata. * <p>Callers define methods of interest through the {@link MetadataLookup} parameter,
* allowing to collect the associated metadata into the result map. * @param targetType the target type to search methods on
* @param metadataLookup a {@link MetadataLookup} callback to inspect methods of interest,
* returning non-null metadata to be associated with a given method if there is a match, * or {@code null} for no match
* @return the selected methods associated with their metadata (in the order of retrieval), * or an empty map in case of no match */public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = new LinkedHashMap<>();
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Class<?> specificHandlerType = null;
if (!Proxy.isProxyClass(targetType)) {
specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
} }, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
getMappingForMethod()
由子類實現,用來指定不同的解析規則。例如,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod
:
/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo. * @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 1、獲取方法級別的地址資訊
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 2、獲取類級別的地址資訊
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);
}
} return info;
}
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(org.springframework.web.bind.annotation.RequestMapping, org.springframework.web.servlet.mvc.condition.RequestCondition<?>)
:
/**
* Create a {@link RequestMappingInfo} from the supplied
* {@link RequestMapping @RequestMapping} annotation, which is either
* a directly declared annotation, a meta-annotation, or the synthesized * result of merging annotation attributes within an annotation hierarchy. */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();
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod
:
/**
* Register a handler method and its unique mapping. Invoked at startup for * each detected handler method. * @param handler the bean name of the handler or the handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already registered
* under the same mapping */protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register
:
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
// @CrossOrigin配置資訊初始化
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
3.3 處理請求:請求地址對映
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
:
/**
* Look up a handler method for the given request.
*/
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 1、解析請求地址
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
// 2、根據請求地址找對映方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}