【Java EE】Spring MVC的初始化

Spring MVC的初始化


Spring Web MVC是Spring提供給Web應用的框架設計,實際上也是一個設計理念。對於Spring MVC,它的流程和各個組件的應用和改造Spring MVC的根本。

1. MVC設計概述

MVC設計不僅限於Java Web應用,還包括許多應用,比如前端、PHP、.NET等語言。之所以這麼做的根本原因在於解耦各個模塊。

早期的MVC模型多了一個Servlet組件,首先是用戶的請求到達Servlet,Servlet組件主要作爲控制器,這樣Servlet就接受了這個請求,可以通過它調用java Bean,來讀寫數據庫的數據,然後將結果放到JSP中,這樣就可以獲取數據並展現給用戶了。這樣的模式稱爲MVC模式,而Servlet扮演控制器(Controller)的功能,Java Bean則是一個專門操作數據庫組件的模型層(Model)。JSP主要是展示給用戶看的,所以是一個視圖(View)的功能。

1.1 Spring MVC的架構

對於持久層而言,隨着軟件發展,遷移數據庫的可能性很小,所以在大部分情況下都用不到Hibernate的HQL來滿足移植數據庫的要求。與此同時,性能對互聯網更爲重要,不可優化SQL、不夠靈活成了Hibernate難以治癒的傷痛,這樣MyBatis就崛起了。無論是Hibernate還是MyBatis都沒處理好數據庫事務的編程,同時隨着各種NoSQL的強勢崛起,使得Java Web應用不僅能夠在數據庫獲取數據,也可以從NoSQL中獲取數據,這些已經不是持久層框架能夠處理的了,而Spring MVC給出了方案,如圖所示:

 

圖中展示了傳統的模型層被拆分爲業務層(Service)和數據訪問層(DAO,Data Access Object)在Service下可以通過Spring的聲明式事務操作數據訪問層,而在業務層上還允許我們訪問NoSQL,這樣就能夠滿足現今異軍崛起的NoSQL的使用了,它的使用將大大提高互聯網系統的性能。對於Spring MVC而言,其最大的特色是結構鬆散,比如幾乎可以在Spring MVC中使用各類視圖,包括JSON、JSP、XML、PDF等,所以它能夠滿足手機端、頁面端和平板電腦端的各類請求,這就是現在它如此流行的原因。

1.2 Spring MVC組件與流程

Spring MVC的核心在於其流程,這是使用Spring MVC框架的基礎,Spring MVC是一種基於Servlet的技術,它提供了核心控制器DispatcherServlet和相關的組件,並制定了鬆散的結構,以適合各種靈活的需要。首先給出其組件和流程圖,如圖所示:

圖中的數字給出了Spring MVC的服務流程及其各個組件運行的順序,這是Spring MVC的核心

首先,Spring MVC框架是圍繞着DispatcherServlet而工作的,所以這個類是其最爲重要的類。從它的名字來看,它是一個Servlet,它可以攔截HTTP發送過來的請求,在Servlet初始化(調用init方法)時,Spring MVC會根據配置,獲取配置信息,從而得到統一資源標識符(URI,Uniform Resource Identifier)和處理器(Handler)之間的映射關係(HandlerMapping),爲了更加靈活和增強功能,Spring MVC還會給處理器加入攔截器,所以還可以在處理器執行前後加入自己的代碼,這樣就構成了一個處理器的執行鏈(HandlerExecutionChain),並且根據上下文初始化視圖解析器等內容,當處理器返回的時候就可以通過視圖解析器定位視圖,然後將數據模型渲染到視圖中,用來響應用戶的請求了。
 

當一個請求到來時,DispatcherServlet首先通過請求和事先解析好的HandlerMapping配置,找到對應的處理器(Handler),這樣就準備開始運行處理器和攔截器組成的執行鏈,而運行處理器需要有一個對應的環境,這樣它就有了一個處理器的適配器(HandlerAdapter),通過這個適配器就能運行對應的處理器及其攔截器,這裏的處理器包含了控制器的內容和其他增強的功能,在處理器返回模型和視圖給DispacherServlet後,DispacherServlet就會把對應的視圖信息傳遞給視圖解析器(ViewResolver)。注意,這一步取決於是否使用邏輯視圖,如果是邏輯視圖,那麼視圖解析器就會解析它,然後把模型渲染到視圖中去,最後響應用戶的請求;如果不是邏輯視圖,則不會進行處理,而是直接通過視圖渲染數據模型。這就是一個SpringMVC完整的流程,它是一個鬆散的結構,所以可以滿足各類請求的需要,爲此它也實現了大部分的請求所需的類庫,擁有較爲豐富的類庫供我們使用,所以流程中的大部分組件並不需要我們去實現,只是我們應該知道整個流程,熟悉它們的使用就可以構建出強大的互聯網系統了。

