SpringMVC 的工作機制


在一個工程中如果想要使用 SpringMVC的話,只需要兩個步驟

  1. 在web.xml中配置一個DispatcherServlet。需要配置一個org.springframework.web.servlet.DispatcherServlet的servlet。
  2. 定義一個dispatcherServlet-servlet.xml配置文件。在這個配置文件里面我們只需要擴展一個路徑映射關系,定義一個視圖解析器,再定義一個業務邏輯的處理流程規則。

這樣就可以搞定一個最基本的Spring MVC的應用了。

對於spring MVC框架中,有三個組件是用戶必須定義和擴展的:

  1.     定義URL映射規則:handlerMapping
  2.     實現業務邏輯的handler實例對象:handlerAdapter
  3.     渲染模版資源:ViewResolver

下面是傳統的配置示例:

<!-- web.xml -->
<servlet>
 <servlet-name>countries</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <load-on-startup>2</load-on-startup>
</servlet> 
<servlet-mapping>
 <servlet-name>countries</servlet-name>
 <url-pattern>*.htm</url-pattern>
</servlet-mapping

<!-- dispatcherServlet-servlet.xml -->
<!--  派遣器收到請求后,把不同的頁面派遣到不同的控制器進行處理
Spring有兩種MAPPING的機制,默認為BeanNameUrlHandlerMapping,在DispatcherServlet.properties文件中配置
我們這里采用的是SimpleUrlHandlerMapping. -->
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
 <property name="mappings">
  <props>
   <prop key="/home.htm">countriesController</prop>
  </props>
 </property>
<!-- 在控制器處理前,我們可以將其攔截,進行一些特殊或通用處理;攔截器可以選用Spring已經實現的或我們自己實現的  -->
 <property name="interceptors">
  <list>
   <ref local="localeChangeInterceptor"/>
   <ref local="themeChangeInterceptor"/>
   <ref local="copyInterceptor"/>
  </list>
 </property>
</bean>

<bean id="countriesController" class="cn.jephon.demo.CountriesController" />

下面是用freemaker引擎的配置示例:

<!--web.xml 配置 spring 的DispatcherServlet-->
<servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!--給出框架配置文件所在的路徑-->
            <param-value>classpath:conf/freemarker-servlet.xml</param-value>
        </init-param>
</servlet>


<!--freemarker-servlet在conf/freemarker-servlet.xml文件中的參數配置如下-->
<!-- freemark --> 
    <bean id="freemarkerConfig"
        class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <property name="templateLoaderPath" value="/WEB-INF/ftl/" />
        <property name="defaultEncoding" value="UTF-8" />
    </bean>

    <bean id="viewResolver"
    class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
        <property name="cache" value="true" />
        <property name="prefix" value="" />
        <property name="suffix" value=".ftl" />
        <property name="exposeSpringMacroHelpers" value="true" />
        <property name="exposeRequestAttributes" value="true" />
        <property name="exposeSessionAttributes" value="true" />
        <property name="requestContextAttribute" value="request" />
        <property name="contentType" value="text/html; charset=utf-8" />
    </bean>

SpringMVC的入口是DispatchServlet,它的工作大致可以分為兩個部分,一個是初始化,另外一個是請求處理。

DispatcherServlet初始化

