【Spring MVC】HandlerMapping初始化詳解(超詳細過程原始碼分析)
阿新 • • 發佈:2019-02-10
Spring MVC的Control主要由HandlerMapping和HandlerAdapter兩個元件提供。HandlerMapping負責對映使用者的URL和對應的處理類,HandlerMapping並沒有規定這個URL與應用的處理類如何對映,在HandlerMapping介面中只定義了根據一個URL必須返回一個由HandlerExecutionChain代表的處理鏈,我們可以在這個處理鏈中新增任意的HandlerAdapter例項來處理這個URL對應的請求。
HandlerMapping類相關的結構圖
HandlerMapping的初始化程式
Spring MVC提供了許多HandlerMapping的實現,預設使用的是BeanNameUrlHandlerMapping,可以根據Bean的name屬性對映到URL中。同時,我們可以為DispatcherServlet提供多個HandlerMapping供其使用。DispatcherServlet在選用HandlerMapping的過程中,將根據我們指定的一系列HandlerMapping的優先順序進行排序,然後優先使用優先順序高的HandlerMapping。預設情況下,Spring MVC會載入在當前系統中所有實現了HandlerMapping介面的bean,再進行按優先順序排序。如果只期望Spring MVC只加載指定的HandlerMapping,可以修改web.xml中的DispatcherServlet的初始化引數,將detectAllHandlerMappings的值設定為false。這樣,Spring MVC就只會查詢名為“handlerMapping”的bean,並作為當前系統的唯一的HandlerMapping。所以在DispatcherServlet的initHandlerMappings()方法中,優先判斷detectAllHandlerMappings的值,再進行下面內容。如果沒有定義HandlerMapping的話,Spring MVC就會按照DispatcherServlet.properties所定義的內容來載入預設的HandlerMapping。從配置檔案中確定了需要初始化的HandlerMapping,而HandlerMapping有很多的實現類,具體的某個實現類是怎麼初始化的呢?那麼接下來以SimpleUrlHandlerMapping為例,具體看看HandlerMapping的初始化過程。private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // 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<HandlerMapping>(matchingBeans.values()); // We keep HandlerMappings in sorted order. OrderComparator.sort(this.handlerMappings); } } else { try { 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) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
ApplicationObjectSupport首先呼叫setApplicationContext()方法,其中的initApplicationContext()方法由子類覆蓋和實現。public final void setApplicationContext(ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } }
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
在子類SimpleUrlHandlerMapping中的initApplicationContext()方法中,先初始化Spring MVC容器,然後再對Handler進行註冊。 protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}
在SimpleUrlHandlerMapping的父類AbstractHandlerMapping中,detectMappingInterceptors()方法探測ApplicationContext中已經解析過的MappedInterceptor,initInterceptors()方法來初始化攔截器。 protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
if (interceptor instanceof MappedInterceptor) {
mappedInterceptors.add((MappedInterceptor) interceptor);
}
else {
adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
}
呼叫initInterceptors()方法將SimpleUrlHandlerMapping中定義的interceptors包裝成HandlerInterceptor物件儲存在adaptedInterceptors陣列中。 protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
}
}
}
再回到initApplicationContext()方法中呼叫的registerHandlers()方法,這邊主要是主要是對urlMap中的key值進行了一些處理,要是沒有“/”的就加上"/",去掉空格等處理。這裡的urlMap就是在配置檔案中SimpleUrlHandlerMapping的通過mappings屬性注入的的內容。key是url的某個欄位,value是bean的id。這個方法中的重點是呼叫了registerHandler(url, handler)這個方法,在這個方法是它的父類AbstractUrlHandlerMapping中的方法。 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 {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
從這邊就可以清楚的看到,這裡根據SimpleUrlHandlerMapping中的urlMap中的value值通過getBean()方法得到bean物件(通過id查詢)。同時將url的某個欄位作為key值,bean作為value重新存入到AbstractUrlHandlerMapping的urlMap屬性中去,這樣就達到url的某個欄位對應到具體的controller了的目的,當遇到有請求訪問伺服器的時候,就可以根據url找到具體的controller去執行這個請求了。也就是說,HandlerMapping的初始化過程主要分成兩部分,通過initInterceptors()方法將SimpleUrlHandlerMapping中定義的interceptors包裝成HandlerInterceptor物件儲存在adaptedInterceptors陣列中,同時通過registerHandlers()方法將SimpleHandlerMapping中定義的mappings(即URL與Handler的對映)註冊到handlerMap集合中。