细解spring mvc架构
概述
网上关于spring mvc的架构解析类的文章已经很多了,可以说是多如牛毛,但是为什么这里还要补充这么一篇呢?
这一篇实际上也没有太多的新内容,但是也包含一些新内容。描述的角度上也稍微有一些区别,而且关注了一点其他文章没有关注的问题点和新内容,如果有时间不妨一看。
以下内容,是在做着调研是否可以自定义一个MessageConverter,实现@ResponseBody结果依据请求路径不同,解析成两种不同json格式的可能性时的调研总结。
这里的描述适用于spring mvc 3.0 -4.0版本。
spring mvc整体流程
整体流程都是一样的,如果看到的不一样,那么很可能是错的,也有可能是将来很远的spring mvc版本。下图是spring请求处理流程图(摘自开涛博客):
这张图已经标注的比较明确。接下来,我们从实际使用的角度,来对照spring 工程中各个部分的xml配置进行进一步的对照说明。
Dispatcher
Dispatcher是前端控制器,是整个spring mvc框架的对外接口点,和全局调度器。request和response都从这里经过,请求的处理流程由Dispatcher控制。Dispatcher是一个Servlet的实现类,需要配置在web.xml里面,或者添加到WebApplicationInitializer(全注解方式配置)里面。
Xml配置方式:
注解配置方式:
Dispatcher中的主要代码:
第一个红框是HandlerMapping对应的处理位置,第二个红框是HandlerAdapter对应的处理位置,第三个红框是ViewResolver对应的处理位置。
HandlerMapping
HandlerMapping的作用是通过请求的url找到对应的Controller中的处理方法,也就是映射。然而,你会发现这个类我们没有在任何地方配置过,他是怎么就加载到Spring容器里边的呢?又加载的是哪一个呢?
Spring 默认提供了这些HandlerMapping的实现类:
当DispatcherServlet初始化的时候,就会把所有能在ApplicationContext里面找到的HandlerMapping实例(xml或者注解方式添加到spring 容器中)添加到自己的handlerMappings集合里面,这样就可以在后续的doDispatch中使用这些HandlerMapping了。
以下是DispatcherServlet初始化handlerMappings集合的位置:
那么,这些HandlerMapping是在哪里被标记为bean了呢,或者是是什么时候以bean的形式被添加到ApplicationContext里边了呢?关键就在于<mvc:annotation-driven />
, 这个配置项会自动注入mvc需要的HandlerMapping和HandlerAdapter,所以一旦添加了这个配置,我们就不用配置HandlerMapping和HandlerAdapter了。注意,annotation-driven还给我们注入了其他mvc需要的东西。
HandlerMapping解析后返回的是一个HandlerMethod实例,其中包含了处理请求的类和方法:
HandlerAdapter
HandlerAdapter是spring mvc的主要执行器,像参数解析、方法调用、结果数据封装都是在HandlerAdapter中触发的。
Spring里面有两个默认实现的HandlerAdapter,一个是AnnotationMethodHandlerAdapter,另一个是RequestMappingHandlerAdapter。在spring3和spring3之前一般用的是AnnotationMethodHandlerAdapter,从spring 3.1开始慢慢的转变成使用RequestMappingHandlerAdapter。
HandlerAdapter中的主要代码逻辑:
invokeAndHandle完成了参数提取和解析、方法调用、响应数据解析(包含@ResponseBody数据解析)。
invokeAndHandle方法的核心代码:
其中invokeForRequest完成参数解析、方法调用。handleReturnValue完成返回结果数据的整理和处理,@ResponseBody数据的处理也是由handleReturnValue完成。
大家都知道@ResponseBody标注的方法返回结果会被解析成json串,而不会进行模板视图的渲染,那么spring是怎么控制跳过模板视图渲染这个步骤的呢?注意上图的第二个红框setRequestHandled,当handlReturnValue处理过程中遇到@ResponseBody注解的方法时,处理过程中会把requestHandled设置为true,后续的视图解析过程就会跳过,不进行模板渲染。
也就是说@ResponseBody注解方法的返回结果数据是直接在HandlerAdapter里面处理的,处理之后把ModelAndView设置为null,从而后续的视图渲染环节就不进行处理了。
从上边的说明,我们知道@ResponseBody注解方法的处理是在HandlerAdapter里面由returnValueHandlers处理的,一般我们也没有配置过returnValueHandlers属性,他们是怎么来的呢?
HandlerAdapter初始化时会自动创建一组默认的returnValueHandlers,代码位置如下:
既然,@ResponseBody注解方法返回数据的处理类是默认创建的,那么还能不能自定义呢?按照spring几个初始化点的加载顺序来说,是可以的。Spring几个初始化点的加载顺序是这样的:构造器-->自动注入-->PostConstrut-->InitializingBean-->xml中配置init方法
。
上图中的getDefaultReturnValueHandler方法是通过InitializingBean的方式调用的,也就是在它之后还有一个xml中配置的init方法,也就是说我们还有一个xml中配置init的方式来覆盖掉默认创建的处理器。
由于RequestMappingHandlerAdapter调用getDefaultReturnValueHandler获取默认的returnValueHandler之前有一个判断,判断returnValueHandlers是否为空,如果不为空,那么就不调用getDefaultReturnValueHandler。所以我们还可以在InitializingBean之前的位置注入自己的returnValueHandlers。
ViewResolver
ViewResolver的作用是通过请求信息、返回结果和方法映射信息获取View对象,View对象包含一个renderMergedTemplateModel方法,调用后会渲染结果数据为对应的展示形式(页面、xml、csv等等)。
ViewResolver可以单独配置成一个特定的类,这样只能处理一种类型的视图解析。如果有需要设置多种类型的视图解析,可以使用ContentNegotiatingViewResolver配置多种视图解析器。ContentNegotiatingViewResolver可以依据请求参数中的format值、url的尾缀、请求头中的MediaType三种方式确定返回结果类型。
单视图解析器的话,我们可以直接配置一种就可以了,比如使用FreeMarker模板引擎,配置如下:
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="false" />
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
<property name="contentType" value="text/html;charset=UTF-8"></property>
<property name="requestContextAttribute" value="rc" />
<property name="exposeSpringMacroHelpers" value="true" />
<property name="exposeRequestAttributes" value="true" />
<property name="exposeSessionAttributes" value="true" />
<property name="allowSessionOverride" value="true" />
</bean>
使用ContentNegotiatingViewResolver配置如下:
<!-- ViewResolver -->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="1" />
<property name="favorParameter" value="false" />
<property name="ignoreAcceptHeader" value="true" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="defaultViews">
<list>
<!-- JSON View -->
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
<!-- JAXB XML View -->
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>learning.webapp.model.EmployeeX</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
</list>
</property>
</bean>
<!-- If no extension matched, use JSP view -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="order" value="2" />
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/views/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
如果想自定义视图解析器,可以参见作者之前写的一篇《自定义springmvc视图解析器》
总结一下
在配置spring mvc的时候,我们只需要配置Dispatcher、ViewResolver,xml中添加annotation-driven就可以了。
如果是放到spring 3.0之前,没有annotation-driven的时候,我们需要自己配置HandlerMapping和HandlerAdapter,同时也不能支持@ResponseBody注解的方法。
所以,网上很多的配置教程都是混杂了spring 2和spring 3的,有些配置项完全可以省掉。
Annotation-driven & Component-scan & annotation-config
Component-scan: 把以**解的类实例化到容器中:@Component、@Repository、@Service、@Controller;解析自动注入的注解: @Autowired、@Qualifier。
annotation-config:处理已经包含到容器(注解或者xml配置方式实例化的bean)中实例的@Autowired、@Qualifier注解。
Annotation-driven:会实例化spring mvc相关的一些必要对象,比如HandlerMapping和HandlerAdapter。由于spring mcv有两个容器,annotation-driven只能应该,且必须放在Dispatcher对应的配置文件里。
推荐阅读
-
spring mvc DispatcherServlet之前端控制器架构详解
-
spring mvc DispatcherServlet之前端控制器架构详解
-
Android 客户端通过内置API(HttpClient) 访问 服务器(用Spring MVC 架构) 返回的json数据全过程
-
细解spring mvc架构
-
Spring 自带验证框架 - MVC架构 - 前端给后端传递数据时校验数据-较为方便
-
Spring 框架基础(06):Mvc架构模式简介,执行流程详解
-
Spring技术内幕——深入解析Spring架构与设计原理(四)Web MVC的实现
-
Spring MVC的 架构模式
-
Spring MVC代码实践之网站架构及演变
-
Spring 自带验证框架 - MVC架构 - 前端给后端传递数据时校验数据-较为方便