Spring MVC框架算是當下比較流行的Java開源框架。但實話實說,做了幾年WEB項目,完全沒有SpringMVC實戰經驗,乃至在某些交流場合下被同行嚴重鄙視“奧特曼”了。“心塞”的同時,只好默默的打開IDE從HelloWorld開始。
初步認識
宏觀視野決定微觀實現的質量,首先對Spring MVC框架組件及其流程做一個簡單的認識。以下是從互聯網中某Spring MVC教材扣來一張介紹圖(懶得重復造輪子了):
從上圖可以看出,Spring MVC框架的核心組件有DispatcherServlet、HandlerMapping、HandlerAdapter、Handler、ModelAndView、Model、View以及ViewResolver。既然是核心組件,怎么也得結合組件源碼來探索個究竟吧:
DispatcherServlet
從名字可以看出,這就是一個Servlet實例,既然是Servlet,那當然是Srping MVC框架入口了,也是web.xml的一個Spring MVC配置項:
<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:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
其中springmvc為servlet的自定義命名名稱,其中Spring MVC配置文件也是默認名稱為[servletName]-servlet.xml。
從DispatcherServlet源碼看到到,DispatcherServlet的基礎結構是:
DispatcherServlet extend FrameworkServlet
FrameworkServlet extend HttpServletBean
HttpServletBean extend HttpServlet
初略的看了一下DispatcherServlet的干系源碼,主要做了兩大部分,其一是初始化WEB容器的上下文信息和一些Spring MVC策略容器(如HandlerMapping、HandlerAdapter等),在啟動WEB容器時可以通過控制台輸出看到Spring MVC的一些初始化操作:
……
信息: Starting Servlet Engine: Apache Tomcat/6.0.13
2016-4-12 10:51:54 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring FrameworkServlet 'springmvc'
2016-4-12 10:51:54 org.springframework.web.servlet.FrameworkServlet initServletBean
信息: FrameworkServlet 'springmvc': initialization started
2016-4-12 10:51:54 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing WebApplicationContext for namespace 'springmvc-servlet': startup date [Tue Apr 12 10:51:54 CST 2016]; root of context hierarchy
2016-4-12 10:51:54 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [springmvc-servlet.xml]
2016-4-12 10:51:55 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@c7f06: defining beans [helloWorldAnnotation,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0,/helloWorldController]; root of factory hierarchy
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldAnnotation] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldAnnotation.*] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldAnnotation/] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldController] onto handler [com.maventest.springmvc.HelloWorldController@85ce5a]
2016-4-12 10:51:55 org.springframework.web.servlet.FrameworkServlet initServletBean
信息: FrameworkServlet 'springmvc': initialization completed in 1222 ms
……
其二就是對MVC容器的流程控制,其主要流程控制方法是doDispatch,接下來結合源碼針對此方法的一些重要操作進行分析和學習:
//檢查請求是否是multipart(如文件上傳),如果是則通過MultipartResolver解析
processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; //獲取請求對應的mappedHandler
mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } //獲取請求對應的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //由適配器執行處理器(調用處理器相應功能處理方法)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //如果HandlerAdapter沒有對應的ModelAndView響應,怎通過上下文獲取默認對應的view,接着
applyDefaultViewName(request, mv); //看applyPostHandle得知,這是定義攔截器的處理方法
mappedHandler.applyPostHandle(processedRequest, response, mv); //解析視圖並進行視圖的渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
從doDispatch方法流程分析可以看出,跟以上Spring MVC框架流程圖的處理流程是一致的,整個DispatcherServlet組件就是Spring MVC的總流程控制器,再形象一點就如下圖所示:
HandlerMapping
察人先察色,HandlerMapping中文意思就是“處理映射”,作為一個強大的開源框架,命名自然不會亂來,通過名稱就大概知其所以。看看getHandle這個方法:
protected HandlerExecutionChain getHandler(HttpServletRequest request)…
先不看源碼,就大概可以猜個一二,這是通過request參數,獲取一個對應的的處理類,而這個HandlerExecutionChain就是這個返回的處理類。這個HandlerMapping已經在項目啟動的時候跟隨Servlet一同初始化了:
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
而getHandler方法可以通過request獲取請求的所有信息,包括請求方法、URL路徑等,就可以通過這個映射容器找出對應的處理類了。下面再看看這個HandlerExecutionChain響應類屬性:
private final Object handler; private HandlerInterceptor[] interceptors; private List<HandlerInterceptor> interceptorList; private int interceptorIndex = -1;
它包括了請求處理的所有攔截實例和核心處理handler實例,這都會在DispatcherServlet往下幾個步驟會使用到的,具體可以往上回看DispatcherServlet的處理流程。
HandlerAdapter
還是從名稱理解開始,HandlerAdapter中文意思就是處理對象適配器,按意思就是說Spring MVC有很多個Handler處理對象,這個處理器實際就是一個Handler代理。那么如果不自己定義Handler代理的話,那默認有多少個呢,那就可以看看DispatcherServlet.properties這個配置文件了:
Name:org.springframework.web.servlet.HandlerAdapter
Value:org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
哦,原來默認有HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter一共三個默認的Handler代理。那他們分別有什么用呢,看看我自己親自動手做過Spring MVC HelloWorld實例就很明白了,我通過兩種方式實現了兩個HelloWorld Handler,一個在配置文件配置的bean:
<bean name="/helloWorldController" class="com.maventest.springmvc.HelloWorldController"/>
public class HelloWorldController implements Controller{ public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { ModelAndView mv = new ModelAndView(); mv.addObject("message", "Hello World!,i am HelloWorldController."); mv.setViewName("hello"); return mv; } }
而另一個是通過注解實現的HelloWorld Handler:
@Controller public class HelloWorldAnnotation{ @RequestMapping(value="/helloWorldAnnotation") public String hello(ModelMap model){ model.addAttribute("message", "Hello, World!I am HelloWorldAnnotation."); return "hello"; } }
這兩種方式就是分別通過SimpleControllerHandlerAdapte和AnnotationMethodHandlerAdapter處理的,那這樣一說就很明白了。另外這三個個Handler代理都實現了HandlerAdapter接口,就是Spring MVC規定了Handler代理的規則,分別有以下定義方法:
public interface HandlerAdapter { //判斷處理適配器是不是支持該Handler
boolean supports(Object handler); //調用對應的Handler中適配到的方法,並返回一個ModelView
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; //這個暫時還沒看懂具體想干什么(不是重點,暫時放下)
long getLastModified(HttpServletRequest request, Object handler); }
其中判斷是否找到合適的Handler代理就靠這個supports方法的具體實現,如果適配成功,這個代理會替這個Handler實現業務路基處理。再簡單這三個代理的supports實現:
//HttpRequestHandlerAdapter @Override public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } //SimpleControllerHandlerAdapte @Override public boolean supports(Object handler) { return (handler instanceof Controller); } //AnnotationMethodHandlerAdapter @Override public boolean supports(Object handler) { return getMethodResolver(handler).hasHandlerMethods(); }
再來簡單分別說說以上三個代理對handle方法的實現:
HttpRequestHandlerAdapter和SimpleControllerHandlerAdapte都是直接調用handler的handleRequest方法,而AnnotationMethodHandlerAdapter稍微復雜一點,它是通過注釋和反射獲取相關自定義信息,進行匹配和封裝,具體可自行參考其源碼。
Handler
這就是自己實現的具體業務處理類了,上文提到很多,不用多說了。
ModelAndView
通過handler代理完成業務流程后返回一個ModelAndView對象,從名稱就大概可以知道這是一個裝載的數據模型(Model)和數據視圖的對象(View)。
Model
從源碼可以看出,model集成了LinkedHashMap<String,Object>類,這個model對象裝載了所有在Handler響應給頁面的數據。例如在我自己例子中的message數據:
model.addAttribute("message", "Hello, World!I am HelloWorldAnnotation.");
這些數據將會在頁面上通過JSTL獲取。
View
View接口表示一個響應給用戶的視圖,例如jsp文件,pdf文件,html文件等,該接口定義如下:
public interface View { //HttpServletRequest中的屬性名,其值為響應狀態碼
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; //HttpServletRequest中的屬性名,前一篇文章用到了該變量,它的對應值是請求路徑中的變量,及@PathVariable注解的變量
String PATH_VARIABLES = View.class.getName() + ".pathVariables"; //該視圖的ContentType
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType"; //獲取該視圖ContentType
String getContentType(); //渲染該視圖
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
該接口只有兩個方法定義,分別表明該視圖的ContentType和如何被渲染。Spring中提供了豐富的視圖支持,並且可以自定義視圖。
ViewResolver
ViewResolver接口定義了如何通過view 名稱來解析對應View實例的行為。例如在我自己的一個注解Handler實現里面,我返回的是“hello”view name字符串,意思就是響應到對應的hello.jsp視圖(在springmvc-servlet.xml配置文件定義了):
//controller
@RequestMapping(value="/helloWorldAnnotation") public String hello(ModelMap model){ … return "hello"; }
springmvc-servlet.xml:
<!-- ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
在這里,我選擇了默認Spring MVC JSP的實現類InternalResourceViewResolver。再來看看ViewResolver的接口定義:
public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
該接口只有一個方法,通過view name 解析出View。還是以我例子為准,通過“hello”view name字符串,通過ViewResolver. resolveViewName方法生成View實例。再通過View實例的render方法渲染該視圖,剩下的具體細節可自行學習。
總結
兩天學習下來,終於對Spring MVC有個大概的了解。畢竟是一個通用的框架,除了默認的實現,Spring MVC框架還定義了大量的標准可供用戶自定義實現,整體也算是采用了Open-Closed原則,擴展性好,但有不失整體優雅。