1.3 Spring MVC入門實例

首先介紹以XML配置的方式學習Spring MVC,這裏首先需要配置Web工程的web.xml文件,代碼(web.xml)如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
    <!-- 配置Spring IoC配置文件路徑 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

    <!-- 配置ContextLoaderListener用以初始化Spring IoC容器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 配置DispatcherServlet -->
    <servlet>
        <!-- 注意:Spring MVC 框架會根據 servlet-name 配置,找到/WEB-INF/dispatcher-servlet.xml作爲配置文件載入Web工程中 -->
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 使得Dispatcher在服務器啓動的時候就初始化 -->
        <load-on-startup>2</load-on-startup>
    </servlet>

    <!-- Servlet攔截配置 -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

其中的:

  • 系統變量contextConfigLocation的配置,它會告訴Spring MVC其Spring IoC的配置文件在哪裏,這樣Spring就會找到這些配置文件去加載它們。如果是多個配置文件,可以使用逗號將它們分隔開來,並且它還能支持正則表達式匹配,進行模糊匹配,這樣就更加靈活了,其默認值爲/WEB-INF/applicationContext.xml。
  • ContextLoaderListener實現了接口ServletContextListener,通過Java Web容器的學習,知道ServletContextListener的作用是可以在整個Web工程前後加入自定義代碼,所以可以在Web工程初始化之前,它先完成對Spring IoC容器的初始化,也可以在Web工程關閉之時完成Spring IoC容器的資源進行釋放。
  • 配置DispatcherServlet,首先是配置了servlet-name爲dispatcher,這意味着需要一個/WEB-INF/dispatcher-servlet.xml文件(注意,servlet-name和文件名的對應關係)與之對應,並且我們配置了在服務器啓動期間就初始化它。
  • 配置DispatcherServlet攔截以後綴“do”結束的請求,這樣所有的以後綴“do”結尾的請求都會被它攔截。

在最簡單的入門例子中暫時不配置applicationContext.xml的任何內容,所以其代碼也是空的,代碼(applicationContext.xml)如下:

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans         
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
</beans>

這樣Spring IoC容器就沒有裝載自己的類,根據之前的論述,它還會加載一個/WEB-INF/dispatcher-servlet.xml文件,它是與Spring MVC配置相關的內容,所以它會有一定的內容,代碼(dispatcher-servlet.xml)如下:

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 使用註解驅動 -->
    <mvc:annotation-driven/>
    <!-- 定義掃描裝載的包 -->
    <context:component-scan base-package="com.ssm.chapter14.*"/>

    <!-- 定義視圖解析器 -->
    <!-- 找到Web工程/WEB-INF/JSP文件夾,且文件結尾爲jsp的文件作爲映射 -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>

    <!-- 如果有配置數據庫事務,需要開啓註解事務的,需要開啓這段代碼 -->
    <!--  <tx:annotation-driven transaction-manager="transactionManager" />      -->

</beans>

配置說明如下:

  • <mvc:annotation-driven />表示使用註解驅動Spring MVC.
  • 定義一個掃描的,用它來掃描對應的包,用以加載對應的控制器和其他的一些組件。
  • 定義視圖解析器,解析器中定義了前綴和後綴,這樣視圖就知道去Web工程的/WEB-INF/JSP文件作爲視圖響應用戶請求。

這樣,一個簡單的Controller如下:

package com.ssm.chapter14.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

//註解@Controller表示它是一個控制器
@Controller("myController")
//表明當請求的URI在/my下的時候纔有該控制器響應
@RequestMapping("/my")
public class MyController {

    //表明URI是/index的時候該方法才請求
    @RequestMapping("/index")
    public ModelAndView index() {
        //模型和視圖
        ModelAndView mv = new ModelAndView();
        //視圖邏輯名稱爲index
        mv.setViewName("index");
        //返回模型和視圖
        return mv;
    }
}

 首先註解@Controller是一個控制器。Spring MVC掃描的時候就會把它作爲控制器加載進來。然後,註解@RequestMapping指定了對應的請求的URI,Spring MVC在初始化的時候就會將這些信息解析,存放起來,於是便有了HandlerMapping,當發生請求時,Spring MVC就會去使用這些信息去找到對應的控制器提供服務。