//DispatcherServlet的初始化方法
@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, this.environment));
     //只有HttpServletBean覆蓋了此方法,
     //所以由HttpServletBean讀取配置在ServletContext中的bean屬性參數,即web.xml中的context-param,然后創建和設置這些bean屬性
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }
//由FrameServlet執行此方法,調用FrameworkServlet的createWebApplicationContext方法,初始化spring容器。
  //調用FrameworkServlet的onRefresh方法完成配置文件的加載,配置文件的加載是先查找servlet的init-param參數中設置的路徑,如果沒有,就會根據namespace+Servlet的名稱來查找XML文件。 
  //Spring容器加載的時候會調用DispatcherServlet的initStrategies方法來初始化springMVC框架所需要的八個組件,
  //這八個組件對應的八個bean對應都保存在DispatcherServlet類中。
  initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } } protected void initStrategies(ApplicationContext context) { //初始化MultipartResolver,主要是處理文件上傳服務。 initMultipartResolver(context); //用於處理應用的國際化問題 initLocaleResolver(context); //用於定義一個主題 initThemeResolver(context); //用於定義用戶設置的請求映射關系 initHandlerMappings(context); //用於根據Handler的類型定義不同的處理規則 initHandlerAdapters(context); //當Handler處理錯誤的時候,通過這個handler來做統一的處理 initHandlerExceptionResolvers(context); //將指定的ViewName按照定義的RequestToViewNameTranslator替換成想要的格式。 initRequestToViewNameTranslator(context); //用於將view解析成頁面 initViewResolvers(context); //用於映射flash管理的。 initFlashMapManager(context); }

 

要做的8件事是如下。

  1. initMultipartResolver:初始化MultipartResolver,用於處理文件上傳服務,如果有文件上傳,那么會將當前的HttpServletRequest包裝成DefaultMultipartHttp ServletRequest,並且將每個上傳的內容封裝成CommonsMultipartFile對象。
  2. initLocaleResolver:用於處理應用的國際化問題,通過解析請求的Locale和設置響應的Locale來控制應用中的字符編碼問題。
  3. initThemeResolver:用於定義一個主題,例如,可以根據用戶的喜好來設置用戶訪問的頁面的樣式,可以將這個樣式作為一個Theme Name保存,用於請求的Cookie中或者保存在服務端的Session中,以后每次請求根據這個Theme Name來返回特定的內容。
  4. initHandlerMappings:用於定義用戶設置的請求映射關系,例如,前面示例中的SimpleUrlHandlerMapping把用於用戶請求的URL映射成一個個Handler實例。HandlerMapping必須定義,如果沒有定義,將獲取DispatcherServlet.properties文件中默認的兩個HandlerMapping,分別是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。
  5. initHandlerAdapters:用於根據Handler的類型定義不同的處理規則,例如,定義SimpleControllerHandlerAdapter處理所有Controller的實例對象,在HandlerMapping中將URL映射成一個Controller實例,那么Spring MVC在解析時SimpleController HandlerAdapter就會調用這個Controller實例。同樣HandlerAdapters也必須定義,如果沒有定義,將獲取DispatcherServlet.properties文件中默認的四個Handler Adapters,分別是HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、ThrowawayControllerHandlerAdapter和AnnotationMethodHandlerAdapter。
  6. initHandlerExceptionResolvers:當Handler處理出錯時,通過這個Handler來統一做處理,默認的實現類是SimpleMappingExceptionResolver,將錯誤日志記錄在log文件中,並且轉到默認的錯誤頁面。
  7. initRequestToViewNameTranslator:將指定的ViewName按照定義的RequestToVie wNameTranslator替換成想要的格式,如加上前綴或者后綴等。
  8. initViewResolvers:用於將View解析成頁面,在ViewResolvers中可以設置多個解析策略,如可以根據JSP來解析,或者按照Velocity模板解析。默認的解析策略是InternalResourceViewResolver,按照JSP頁面來解析。

請求處理

 

上面的是springMVC的工作原理圖:

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

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

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

5、Handler對數據處理完成以后將返回一個ModelAndView()對象給DispatcherServlet。

6、Handler返回的ModelAndView()只是一個邏輯視圖並不是一個正式的視圖,DispatcherSevlet通過ViewResolver將邏輯視圖轉化為真正的視圖View。

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

Control

HandlerMapping初始化:

將URL與Handler的對應關系保存在HandlerMapping集合中,並將所有的interceptors對象保存在adaptedInterceptors數組中,等請求到來的時候執行所有的adaptedIntercoptors數組中的interceptor對象。所有的interceptor必須實現HandlerInterceptor接口。

HandlerAdapter初始化:

Spring MVC中提供了如下三個典型的簡單HandlerAdapter實現類。

  1. SimpleServletHandlerAdapter:可以繼承HttpRequestHandler接口,所有的Handler可以實現其void handleRequest(HttpServletRequest request, HttpServletResponse response)方法,這個方法沒有返回值。
  2. SimpleControllerHandlerAdapter:可以繼承Controller接口,所有的Handler可以實現其public ModelAndView handle(HttpServletRequest request, HttpServletRes ponse response, Object handler)方法,該方法會返回ModelAndView對象,用於后續的模板渲染。
  3. SimpleServletHandlerAdapter:可以直接繼承Servlet接口,可以將一個Servlet作為一個Handler來處理這個請求。

Spring MVC的HandlerAdapter機制可讓Handler的實現更加靈活,不需要和其他框架那樣只能和某個Handler接口綁定起來。

對於handlerAdapter的初始化沒有什么特別之處,只是簡單的創建一個handlerAdapter對象,將這個對象保存在DispatcherServlet的HandlerAdapters集合中。當Spring MVC將某個URL對應到某個Handler時候,在handlerAdapters集合中查詢那個handlerAdapter對象supports這個Handler,handlerAdapter對象將會被返回,然后調用這個handlerAdapter接口對應的方法。如果這個handlerAdapter對象是SimpleControllerHandlerAdapter,將調用Controller接口的public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)方法。

Control的調用邏輯:

整個Spring MVC的調用是從DispatcherServlet的doService方法開始的,在doService方法中會將ApplicationContext、localeResolver、themeResolver等對象添加到request中便於在后面使用,接着就調用doDispatch方法,這個方法是主要的處理用戶請求的地方。 

