Spring MVC源碼學習一之初始化

1、簡單提下使用入門(依賴於註解方式)

一、經過在web.xml中添加配置,引入spring mvc框架,相似於Struts2引入須要在web.xml中配置過濾器StrutsPrepareAndExecuteFilter同樣,可是Spring MVC是經過servlet實現的,須要配置一個Servlet。前端

<servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springConfig/springmvx-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
 </servlet-mapping>

二、在springmvc中主要是配置Controller,靜態文件的映射策略,視圖的配置等(即後面原理要講的幾個組件)。java

<!-- 自動掃描的包名 -->  
    <context:component-scan base-package="com.test"/>  
  
    <!-- 默認的註解映射的支持,自動註冊RequestMappingHandlerMapping和RequestMappingHandlerAdapter
	(spring 3.2以上版本已經再也不是DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter)
    -->  
    <mvc:annotation-driven />  
  
    <!-- 視圖解釋類 -->  
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
        <property name="prefix" value="/WEB-INF/jsp/"/>  
        <property name="suffix" value=".jsp"/>  
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />  
    </bean>

三、編寫controllerios

@Controller
@RequestMapping("/test")
public class IndexController {

    @RequestMapping("/index")
    public ModelAndView index() {
        ModelAndView view = new ModelAndView("index");
        view.addObject("welcome", "hello");
        return view;
    }

}

四、訪問地址:xxx/contexpath/test/index。web

說明:關於mvc:annotation-driven配置的詳細說明能夠參考:https://my.oschina.net/HeliosFly/blog/205343spring

2、先嚐試理解原理

Spring的MVC框架主要由DispatcherServlet、處理器映射(HandlerMapping)、處理器(Controller)、視圖解析器(ViewResolver)、視圖(View)組成。DispatcherServlet是整個Spring MVC的核心。它負責接收HTTP請求組織協調Spring MVC的各個組成部分。編程

  • 原理圖

  • 原理圖說明

一、客戶端發出一個http請求給web服務器,web服務器對http請求進行解析,若是匹配DispatcherServlet的請求映射路徑(在web.xml中指定),web容器將請求轉交給DispatcherServlet.設計模式

二、DipatcherServlet接收到這個請求以後將根據請求的信息(包括URL、Http方法、請求報文頭和請求參數Cookie等)以及HandlerMapping的配置找處處理請求的處理器(Handler)。tomcat

3-四、DispatcherServlet根據HandlerMapping找到對應的Handler,將處理權交給Handler(Handler將具體的處理進行封裝),再由具體的HandlerAdapter對Handler進行具體的調用。服務器

五、Handler對數據處理完成之後將返回一個ModelAndView()對象給DispatcherServlet。mvc

六、Handler返回的ModelAndView()只是一個邏輯視圖並非一個正式的視圖,DispatcherSevlet經過ViewResolver將邏輯視圖轉化爲真正的視圖View。

七、Dispatcher經過model解析出ModelAndView()中的參數進行解析最終展示出完整的view並返回給客戶端。

  • 重要的類說明

DispatcherServlet

Spring提供的前端控制器,全部的請求都有通過它來統一分發。在DispatcherServlet將請求分發給Spring Controller以前,須要藉助於Spring提供的HandlerMapping定位到具體的Controller。

HandlerMapping 

Spring mvc 使用HandlerMapping來找到並保存url請求和處理函數間的mapping關係。   
以RequestMappingHandlerMapping爲例來具體看HandlerMapping的做用 
DefaultAnnotationHandlerMapping將掃描當前全部已經註冊的spring beans中的@requestmapping標註以找出url 和 handler method處理函數的關係並予以關聯。 

Handleradapter 