方法定義返回ModelAndView,在方法中把視圖名稱定義爲index,在配置文件中所配置的視圖解析器,由於配置前綴/WEB-INF/jsp/,後綴.jsp,加上返回的視圖邏輯名稱爲in-dex,所以它會選擇使用/WEB-INF/jsp/index.jsp作爲最後的響應,於是要開發/WEB-INF/jsp/index.jsp文件,如代碼(index.jsp)所示:

<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Welcome to Spring Web MVC project</title>
</head>

<body><h1>Hello, Spring MVC</h1></body>

</html>

啓動服務器比如Tomcat,輸入對應的URL,就可以看到對應的響應了。

由於Spring MVC組件和流程的重要性,這裏展示了上述案例的流程圖:

當Spring MVC啓動的時候就會去解析MyController的註解,然後生成對應的URI和請求的映射關係,並註冊對應的方法。當請求來到的時候,首先根據URI找到對應的HandleMapping,然後組織爲一個執行鏈,通過請求類型找到RequestMappingHandlerAdapter,它的實例是在DispatcherServlet初始化的時候進行創建的。然後通過它去執行HandlerExecutionChain的內容,最終在MyController的方法中將index的視圖返回DispatcherServlet。由於配置的視圖解析器(InternalResourceViewResolver)前綴爲/WEB-INF/jsp/,後綴爲.jsp,視圖名爲index,所以最終它會找到/WEB-INF/jsp/index.jsp文件作爲視圖,響應最終的請求,這樣整個Spring MVC的流程就走通了。

2. Spring MVC初始化

整個Spring MVC的初始化,配置了DispatcherServlet和ContextLoaderListener,那麼它們是如何初始化Spring IoC容器上下文和映射請求上下文的呢?所以這裏的初始化會涉及兩個上下文的初始化,只是映射請求上下文是基於Spring IoC上下文擴展出來,以適應Java Web工程的需要。

2.1 初始化Spring IoC上下文

Java Web容器爲其生命週期中提供了ServletContextListener接口,這個接口可以在Web容器初始化和結束期中執行一定的邏輯,換句話說,通過實現它可以使得在DispatcherServlet初始化前就可以完成Spring IoC容器的初始化,也可以在結束期完成對Spring IoC容器的銷燬,只要實現ServletContextListener接口的方法就可以了。Spring MVC交給了類ContextLoaderListener,其源碼如下:

package org.springframework.web.context;
/************import*******************/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
......
    /**
    * Initialize the root web application context
    */
    @Override
    public void contextInitialized(ServletContext event){
        //初始化Spring IoC容器,使用的是滿足ApplicationContext接口的Spring Web IoC容器
        initWebApplicationContext(event.getServletContext());
    }
    /**
    * Close the root web application context
    */
    @Override
    public void contextDestoryed(ServletContextEvent event){
        //關閉Web IoC容器
        closeWebApplicationContext(event.getServletContext());
        //清除相關參數
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

2.2 初始化映射請求上下文

映射請求上下文是通過DispatcherServlet初始化的,和普通的Servlet一樣,可以根據自己的需求配置它在啓動時初始化,或者等待用戶第一次請求的時候進行初始化。注意,也許在Web工程中並沒有註冊ContextLoaderListener,這個時候DispatcherServlet就會在其初始化的時候進行對Spring IoC容器的初始化。這樣也許會有一個疑問:選擇在什麼時候初始化DispatcherServle?

首先,初始化一個Spring IoC容器是一個耗時的操作,所以這個工作不應該放到用戶請求上,沒有必要讓一個用戶陷入長期等待中,因此大部分場景下,都應該讓DispatcherServle在服務器啓動期間就完成Spring IoC容器的初始化,我們可以在Web容器剛啓動的時候,也可以在Web容器載入DispatcherServle的時候進行初始化。建議是在Web容器剛開始的時候對其初始化,因爲在整個Web的初始化中,不只是DispatcherServle需要使用到Spring IoC的資源,其他的組件可能也需要。在最開始就初始化可以讓Web中的各個組件共享資源。當然你可以指定Web容器中組件初始化的順序,讓DispatcherServle第一個初始化,來解決這個問題,但是這就加大了配置的複雜度,因此大部分的情況下都建議使用ContextLoaderListener進行初始化

DispatcherServlet的設計如下圖所示;

從圖中可以看出,DispatcherServlet的父類是FrameworkServlet,而FrameworkServlet的父類則是HttpServletBean。HttpServletBean繼承了Web容器所提供的HttpServlet,所以它是一個可以載入Web容器中的Servlet

Web容器對於Servlet的初始化,首先是調用其init方法,對於DispatcherServlet也是如此,這個方法位於它的父類HttpServletBean裏,代碼如下:

	/**
	 * 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 {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		try {
			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);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

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

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

 在類HttpServletBean中可以看到initServletBean方法,在FrameworkServlet中也可以看到它,我們知道子類的方法會覆蓋掉父類的方法,所以着重看FrameworkServlet中的initServletBean方法。代碼如下:

	/**
	 * 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 {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}
	/**
	 * Initialize and publish the WebApplicationContext for this servlet.
	 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
	 * of the context. Can be overridden in subclasses.
	 * @return the WebApplicationContext instance
	 * @see #FrameworkServlet(WebApplicationContext)
	 * @see #setContextClass
	 * @see #setContextConfigLocation
	 */
	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;
	}

 當IoC容器沒有對應的初始化的時候,DispatcherServlet會嘗試去初始化它,最後調度onRefresh方法,那麼它就是DispatcherServlet一個十分值得關注的方法。因爲它將初始化Spring MVC的各個組件,而onRefresh這個方法就在DispatcherServlet中,代碼如下:

	/**
	 * This implementation calls {@link #initStrategies}.
	 */
	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
                // 初始化文件的解析
		initMultipartResolver(context);
                // 本地解析化
		initLocaleResolver(context);
                // 主題解析
		initThemeResolver(context);
                // 處理器映射
		initHandlerMappings(context);
                // 處理器的適配器
		initHandlerAdapters(context);
                // Handler的異常解析器
		initHandlerExceptionResolvers(context);
                // 當處理器沒有返回邏輯視圖名稱等相關信息時,自動將請求URL映射爲邏輯視圖名
		initRequestToViewNameTranslator(context);
                // 視圖邏輯名稱轉化器,即允許返回邏輯視圖名稱,然後它會找到真實的視圖
		initViewResolvers(context);
                // 這是一個關注Flash開發的Map管理器,不再介紹
		initFlashMapManager(context);
	}

 Spring MVC的核心組件如下:

  • MultipartResolver:文件解析器,用於支持服務器的文件上傳
  • LocaleResolver:國際化解析器,可以提供國際化的功能。
  • ThemeResolver:主題解析器,類似於軟件皮膚的轉換功能。
  • HandlerMapping:Spring MVC中十分重要的內容,它會包裝用戶提供一個控制器的方法和對它的一些攔截器,通過調用它就能夠運行控制器。
  • handlerAdapter:處理器適配器,因爲處理器會在不同的上下文中運行,所以Spring MVC會先找到合適的適配器,然後運行處理器服務方法。比如對於控制器的SimpleControllerHandlerAdapter、對於普通請求的HttpRequestHandlerAdapter等。
  • HandlerExceptionResolver:處理器異常解析器,處理器有可能產生異常,如果產生異常,則可以通過異常解析器來處理它,比如出現異常後,可以轉到指定的異常頁面,這樣使得用戶的UI體驗得到了改善。
  • RequestToViewNameTranslator:視圖邏輯名稱轉換器,有時候在控制器中返回一個視圖的名稱,通過它可以找到實際的視圖。當處理器沒有返回邏輯視圖名等相關信息時,自動將請求URL映射爲邏輯視圖名。
  • ViewResolver:視圖解析器,當控制器返回後,通過視圖解析器會把邏輯視圖名稱進行解析,然後定位實際視圖。

事實上,對這些組件DispatcherServlet會根據其配置文件DispatcherServlet.properties進行初始化,文件內容如下:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

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

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

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

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

 由此可見,在啓動期間DispatcherServlet會加載這些配置的組件進行初始化。

2.3 使用註解配置方式初始化

由於在Servlet3.0之後的規範允許取消web.xml配置,只使用註解方式便可以了,所以在Spring3.1之後的版本也提供了註解方式的配置。使用註解方式很簡單,首先繼承一個名字比較長的類AbstractAnnotationConfigDispatcherServletInitializer,然後實現它所定義的方法。它所定義的內容就不是太複雜,甚至是比較簡單的,讓我們通過一個類去繼承它,如下代碼所示,它實現的是入門實例的功能。

