springmvc篇:【DispatcherServlet源碼詳解】

注意:爲了突出源碼主要代碼邏輯,本章中貼出的源碼,將刪除例如log,try等非主要的代碼。而知貼出重要的代碼塊
先看入口

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;

static {
    // 將spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet 目錄下的
    // DispatcherServlet.properties文件中的內容放到properties對象中,下面各個init方法中的
    // getDefaultStrategy方法裏,將會通過這個對象,來生成默認放到容器中的類
    ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
    defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		
}

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
}

當我們啓動tomcat時,會通過onRefresh方法開始進入,然後出初始化一些內容。下面我們分別來看一下

  • initMultipartResolver(context)方法
    public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
    private void initMultipartResolver(ApplicationContext context) {
        // 從容器中獲取實現了MultipartResolver接口並且名字爲multipartResolver的bean
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    }

    這個方法主要是從容器中獲取名字爲multipartResolver的bean。這個bean主要是用來處理文件上傳用的,並且這個bean必須實現MultipartResolver接口。現在再來回想一下,我們spring-mvc.xml中是怎麼配置文件上傳的?是不是如下樣子:

    注意看id,它與源碼中的常量字符串是一樣的。這也就是爲什麼這個id必須要這麼寫的原因了。不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。
    MultipartResolver的默認實現類有兩個,上圖便是使用其中一個:

  • initLocaleResolver(context)方法

    public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
    private void initLocaleResolver(ApplicationContext context) {
       try {
            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
            }
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 將DispatcherServlet.properties中名爲LocaleResolver對應的類放到容器中
            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        }
    }
    
    DispatcherServlet.properties位於
    spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet目錄下,內容如下:
    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    ...其他的省略....

    這個方法是用來從容器中獲取實現本地化的bean,回想一下,我們如果要做本地化的話,在spring-mvc.xml中是怎麼寫的?
    是不是一般會如下的寫法:

    注意看id,它與源碼中的常量字符串是一樣的。這也就是爲什麼這個id必須要這麼寫的原因了。不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。
    這個bean你既可以自己通過實現接口自定義一個,也可以使用springmvc自己的一些實現類,如下:


     

  • initThemeResolver(context)方法

    public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
    private void initThemeResolver(ApplicationContext context) {
        try {
            this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
           
        }catch (NoSuchBeanDefinitionException ex) {
             // 將DispatcherServlet.properties中名爲LocaleResolver對應的類放到容器中
            this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
        }		
    }
    
    
    DispatcherServlet.properties內容如下:
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    ...其他的省略....

    這個方法主要是從容器中獲取主題解析器(ThemeResolver)的,什麼是ThemeResolver呢?大家都見過通過在管理系統上選擇樣色樣式,然後系統的色調就都變了的效果吧。關於如何使用主題解析器,可以參考這篇文章:https://blog.csdn.net/qq924862077/article/details/53438710

    下面來看一下如何通過spring-mvc.xml來聲明一個主題解析器相關的bean,如下:

    注意看id,它與源碼中的常量字符串是一樣的。這也就是爲什麼這個id必須要寫themeResolver的原因了。不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。

    下面來看一下springmvc默認給我們帶了那些ThemeResolver的實現類,如下:

     

  • initHandlerMappings(context)方法

    public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
        // 通過在xml中設置detectAllHandlerMappings這個屬性,來決定是加載所有的handlermapping還是隻加載我們自定義的那個handlermapping
        // 如果未true,從容器中獲取所有的handlermapping
        if (this.detectAllHandlerMappings) {
        // 從容器中獲取所有的handlerMapping,比如springmvc啓動時自動加載的和我們自定義的
        Map<String, HandlerMapping> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // 對這些handlermapping的順序進行排序
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
    	}else { // 爲false,那麼就只從容器中獲取名字爲handlerMapping的那個handlermapping
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
    		}
            //如果從容器找不到,就生成一個默認的
    		if (this.handlerMappings == null) {
                // 從DispatcherServlet.properties文件中找HandlerMapping對應的那些就是默認的handlermapping
                this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    		}
    	}

    這個方法主要就是從容器中獲取handlermapping的(什麼是handlermapping,請參考前面的文章)。springmvc默認給我們帶的handlermapping有哪些,如下圖:

    到底哪些handlermapping在程序啓動時,會自動放到容器中,這就需要根絕一些觸發條件了,比如,如果我們少用了<mvc:annotation-driven/>標籤,那麼RequestMappingHandlerMapping就會自動放到容器中(原因不在此解釋)。
    基本上來看,當detectAllHandlerMappings爲true時,下面三個handlermapping會在容器出現。
    1) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    2) org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
    3) org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
    那麼如果detectAllHandlerMappings爲false時,會從容器中獲取名字爲handlerMapping的那個bean,也就是說如果我們想讓springmvc只用我們指定的handlermapping時,通過兩步即可,如下:
    第一步:在web.xml中設置detectAllHandlerMappings爲false(默認爲true)如下:

    第二步:在spring-mvc.xml中聲明要使用的handlermapping,如下:

    <bean id="/myHanler" class="com.lhb.controller.BeanNameURLHandlerMappingController"/> 
    <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    注意id必須爲handlerMapping,不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。
    如果此時此時在容器中找不到對應的名爲handlerMapping的bean則會調用
    this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    去獲取默認的handlerMapping,我們看看getDefaultStrategies是如何獲取的,源碼如下:

    private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
    static {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
            // key就是接口的名字HandlerMapping
    		String key = strategyInterface.getName();
            // 這裏就是從springmvc的jar包中的\org\springframework\web\servlet 文件夾下的DispatcherServlet.properties文件中找到一個名爲HandlerMapping的key,它對應了springmvc默認放到容器中的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) {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
    					strategies.add((T) strategy);
    			}
    			return strategies;
    		}
    		else {
    			return new LinkedList<T>();
    		}
    	}

    DispatcherServlet.properties如下:

    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
    ...其他內容省略...

    從上面源碼可以看出,獲取默認的handlermapping就是從DispatcherServlet.properties文件中找到HandlerMapping對應的這兩個類的全限定名,然後在程序中通過反射實例化放到容器的而已。這也就是爲什麼有時候我們沒聲明bean,程序未報錯的原因了。
     

  • initHandlerAdapters(context)方法

    public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
    private void initHandlerAdapters(ApplicationContext context) {
        this.handlerAdapters = null;
        // detectAllHandlerAdapters爲true,表示從容器獲取所有的handlerAdpater
        if (this.detectAllHandlerAdapters) {
            Map<String, HandlerAdapter> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
                AnnotationAwareOrderComparator.sort(this.handlerAdapters);
            }
        }else {//detectAllHandlerAdapters爲false,則只從容器中找名字爲handlerAdapter的bean
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        // 如果找不到一個handlerAdpater都沒有
        if (this.handlerAdapters == null) {
            // 那麼就加載DispatcherServlet.properties中名爲HandlerAdapter的類作爲默認的handlerAdapter放到容器中
            this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        }
    }
    
    

    從源碼可以看出initHandlerAdapters和initHandlerMapping如出一致,邏輯都是相同的,都是根據detectAllHandlerAdapters來決定是從容器中獲取所有adapter還是隻從容器中獲取名爲handlerAdapter的adapter。
    同理,他的getDefaultStrategies方法,也是將DispatcherServlet.properties中名爲HandlerAdapter對應的類進行實例化,然後放到容器中來作爲默認的adatper的。這裏就不多做解釋了。
    DispatcherServlet.properties如下:

    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

     

  • initHandlerExceptionResolvers(context)方法

    
    public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
    private void initHandlerExceptionResolvers(ApplicationContext context) {
    		this.handlerExceptionResolvers = null;
            
    		if (this.detectAllHandlerExceptionResolvers) {
                // 從容器中獲取所有的HandlerExceptionResolver的實現類
    			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
    					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
    				// 對這些HandlerExceptionResolver的實現類進行排序
    				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
    			}
    		}
    		else {
                // 從容器中找的名爲handlerExceptionResolver的那個類,來處理controller拋出的異常
                HandlerExceptionResolver her =
    						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
                this.handlerExceptionResolvers = Collections.singletonList(her);
    		}
    
    		// 如果從容器中一個對應的resolver都找不到,就生成DispatcherServlet.properties中指定的默認resolver。
    		if (this.handlerExceptionResolvers == null) {
    			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
    			
    			}
    		}
    	}

    從源碼可以看出,同樣是通過detectAllHandlerExceptionResolvers這個屬性來決定是從容器中獲取素有的exceptionResolver還是隻從容器值獲取指定名字的exceptionResolver。如果從容器中一個exceptionResolver都獲取不到,那麼就通過getDefaultStrategies方法,將DispatcherServlet.properties中寫的exceptionResolver進行實例化,來使用。
    DispatcherServlet.properties如下:

    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver


     

  • initRequestToViewNameTranslator(context)方法
    什麼是RequestToViewNameTranslator?作用是什麼?可以看我的文章spring篇:【RequestToViewNameTranslator】裏面有詳細說明。下面我們來看源碼:

    public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
    private void initRequestToViewNameTranslator(ApplicationContext context) {
        try {
            this.viewNameTranslator =context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
        }catch (NoSuchBeanDefinitionException ex) {
            // We need to use the default.
            this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
        }
    }
    

    源碼很簡單,就是從容器中去獲取名爲viewNameTranslator的translator,如果容器中沒有,那麼就把DispatcherServlet.properties中寫的那個translator進行實例化來使用。DispatcherServlet.properties如下:

    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator


     

  • initViewResolvers(context)方法
    什麼是RequestToViewNameTranslator?作用是什麼?可以看我的文章spring篇:【RequestToViewNameTranslator】裏面有詳細說明。下面我們來看這個方法的源碼:
     

    public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
    private void initViewResolvers(ApplicationContext context) {
    		this.viewResolvers = null;
            //是否從容器中檢測所有的ViewResolver,detectAllViewResolvers默認爲true,可通過web.xml來修改這個值
    		if (this.detectAllViewResolvers) {
    			// 從容器中檢測所有的ViewResolver的實現類
    			Map<String, ViewResolver> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
    				// 將從容器中檢索到的所有ViewResolver進行排序
    				AnnotationAwareOrderComparator.sort(this.viewResolvers);
    			}
    		}
    		else {
                //否則只從容器中檢測名字爲viewResolver的ViewResolver 
                ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
                this.viewResolvers = Collections.singletonList(vr);
    		}
    
    		// 如果一個ViewResolver也沒得到,就根據DispatcherServlet.properties中指定的來創建一個默認的ViewResolver
    		if (this.viewResolvers == null) {
    			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
    			}
    		}
    	}


     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章