Spring MVC經過HandlerAdapter來實際調用處理函數。 
以RequestMappingHandlerAdapter爲例 
DispatcherServlet中根據handlermapping找到對應的handler method後,首先檢查當前工程中註冊的全部可用的handlerAdapter,根據handlerAdapter中的supports方法找到可使用的handlerAdapter。經過調用handlerAdapter中的handle方法來處理及準備handler method中的參數及annotation(這就是spring mvc如何將reqeust中的參數變成handle method中的輸入參數的地方),最終調用實際的handle method。 

 

ViewResolver

Spring提供的視圖解析器(ViewResolver)在Web應用中查找View對象,從而將相應結果渲染給客戶。

 

3、初始化過程

DispatcherServlet的繼承體系如:

看到它們繼承自HttpServlet,你就知道初始化過程應該是從init方法開始了,整個初始化的流程爲:

一、服務器啓動的時候,tomcat容器執行init()方法,主要作兩個事情:加載web.xml配置的初始化參數、讓子類回調同時覆寫方法initServletBean()。

/**
	 * Map config parameters onto bean properties of this servlet, and
	 * invoke subclass initialization.
	 * @throws ServletException if bean properties are invalid (or required
	 * properties are missing), or if subclass initialization fails.
	 */
	@Override
	public final void init() throws ServletException {
		// Set bean properties from init parameters.
		
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
		ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
		initBeanWrapper(bw);
		bw.setPropertyValues(pvs, true);

		// Let subclasses do whatever initialization they like.
		initServletBean();
	}

二、對於第一點的initServletBean(),由子類FrameworkServlet繼承,覆寫該方法,所以接下來執行該方法。

/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 */
	@Override
	protected final void initServletBean() throws ServletException {
		
		this.webApplicationContext = initWebApplicationContext();
		//此方法本類沒有代碼邏輯,由子類繼承覆寫該方法,
        //可是DispatcherServlet並無覆寫,一次此方法沒有作任何事。
        initFrameworkServlet();	
	}

第一行代碼的做用是:初始化FrameworkServlet的屬性webApplicationContext,這個屬性表明SpringMVC上下文,它有個父類上下文,既web.xml中配置的ContextLoaderListener監聽器初始化的容器上下文,而後提供onRefresh方法給DispatcherServlet覆寫。

進入initWebApplicationContext()方法,咱們能夠發現代碼分紅兩塊,一塊是獲取WebApplicationContext ,另外一塊是拿到這個類的實例傳遞給onfresh()方法。對於onRefresh方法本類並無任何的邏輯,只是提供了一個模版供子類覆寫。

protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

三、對於第二點提到的onfresh方法,由子類DispatcherServlet繼承覆寫,所以接下來執行DispatcherServlet類中的onRefresh()方法。

/**
	 * This implementation calls {@link #initStrategies}.
	 */
	@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);
	}

從代碼能夠清晰地知道,這個方法就是根據拿到的上下文初始化一些列的策略,即初始化什麼樣的handlerMappings、handlerAdapters等等,這裏咱們看initHandlerMappings這個方法,簡化後的代碼以下:

/**
	 * 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;

		// 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);
		}

		// 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);
		}
	}

先根據前面獲取好的ApplicationContext獲取到全部的handlerMappings,而後排序,這裏調試後的結果是這裏獲取到了兩個handlerMapping:RequestMappingHandlerMapping、=和BeanNameUrlHandlerMapping,若是獲取到的 handlerMappings 集合爲空,則按照默認去加載,這個默認的規則,這個默認的規則是配置在DispatcherServlet.properties中,看下圖代碼結構便可知道。

 

4、總結

回顧整個SpringMVC的初始化流程,咱們看到,經過HttpServletBean、FrameworkServlet、DispatcherServlet三個不一樣的類層次,SpringMVC的設計者將三種不一樣的職責分別抽象,運用模版方法設計模式分別固定在三個類層次中。其中HttpServletBean完成的是<init-param>配置元素的依賴注入,FrameworkServlet完成的是容器上下文的創建,DispatcherServlet完成的是SpringMVC具體編程元素的初始化策略。

相關文章
相關標籤/搜索