package com.ssm.chapter14.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //Spring IoC容器配置
    //getRootConfigClasses獲取Spring IoC容器的Java配置類,用以裝載各類Spring Bean。
    @Override
    protected Class<?>[] getRootConfigClasses() {
        //可以返回Spring的Java配置文件數組
        return new Class<?>[]{};
    }

    //DispatcherServlet的URI映射關係配置
    //getServletConfigClasses獲取各類Spring MVC的URI和控制器的配置關係類,用以生成Web請求的上下文。
    @Override
    protected Class<?>[] getServletConfigClasses() {
        //可以返回Spring的Java配置文件數組
        return new Class<?>[]{WebConfig.class};
    }

    //DispatcherServlet攔截內容
    // getServletMappings定義DispatcherServlet攔截的請求。
    @Override
    protected String[] getServletMappings() {
        return new String[]{"*.do"};
    }

}

 這裏使用它來代替XML的配置,爲什麼只需要繼承類AbstractAnnotationConfigDispatcherServletInitializer,Spring MVC就會去加載這個Java文件?Servlet 3.0之後的版本允許動態加載Servlet,只是按照規範需要實現ServletContainerIntializer接口而已。於是Spring MVC框架在自己的包內實現了一個類,它就是SpringServletContainerInitializer,它實現了ServletContainerInitializer接口,這樣就能夠通過它去加載開發者提供的MyWebAppInitializer了,SpringServletContainerInitializer源碼如下:

// 定義初始化器的類型,只要實現WebApplicationInitializer接口則爲初始化器
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        // 初始化器, 允許多個
        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
        // webAppInitializerClasses 就是servlet3.0規範中爲我們收集的 WebApplicationInitializer 接口的實現類的class
        // 從webAppInitializerClasses中篩選並實例化出合格的相應的類
        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }
        //找不到初始化器
        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        // 這行代碼說明我們在實現WebApplicationInitializer可以通過繼承Ordered, PriorityOrdered來自定義執行順序
        AnnotationAwareOrderComparator.sort(initializers);
        servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);

        // 迭代每個initializer實現的方法
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

 這段代碼中可以看到只要實現了WebApplicationInitializer接口的onStartup方法,Spring MVC就會把類當作一個初始化器加載進來。

MyWebAppInitializer也實現了WebApplicationInitializer接口。ContextLoader和DispatcherServlet的初始化器都是抽象類,通過它們就能初始化Spring IoC上下文和映射關係上下文,這就是只要繼承AbstractAnnotationConfigDispatcherServle-tInitializer類就完成了DispatcherServlet映射關係和SpringIoC容器的初始化工作的原因。 

這樣關注的焦點就再次回到MyWebAppInitializer配置類上,它有3種方法:

  • getRootConfigClasses獲取Spring IoC容器的Java配置類,用以裝載各類Spring Bean。
  • getServletConfigClasses獲取各類Spring MVC的URI和控制器的配置關係類,用以生成Web請求的上下文。
  • getServletMappings定義DispatcherServlet攔截的請求。 

如果getRootConfigClasses方法返回爲空,就不加載自定義的Bean到Spring IoC容器中,而getServletConfigClasses加載了WebConfig,則它就是一個URI和控制器的映射關係類。由此產生Web請求的上下文。WebConfig的內容如下所示:

package com.ssm.chapter14.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
//定義掃描的包,加載控制器
@ComponentScan("com.ssm.chapter14.*")
//啓用Spring Web MVC
@EnableWebMvc
public class WebConfig {

    /***
     * 創建視圖解析器
     *  @return 視圖解析器
     */
    @Bean(name = "viewResolver")
    public ViewResolver initViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

}

這段代碼和Spring IoC使用Java的配置也是一樣的,只是多了一個註解@EnableWebMvc,它代表啓動Spring MVC框架的配置。和入門實例同樣也定義了視圖解析器,並且設置了它的前綴和後綴,這樣就能獲取由控制器返回的視圖邏輯名,進而找到對應的JSP文件。
如果還是使用入門實例進行測試,此時可以把web.xml和所有關於Spring IoC容器所需的XML文件都刪掉,只使用上面兩個Java文件作爲配置便可以了,然後重啓服務器。 

 

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