SpringMVC中的HandlerMapping詳解
1.簡介
在SpringMVC請求流程中我們可以看出,HandlerMapping是請求映射處理器,也就是通過請求的url找到對應(yīng)的邏輯處理單元(Controller),注意這里只是建立請求與Controller的映射關(guān)系,最終的處理是通過HandlerAdapt來進(jìn)行處理的。
2.初始化分析
在初識DispatcherServlet中提到過DispatcherServlet的初始化中會調(diào)用initStrategies()方法,實際上HandlerMapping的初始化是調(diào)用initStrategies()中的
initHandlerMappings(context);
從方法名稱就可以看出來是進(jìn)行HandlerMapping的初始化。下面來看一下具體的源碼:
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
//從ApplicationContext(包括繼承來的上下文)中獲取所有的HandlerMapping
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
//從ApplicationContext中獲取HandlerMapping
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.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
//若上下文中沒有handlerMapping,就使用Spring默認(rèn)的handlerMapping
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}從以上初始化源碼中我們可以看出handlerMapping的初始化:
1.通過detectAllHandlerMappings參數(shù)來控制是僅從當(dāng)前context中獲取handlerMapping,還是需要從當(dāng)前+繼承來的context中獲取所有的handlerMapping
2.若context中沒有獲取到handlerMapping,SpringMVC提供了默認(rèn)的handlerMapping來處理請求
下面我們來看一下,默認(rèn)的handlerMapping
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
//關(guān)鍵是這句,從默認(rèn)配置中獲取handlerMapping
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<T>(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(
"Error loading DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<T>();
}
}關(guān)鍵是defaultStrategies,它是個Properties對象,用來存儲讀取到的SpringMVC默認(rèn)的配置,那SpringMVC的默認(rèn)配置是在哪里定義的呢?在DispatcherServlet中有這么一段:
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}這段就是讀取默認(rèn)配置的代碼,它從DispatcherServlet同級目錄中的DispatcherServlet.properties文件中讀到相關(guān)配置,文件內(nèi)容如下:
. .****省略其他配置***** . org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
從默認(rèn)配置中可以明顯看出,SpringMVC默認(rèn)提供了兩種HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping
3.請求處理分析
HandlerMapping是請求處理的映射器,那是它是如何做請求映射處理的呢?下面我們來看一下DispatcherServlet中對請求映射的處理。 DispatcherServlet的doDispatch方法中有這么一段:
mappedHandler = getHandler(processedRequest);
這一段就是HandlerMapping對請求映射的處理,返回一個Handler執(zhí)行鏈(HandlerExecutionChain)
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根據(jù)reuqest中的請求url獲取handler
Object handler = getHandlerInternal(request);
if (handler == null) {
//若沒有獲取到handler,就獲取默認(rèn)的handler
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
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;
}4.請求是如何找到Controller的
我們知道Web容器在啟動的時候,會自動進(jìn)行Servlet的初始化,由Servlet的初始化來驅(qū)動Spring容器(ApplicationContext)的初始化,在Spring容器初始化的時候,會把標(biāo)注為@Controller的類作為Bean加載到Spring容器中,如下圖方法的調(diào)用??梢钥闯龀跏蓟^程

//org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping
protected void detectHandlers() throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
}
//獲取Spring容器中的BeanName
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
// 遍歷Spring容器中的Bean
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// 將RequestMapping中的url與beanName建立關(guān)系,注冊到handlerMap中
registerHandler(urls, beanName);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}
//注冊Handler
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
//建立url與beanName的關(guān)系,方便后面通過請求url找到對應(yīng)的Bean
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}讓我們再回到HandlerMapping的處理,當(dāng)請求到來時,會先到DispatcherServlet,然后會執(zhí)行HandlerMapping相關(guān)方法,下面我們再次看一下
//DispatcherServlet
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
//獲取Handler
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}//org.springframework.web.servlet.handler.AbstractHandlerMapping
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//通過請求中的url拿到之前初始化時handlerMap中的bean,并返回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 = 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;
}protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//從request中拿到請求的url
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//通過請求的url拿到handler,具體怎么拿的,請往下看lookupHandler()方法的實現(xiàn)
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// 從handlerMap中拿handler,有沒有很熟悉的感覺,對,前面初始化時我們把url與BeanName的對應(yīng)關(guān)系放到handlerMap中,現(xiàn)在取出Handler,實際上取出來的就是beanName,此時通過請求的url就拿到了對應(yīng)的beanName,handlerMapping也光榮的完成了它的任務(wù)
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
//通過beanName從Spring容器中拿到Bean對象
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern +"/");
}
}
}
String bestPatternMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestPatternMatch = matchingPatterns.get(0);
}
if (bestPatternMatch != null) {
handler = this.handlerMap.get(bestPatternMatch);
if (handler == null) {
Assert.isTrue(bestPatternMatch.endsWith("/"));
handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}總結(jié)
1、HandlerMapping的任務(wù)就是建立url與Bean(Controller)的對應(yīng)關(guān)系,并將對應(yīng)關(guān)系保存在handlerMap中
2、對應(yīng)關(guān)系在HandlerMapping初始化的時候就已經(jīng)建立好了
3、當(dāng)請求到來時,會通過DispatcherServlet找到HandlerMapping,然后通過請求的url在handlerMap中找到對應(yīng)的Controller,至此HandlerMapping的任務(wù)完成了,下一步就是HandlerAdapter出場了
到此這篇關(guān)于SpringMVC中的HandlerMapping詳解的文章就介紹到這了,更多相關(guān)HandlerMapping詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何解決UnsupportedOperationException異常問題
這篇文章主要介紹了如何解決UnsupportedOperationException異常問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
Java任意長度byte數(shù)組轉(zhuǎn)換為int數(shù)組的方法
這篇文章主要給大家介紹了關(guān)于Java任意長度byte數(shù)組轉(zhuǎn)換為int數(shù)組的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
解決spring-boot 打成jar包后 啟動時指定參數(shù)無效的問題
這篇文章主要介紹了解決spring-boot 打成jar包后 啟動時指定參數(shù)無效的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Spring中的NamespaceHandler加載過程源碼詳解
這篇文章主要介紹了Spring中的NamespaceHandler加載過程源碼詳解,Spring提供的NamespaceHandler的處理機(jī)制,簡單來說就是命名空間處理器,Spring為了開放性提供了NamespaceHandler機(jī)制,這樣我們就可以根據(jù)需求自己來處理我們設(shè)置的標(biāo)簽元素,需要的朋友可以參考下2024-02-02
Java實現(xiàn)提取Word文檔表格數(shù)據(jù)
使用Java實現(xiàn)Word文檔表格數(shù)據(jù)的提取,可以確保數(shù)據(jù)處理的一致性和準(zhǔn)確性,同時大大減少所需的時間和成本,下面我們來看看具體實現(xiàn)方法吧2025-01-01
Java實現(xiàn)統(tǒng)計在線人數(shù)功能的方法詳解
很多人在筆試或者面試中問到:現(xiàn)在要你實現(xiàn)一個統(tǒng)計在線人數(shù)的功能,你該怎么設(shè)計?不知道的朋友,這篇文章就來告訴你具體實現(xiàn)方法2022-08-08

