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.s[email protected]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 [[email protected]] 2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler 信息: Mapped URL path [/helloWorldAnnotation.*] onto handler [[email protected]] 2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler 信息: Mapped URL path [/helloWorldAnnotation/] onto handler [[email protected]] 2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler 信息: Mapped URL path [/helloWorldController] onto handler [[email protected]] 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原则,扩展性好,但有不失整体优雅。