對於control的處理關鍵就是:DispatcherServlet的handlerMappings集合中根據請求的URL匹配每一個handlerMapping對象中的某個handler,匹配成功之后將會返回這個handler的處理連接handlerExecutionChain對象。而這個handlerExecutionChain對象中將會包含用戶自定義的多個handlerInterceptor對象

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

對於handlerInterceptor接口中定義的三個方法中,preHandler和postHandler分別在handler的執行前和執行后執行,afterCompletion在view渲染完成、在DispatcherServlet返回之前執行。當preHandler返回false時,當前的請求將在執行完afterCompletion后直接返回,handler也將不會執行。

在類HandlerExecutionChain中的getHandler()方法是返回object對象的;這里的handler是沒有類型的,handler的類型是由handlerAdapter決定的。dispatcherServlet會根據handlerAdapters集合中第一個支持該handler對象的HandlerAdapter對象。接下來去執行handler對象的相應方法了,如果該handler對象的相應方法返回一個ModelAndView對象(SampleControllerHandlerAdapter)接下來就是去執行View渲染了。

Model

如果handler返回了ModelAndView對象,那么說明Handler需要傳一個Model實例給view去渲染模版。除了渲染頁面需要model實例,在業務邏輯層通常也有Model實例。

ModelAndView對象是連接業務邏輯層與view展示層的橋梁,對spring MVC來說它也是連接Handler與view的橋梁。ModelAndView對象顧名思義會持有一個ModelMap對象和一個View對象或者View的名稱(譬如hello.vm模板名字)。ModelMap對象就是執行模版渲染時候所需要的變量對應的實例,如jsp的通過request.getAttribute(String)獲取的JSTL標簽名對應的對象。velocity中context.get(String)獲取$foo對應的變量實例。

public class ModelAndView {
 
/** View instance or view name String */
    private Object view;
 
    /** Model Map */
    private ModelMap model;
 
    /** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */
    private boolean cleared = false;
 
.....
 
}

ModelMap其實也是一個Map,Handler中將模版中需要的對象存在這個Map中,然后傳遞到view對應的ViewResolver中。

不同的ViewResolver會對這個Map中的對象有不同的處理方式;

  • velocity中將這個Map保存到VelocityContext中。
  • freemarker模板引擎來說將ModelMap包裝成freemarker.template. TemplateHash Model
  • JSP中將每一個ModelMap中的元素分別設置到request.setAttribute(modelName,modelValue);

View

在spring MVC中,view模塊需要兩個組件來支持:RequestToViewNameTranslator和ViewResolver

public interface RequestToViewNameTranslator {
    String getViewName(HttpServletRequest request) throws Exception;
}
public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
 }

RequestToViewNameTranslator:主要支持用戶自定義對viewName的解析,如將請求的ViewName加上前綴或者后綴,或者替換成特定的字符串等。

ViewResolver:主要是根據用戶請求的viewName創建適合的模版引擎來渲染最終的頁面,ViewResolver會根據viewName創建一個view對象,調用view對象的Void render方法渲染出頁面。

public interface View {
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

UrlBasedViewResolver類實現了AbstractCachingViewResolver抽象類,通過設置ViewClass來創建View對象。如果使用FreeMarkerViewResolver類,會將ViewClass設置為FreeMarkerView.class;使用VelocityViewResolver類,會將ViewClass設置為VelocityView.class。InternalResourceViewResolver類可以通過注入的方式設置ViewClass屬性來初始化自定義的View對象。由於AbstractCachingViewResolver抽象類也繼承了WebApplicationObjectSupport,所以所有的AbstractCachingViewResolver子類可以通過覆蓋initApplicationContext方法在Spring MVC框架啟動時完成初始化工作。如FreeMarkerViewResolver和VelocityViewResolver就是在啟動調用setViewClass方法時設置ViewClass屬性的。JSP的ViewResolver對應的是InternalResourceViewResolver類,當調用resolveViewName方法時會調用createView方法,將ViewClass屬性對應的InternalResourceView類實例化。最后調用InternalResourceView的render方法渲染出JSP頁面。

 Spring MVC解析View的邏輯:

  • dispatcherServlet調用getDefaultViewName()方法;
  • 調用RequestToViewNameTranslator的getViewName方法;
  • 調用LocaleResolver接口的resolveLocale方法;
  • 調用ViewResolver接口的resolveViewName方法,返回view對象
  • 調用render方法渲染出頁面

JSP的ViewResolver對應的是InternalResourceViewResolver類,當調用resolveViewName方法時會調用createView方法,將ViewClass屬性對應的InternalResourceView類實例化。最后調用InternalResourceView的render方法渲染出JSP頁面。

總的時序圖

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM