欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

细解spring mvc架构

程序员文章站 2022-07-12 19:41:42
...

概述

网上关于spring mvc的架构解析类的文章已经很多了,可以说是多如牛毛,但是为什么这里还要补充这么一篇呢?

这一篇实际上也没有太多的新内容,但是也包含一些新内容。描述的角度上也稍微有一些区别,而且关注了一点其他文章没有关注的问题点和新内容,如果有时间不妨一看。

以下内容,是在做着调研是否可以自定义一个MessageConverter,实现@ResponseBody结果依据请求路径不同,解析成两种不同json格式的可能性时的调研总结。

这里的描述适用于spring mvc 3.0 -4.0版本。

spring mvc整体流程

整体流程都是一样的,如果看到的不一样,那么很可能是错的,也有可能是将来很远的spring mvc版本。下图是spring请求处理流程图(摘自开涛博客):

细解spring mvc架构

这张图已经标注的比较明确。接下来,我们从实际使用的角度,来对照spring 工程中各个部分的xml配置进行进一步的对照说明。

Dispatcher

Dispatcher是前端控制器,是整个spring mvc框架的对外接口点,和全局调度器。request和response都从这里经过,请求的处理流程由Dispatcher控制。Dispatcher是一个Servlet的实现类,需要配置在web.xml里面,或者添加到WebApplicationInitializer(全注解方式配置)里面。

Xml配置方式:
细解spring mvc架构

注解配置方式:

细解spring mvc架构

Dispatcher中的主要代码:

细解spring mvc架构

第一个红框是HandlerMapping对应的处理位置,第二个红框是HandlerAdapter对应的处理位置,第三个红框是ViewResolver对应的处理位置。

HandlerMapping

HandlerMapping的作用是通过请求的url找到对应的Controller中的处理方法,也就是映射。然而,你会发现这个类我们没有在任何地方配置过,他是怎么就加载到Spring容器里边的呢?又加载的是哪一个呢?

Spring 默认提供了这些HandlerMapping的实现类:

细解spring mvc架构

细解spring mvc架构

当DispatcherServlet初始化的时候,就会把所有能在ApplicationContext里面找到的HandlerMapping实例(xml或者注解方式添加到spring 容器中)添加到自己的handlerMappings集合里面,这样就可以在后续的doDispatch中使用这些HandlerMapping了。

以下是DispatcherServlet初始化handlerMappings集合的位置:
细解spring mvc架构

那么,这些HandlerMapping是在哪里被标记为bean了呢,或者是是什么时候以bean的形式被添加到ApplicationContext里边了呢?关键就在于<mvc:annotation-driven />, 这个配置项会自动注入mvc需要的HandlerMapping和HandlerAdapter,所以一旦添加了这个配置,我们就不用配置HandlerMapping和HandlerAdapter了。注意,annotation-driven还给我们注入了其他mvc需要的东西。

HandlerMapping解析后返回的是一个HandlerMethod实例,其中包含了处理请求的类和方法:

细解spring mvc架构

HandlerAdapter

HandlerAdapter是spring mvc的主要执行器,像参数解析、方法调用、结果数据封装都是在HandlerAdapter中触发的。

Spring里面有两个默认实现的HandlerAdapter,一个是AnnotationMethodHandlerAdapter,另一个是RequestMappingHandlerAdapter。在spring3和spring3之前一般用的是AnnotationMethodHandlerAdapter,从spring 3.1开始慢慢的转变成使用RequestMappingHandlerAdapter。

HandlerAdapter中的主要代码逻辑:
细解spring mvc架构

invokeAndHandle完成了参数提取和解析、方法调用、响应数据解析(包含@ResponseBody数据解析)。

invokeAndHandle方法的核心代码:

细解spring mvc架构

其中invokeForRequest完成参数解析、方法调用。handleReturnValue完成返回结果数据的整理和处理,@ResponseBody数据的处理也是由handleReturnValue完成。

大家都知道@ResponseBody标注的方法返回结果会被解析成json串,而不会进行模板视图的渲染,那么spring是怎么控制跳过模板视图渲染这个步骤的呢?注意上图的第二个红框setRequestHandled,当handlReturnValue处理过程中遇到@ResponseBody注解的方法时,处理过程中会把requestHandled设置为true,后续的视图解析过程就会跳过,不进行模板渲染。

细解spring mvc架构

也就是说@ResponseBody注解方法的返回结果数据是直接在HandlerAdapter里面处理的,处理之后把ModelAndView设置为null,从而后续的视图渲染环节就不进行处理了。

从上边的说明,我们知道@ResponseBody注解方法的处理是在HandlerAdapter里面由returnValueHandlers处理的,一般我们也没有配置过returnValueHandlers属性,他们是怎么来的呢?

HandlerAdapter初始化时会自动创建一组默认的returnValueHandlers,代码位置如下:

细解spring mvc架构

既然,@ResponseBody注解方法返回数据的处理类是默认创建的,那么还能不能自定义呢?按照spring几个初始化点的加载顺序来说,是可以的。Spring几个初始化点的加载顺序是这样的:构造器-->自动注入-->PostConstrut-->InitializingBean-->xml中配置init方法

上图中的getDefaultReturnValueHandler方法是通过InitializingBean的方式调用的,也就是在它之后还有一个xml中配置的init方法,也就是说我们还有一个xml中配置init的方式来覆盖掉默认创建的处理器。

由于RequestMappingHandlerAdapter调用getDefaultReturnValueHandler获取默认的returnValueHandler之前有一个判断,判断returnValueHandlers是否为空,如果不为空,那么就不调用getDefaultReturnValueHandler。所以我们还可以在InitializingBean之前的位置注入自己的returnValueHandlers。

细解spring mvc架构

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对应的配置文件里。