在一個工程中如果想要使用 SpringMVC的話,只需要兩個步驟
- 在web.xml中配置一個DispatcherServlet。需要配置一個org.springframework.web.servlet.DispatcherServlet的servlet。
- 定義一個dispatcherServlet-servlet.xml配置文件。在這個配置文件里面我們只需要擴展一個路徑映射關系,定義一個視圖解析器,再定義一個業務邏輯的處理流程規則。
這樣就可以搞定一個最基本的Spring MVC的應用了。
對於spring MVC框架中,有三個組件是用戶必須定義和擴展的:
- 定義URL映射規則:handlerMapping
- 實現業務邏輯的handler實例對象:handlerAdapter
- 渲染模版資源: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件事是如下。
- initMultipartResolver:初始化MultipartResolver,用於處理文件上傳服務,如果有文件上傳,那么會將當前的HttpServletRequest包裝成DefaultMultipartHttp ServletRequest,並且將每個上傳的內容封裝成CommonsMultipartFile對象。
- initLocaleResolver:用於處理應用的國際化問題,通過解析請求的Locale和設置響應的Locale來控制應用中的字符編碼問題。
- initThemeResolver:用於定義一個主題,例如,可以根據用戶的喜好來設置用戶訪問的頁面的樣式,可以將這個樣式作為一個Theme Name保存,用於請求的Cookie中或者保存在服務端的Session中,以后每次請求根據這個Theme Name來返回特定的內容。
- initHandlerMappings:用於定義用戶設置的請求映射關系,例如,前面示例中的SimpleUrlHandlerMapping把用於用戶請求的URL映射成一個個Handler實例。HandlerMapping必須定義,如果沒有定義,將獲取DispatcherServlet.properties文件中默認的兩個HandlerMapping,分別是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。
- initHandlerAdapters:用於根據Handler的類型定義不同的處理規則,例如,定義SimpleControllerHandlerAdapter處理所有Controller的實例對象,在HandlerMapping中將URL映射成一個Controller實例,那么Spring MVC在解析時SimpleController HandlerAdapter就會調用這個Controller實例。同樣HandlerAdapters也必須定義,如果沒有定義,將獲取DispatcherServlet.properties文件中默認的四個Handler Adapters,分別是HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、ThrowawayControllerHandlerAdapter和AnnotationMethodHandlerAdapter。
- initHandlerExceptionResolvers:當Handler處理出錯時,通過這個Handler來統一做處理,默認的實現類是SimpleMappingExceptionResolver,將錯誤日志記錄在log文件中,並且轉到默認的錯誤頁面。
- initRequestToViewNameTranslator:將指定的ViewName按照定義的RequestToVie wNameTranslator替換成想要的格式,如加上前綴或者后綴等。
- 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實現類。
- SimpleServletHandlerAdapter:可以繼承HttpRequestHandler接口,所有的Handler可以實現其void handleRequest(HttpServletRequest request, HttpServletResponse response)方法,這個方法沒有返回值。
- SimpleControllerHandlerAdapter:可以繼承Controller接口,所有的Handler可以實現其public ModelAndView handle(HttpServletRequest request, HttpServletRes ponse response, Object handler)方法,該方法會返回ModelAndView對象,用於后續的模板渲染。
- 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頁面。
總的時序圖