SpringMVC用了几年,一直停留在会用层面,annotation-driver的配置一直都是上网搜或者抄老项目的,前台传JSON进来后台如何接收,后台传Date类型,如何格式化给前台?为什么要配置Filter,为什么要配置拦截器,ArgumentResolver和HttpMessageConverter啥区别?如何便捷的校验前台参数?这些东西,全部是SpringMVC做的,不知道的话很难施展拳脚。
下图是我初步整理的一个流程图,里边的关键词都是平时能看到的。
contextConfigLocation
配置文件,初始化Spring IOC容器,初始化ApplicationContext。初始化后,其作为父级公共ApplicationContext供WebApplicationContext使用。contextConfigLocation
->dispatcher-servlet.xml
,初始化WebApplicationContext,包括annotation-driver
所声明的RequestMappingHandlerAdapter
(装载默认的ArgumentResolver、MessageConverter、ConversionService等),还有handlerMappings
。initStrategies
方法,从WebApplicationContext中初始化其自身属性,包含RequestMappingHandlerAdapter
,并将ApplicationContext、WebApplicationContext放入HttpRequest属性中,供后面用。依照上述流程图的文字描述,以HttpServletRequest做为引子,来描述处理过程。 以下描述不尽准确,因为涉及到的继承类太多,我只是将我觉得比较合适的名称放出来,用它来代表一个继承体系。另外,我的关注点在过滤器、拦截器、转换器等对参数的处理上,所以,其他地方都没有深究,描述有误的地方见谅
handlerMappings
中获取合适的HandlerExecutionChain
,拿到HandlerMethod,然后调用RequestMappingHandlerAdapter。AbstractView
子类渲染View,然后获取RequestDispatcher
,将view include至Response中,或forward到新的url。上边过程中提到的类,下边基本都会一一解析。
ServletRequest,持有客户端的请求信息,Servlet容器会创建一个ServletRequest
对象,并将其作为参数传给Servlet.service
方法。ServletRequest
持有的信息有 parameter name and values, attributes, and an input stream
,实现该接口的类可以提供协议相关的其他信息,来丰富扩充ServletRequest
。
ServletResponse,用来帮助Servlet发送返回(Response)给客户端。当请求到达时,Servlet容器会创建一个ServletResponse
对象,并将其作为参数传给Servlet.service
方法。
HttpServletRequest,继承了ServletRequest
接口,提供HTTP请求的信息给Servlet。Servlet容器会创建一个HttpServletRequest
对象,并将其作为参数传给Servlet.service
方法。不同的Servlet容器,有不同的实现,比如Tomcat的实现为org.apache.catalina.connector.RequestFacade
。另外,SpringMVC为了方便使用,定义了WebRequest接口,其实现了HttpServletRequest接口。
HttpServletResponse,继承了ServletResponse
接口,专门处理HTTP请求的返回,比如它有获取HTTP header 和 cookie values的方法。跟上边一样,它也是Servlet容器创建的,然后传递给Servlet.service
方法。
ServletRequestWrapper继承了ServletRequest
接口,即ServletRequest
的包装或装饰器模式,供开发者去增加或修改合适请求信息给Servlet。
ServletResponseWrapper继承了ServletResponse
接口,即ServletResponse
的包装或装饰器模式,供开发者去增加或修改合适的返回信息给客户端。
分别继承了HttpServletRequest
和HttpServletResponse
接口,即HttpServletRequest
和HttpServletResponse
的包装或装饰器模式,供开发者去增加或修改合适的请求和返回信息给Servlet。这里经常会写自己的HttpServletRequestWrapper,处理跨域请求,拦截sql注入,或者,将前台请求的JSON InputStream,转为HttpServletRequest的ParameterMap。
HttpServletRequest到达具体的Servlet前,Filter可以对其进行拦截,并且可以改变HttpServletRequest。比如上边自定义的HttpServletRequestWrapper,然后将其传入下一个Filter,最终到达Servlet,比如下面这样
Map<String, String> paramMap = ServletUtil.getHttpJsonParamMap(request, base64);
JsonHttpServletRequestWrapper decodingRequest = new JsonHttpServletRequestWrapper(request, paramMap);
chain.doFilter(decodingRequest, response);
因DispatcherServlet是SpringMVC的入口,也放在这边介绍,此章是重点。
配置: DispatcherServlet是拦截所有http请求的Servlet,在web.xml中配置
<servlet>
<servlet-name>http</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
继承关系如下:
DispatcherServlet extends FrameworkServlet
FrameworkServlet extends HttpServletBean
工作原理:
DispatcherServlet.properties
,其中配置了DispatcherServlet中所依赖的所有默认实现类;HttpServletBean
上一个标准的Servlet,其init方法中,调用了FrameworkServlet .initWebApplicationContext()
方法;contextConfigLocation
且基于根ApplicationContext来初始化WebApplicationContext;DispatcherServlet.onRefresh()
方法,其调用了DispatcherServlet.initStrategies()
方法,initStrategies
方法建议细看,里边所有的初始化都在这里做,包括HandlerMethodMapping;DispatcherServlet
中的各种Spring的Bean(DispatcherServlet中的属性字段)做了初始化。可以看到DispatcherServlet
中有一堆BeanName常量,供从ApplicationContext中获取bean,可以查看源码,如果指定的名字我们自己定义了bean,则返回,如果没有,则返回spring自己初始化的默认Bean。如果想扩展这些Bean,只要我们在Spring配置文件中定义了这些Bean,就会被加载进去。DispatcherServlet.doService()
方法,doService方法将Spring的上下文信息塞入了HttpRequest对象中,然后doService方法调用了DispatcherServlet.doDispatch()
,这样doDispatch
和doService
方法中便可以使用ApplicationContext中的Bean了。然后,在DispatcherServlet.doDispatch()
方法中,调用了HandlerAdapter.handle()
方法。目前HandlerAdapter的实现类有五个,处理SpringMVC请求的就AnnotationMethodHandlerAdapter
(已过期)和RequestMappingHandlerAdapter
。自定义扩展
DispatcherServlet有如下Bean属性,针对需求可以定义自己的对应的Bean,Spring会将其加载进去。
private MultipartResolver multipartResolver;
private LocaleResolver localeResolver;
private ThemeResolver themeResolver;
/** List of HandlerMappings used by this servlet,HandlerMapping中放了HandlerExecutionChain,总共4种Handler,HttpRequestHandler、Servlet、Controller、HandlerMethod
*/
private List<HandlerMapping> handlerMappings;
/** List of HandlerAdapters used by this servlet,总共有5种,SpringMVC用的RequestMappingHandlerAdapter */
private List<HandlerAdapter> handlerAdapters;
private List<HandlerExceptionResolver> handlerExceptionResolvers;
private RequestToViewNameTranslator viewNameTranslator;
private FlashMapManager flashMapManager;
private List<ViewResolver> viewResolvers;
配置:
可以使用annotation-driver配置,也可以直接配置RequestMappingHandlerAdapter Bean,配置之后DispatcherServlet便可以从WebApplicationContext中拿到该Bean。
通过查看XSD,发现在4.1版本之前,annotation-driven
元素是关联到了AnnotationMethodHandlerAdapter
类,而4.1之后,该类被@Deprecated
标记位过期,而是用RequestMappingHandlerAdapter
取而代之,所以为了使用后者,我们需要使用4.1之后的Schema文件http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
;
下面是简单的一个annotation-driven配置。
<!-- http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
继承关系
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean
调用过程:
RequestMappingHandlerAdapter.handleInternal()
->RequestMappingHandlerAdapter.invokeHandleMethod()
工作原理:
通过代码能看到DispatcherServlet,通过HandlerAdapter去执行http请求,而这个HandlerAdapter包括MessageConverter、ArgumentResolver等,都支持扩展,我们一般都会在Spring-mvc.xml文件中配置,比如:
RequestMappingHandlerAdapter
实现了InitializingBean
接口,在其afterPropertiesSet()
方法中装配了Spring默认的和用户自定义的ArgumentResolver
、ReturnValueHandler
,并将这三者封装为其对应的组合类*Composite,赋值给类属性。比如getDefaultArgumentResolvers()
方法用来初始化类属性argumentResolvers
,其装配了一堆的Spring默认的ArgumentResolver,然后加载了开发者自己写的CustomArgumentResolver。HandlerAdapter.handle()
方法,最终调用了RequestMappingHandlerAdapter.invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)
方法,。invokeHandleMethod()
方法,将HttpServletRequest封装为WebRequest(ServletNativeRequest)。webBindingInitializer
的配置(含ConversionService、Validator),封装WebDataBinderFactory。(WebDataBinderFactory中组合了BindingInitializer,当创建DataBinder时,将BindingInitializer中的ConversionService、Validator等给到DataBinder)自定义扩展:
RequestMappingHandlerAdapter 有如下类属性,我们均可以根据自己的需求自定义自己的扩展,在配置annotation-driver或RequestMappingHandlerAdapter Bean时去指定。
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private HandlerMethodArgumentResolverComposite argumentResolvers;
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
private List<ModelAndViewResolver> modelAndViewResolvers;
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
private List<HttpMessageConverter<?>> messageConverters;
private WebBindingInitializer webBindingInitializer;
继承关系:
ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest
NativeWebRequest extends WebRequest
说明:
做了一层封装,为了Spring框架的设计考虑,做了部分扩展。注意这里的HttpServletRequest是一个接口类,如果自己定义了filter,且指定了自定义的HttpServletRequestWrapper,那么此处的HttpServletRequest实现类即是你自己的HttpServletRequestWrapper。如果没有定义Filter和Wrapper,则其实现类是Web容器中的实现类,不同的Web容器有不同的实现,比如Tomcat的为org.apache.catalina.connector.RequestFacade
继承关系
HttpMethodArgumentResolver是SpringMVC中一个庞大的体系,下面介绍几个有代表性的,具体参见另一篇文章。
// HandlerMethodArgumentResolver的组合器模式,对外提供统一接口,内部调用合适的Resolver解析参数
HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver
// NamedValueResolver,对基本注解参数解析,然后调用ConversionService、Converter做参数转换,调用Validator做参数校验
AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver
// MessageConverterResolver,对@ResponseBody @RequestBody HttpEntity做处理,调用MessageConverter做转换
AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver
// ServletRequestResolver,对特殊类型的参数,比如ServletRequest参数做处理,直接从上下文中取值赋值
ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver
// MapMethodResolver,对参数类型为Map,且未指定参数名的参数做处理,直接从HttpServletRequest的ParameterMap中取
RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver
找个壮丁:
在其庞大的继承体系中RequestResponseBodyMethodProcessor
占了大头,其同时实现了 HandlerMethodReturnValueHandler
和HandlerMethodArgumentResolver
接口,他也是NamedValueResolver家族中的一员,他对@RequestBody、@ResponseBody做处理,首先将HttpServletRequest转换为HttpInputMessage,然后交给HttpMessageConverter转换为Object,然后用Valid校验,最后用WebDataBinder将校验结果绑定到BindingResult中,并将结果加入到ModelAndViewContainer
属性中。因其还实现了HandlerMethodReturnValueHandler
接口,所以他还会处理返回值的类型转换工作。其工作是最复杂的,且处理了大部分SpringMVC请求。
工作原理:
RequestMappingHandlerAdapter.afterPropertiesSet()->getDefaultArgumentResolvers()
方法中,装配了一堆Spring的和开发者自定义的ArgumentResolver,然后赋值给属性argumentResolvers`,其是各ArgumentResolver的组合器——HandlerMethodArgumentResolverCompositeargumentResolvers
用于封装ServletInvocableHandlerMethod
和ModelFactory
support()
方法获取合适的ArgumentResolver类解析参数,不同的ArgumentResolver有不同的解析过程,见继承体系中的注释自定义扩展:
开发中经常需要转换前台的参数,可以自定义自己的实现,RequestMappingHandlerAdapter会将我们自定义的ArgumentResolver塞到customArgumentResolvers属性中,然后在初始化时,和Spring默认的一起加入到argumentResolvers中
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<argument-resolvers>
<beans:bean
class="com.arlen.common.web.argument.JsonMapperArgumentResolver" />
</argument-resolvers>
</mvc:annotation-driven>
工作原理:
RequestMappingHandlerAdapter.afterPropertiesSet()->getDefaultReturnValueHandlers()
方法中,装配了一堆Spring自带的和开发者自定义的ReturnValueHandler(包含了封装MessageConverter的Handler——RequestResponseBodyMethodProcessor),然后在赋值给属性returnValueHandlers
,其是各ReturnValueHandler的组合器——HandlerMethodReturnValueHandlerCompositereturnValueHandlers
用于封装ServletInvocableHandlerMethod,在RequestMappingHandlerAdapter.createRequestMappingMethod
中,将returnValueHandlers
给到HandlerMethod。InvocableHandlerMethod.invokeForRequest()
方法后,用ReturnValueHandler对返回值做处理,如果方法有@ResponseBody注解,则调用HttpMessageConverter做实际的转换。自定义扩展:
同上边的ArgumentResolver
工作原理:
实则为ArgumentResolver、ReturnValueHandler的一个消息转换工具,只是针对@ResponseBody、@RequestBody、HttpEntity参数的特殊处理。
RequestMappingHandlerAdapter.getDefaultArgumentResolvers()
中,将上边装配的messageConverters
,包装为RequestResponseBodyMethodProcessor
、RequestPartMethodArgumentResolver
、HttpEntityMethodProcessor
RequestMappingHandlerAdapter.getDefaultReturnValueHandlers()
中,将messageConverters
包装为HttpEntityMethodProcessor
、RequestResponseBodyMethodProcessor
,与上边不同的是,装配时,指定了ContentNegotiationManager
和List<Object> responseBodyAdvice
参数。,也就是说ContentNegotiationManager使用来处理返回值用的。自定义扩展:
HttpMessageConverter可以自定义实现,或覆盖修改现有的。比如下面这样,将StringHttpMessageConverter覆盖,为其制定参数,以UTF-8编码:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg>
<value>UTF-8</value>
</constructor-arg>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
注意:上边的配置都是覆盖RequestMappingHandlerAdapter构造方法中加载的MessageConverter,比如上边的配置,只为RequestMappingHandlerAdapter指定了一个MessageConverter,其他的都被你覆盖没啦。
WebDataBinderFactory有两个用途,一是,ModelFactory;二是,InvocableHandlerMethod。但是都是在ArgumentResolver解析参数,需要转换参数时使用。
继承体系:
ServletRequestDataBinderFactory extends InitBinderDataBinderFactory
InitBinderDataBinderFactory extends DefaultDataBinderFactory
DefaultDataBinderFactory implements WebDataBinderFactory
工作原理:
RequestMappingHandlerAdapter.invokeHandleMethod()
方法中,构建WebDataBinderFactory,将InitBinderMethod、WebBindingInitializer封装给WebDataBinderFactory;WebDataBinder.convertIfNecessary()
进行参数转换,方法中,会将封装给WebDataBinder的ConversionService转换为SimpleTypeConverter extends TypeConverterSupport
,然后调用TypeConverterSupport.convertIfNecessary()->doConvert()
,最后通过TypeConverterDelegate,判断是调用PropertyEditorRegistrar,还是调用ConversionService做具体的转换。如果是ConversionService的话,转换是调用SpringCore中的Converter做的具体转换自定义扩展:
可以自定义WebBindingInitializer扩展,可以指定自己定义的ConversionService、Validator、PropertyEditorRegistrar。因为annotation-driver不支持RequestMappingHandlerAdapterde的webBindingInitializer属性配置,所以必须单独配置RequestMappingHandlerAdapterde bean。
<bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="webBindingInitializer">
<bean class="com.arlen.common.web.MyWebBindingInitializer" />
</property>
</bean>
ConversionService是SpringCore中的接口,用来替代Java PropertyEditor来做类型转换的,在SpringMVC中也会用到。
上边知道了,ConversionService是在RequestMappingHandlerAdapter->WebBindingInitializer
中的,但是Spring默认的ConversionService是如何加载进来的?查Spring文档,要自己定义一个ConversionService的话,必须用ConversionServiceFactoryBean
,那么猜想Spring初始化默认的ConversionService是用的ConversionServiceFactoryBean
,所以从ConversionServiceFactoryBean
入手去找。
工作原理:
ConversionServiceFactoryBean
继承了InitializingBean
,在其afterPropertiesSet()
方法中调用了自身的createConversionService()
方法,该方法返回一个新的DefaultConversionService
。DefaultConversionService
构造方法中,调用了DefaultConversionService.addDefaultConverters()
,该方法中加载了所有Spring定义的Converter。也即DefaultConversionService拥有所有的Converter,同时DefaultConversionService也组合到每个Converter中。不同的Converter有不同的转换方法,但是最终都是实现了GenericConverter
接口的Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)
方法。(还有实现Converter
、ConverterFactory
接口的,此处没深究)convert
方法做转换,在ConversionService的convert方法中,最终会调用GenericConversionService.convert()
方法,用ConversionUtils.invokeConverter()
方法实现GenericConverter
的convert
方法调用。convert
方法(2中已经讲了,Converter也持有ConversionService)。自定义扩展:
如果要用自定义的和Spring自己的默认Converter,则必须在Spring配置文件中声明ConversionServiceFactoryBean
工厂Bean,Spring会用我们定义的工厂Bean覆盖默认的,进而加载我们定义的Converter。
也可以将自己声明的Bean给到RequestMappingHandlerAdapter的WebBindingInitializer。也即在Annotation-driver中配置WebBindingInitializer
,并且指定conversionService
属性为自己定义的ConversionServiceFactoryBean
工厂Bean
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.arlen.common.core.converter.CustomConverter" />
</list>
</property>
</bean>
注意:同HttpMessageConverter,如果配置了ConversionServiceFactoryBean
工厂Bean,会覆盖默认加载的Converters,所以最好的办法是扩展ConversionServiceFactoryBean,定义自己的工厂Bean,不光可以改变默认Converters的顺序,还能加载自己想要的Converter
附: Formater也是基于ConversionService的,跟ConversionService一样的用法,具体查看Spring文档。
终于到了ServletInvocableHandlerMethod,最终的controller方法被反射调用,此为核心(写得累死了,还得干活。。。)
继承体系:
ServletInvocableHandlerMethod extends InvocableHandlerMethod
InvocableHandlerMethod extends HandlerMethod
工作原理:
createRequestMappingMethod
中创建,封装了HandlerMethod、WebDataBinderFactory、ArgumentResolver、ReturnValueHandler、ParameterNameDiscovererServletInvocableHandlerMethod
,一切都是为它服务,包括:自身被包装对象HandlerMethod,参数解析器ArgumentResolvers,返回处理器ReturnValueHandlers,数据绑定工厂DataBinderFactory,参数名查找器ParameterNameDiscoverer。ServletInvocableHandlerMethod
->invokeAndHandle()
方法,该方法调用了InvocableHandlerMethod.invokeForRequest()
。InvocableHandlerMethod.invokeForRequest()
->getMethodArgumentValues()
->this.argumentResolvers.resolveArgument()
,该方法即调用了ArgumentResolver获取参数的值。doInvoke()
->getBridgedMethod().invoke(getBean(), args)
,然后底层就不看啦,就是反射的方法调用啦。ServletInvocableHandlerMethod
->invokeAndHandle()
继续处理返回值,调用了this.returnValueHandlers.handleReturnValue()
。实则为ArgumentResolver的一个返回值处理工具,只针对@ResponseBody注解方法和HttpEntity类型返回值方法。
contentNegotiationManager = new ContentNegotiationManager()
,然后用户自定义的会替换默认的RequestMappingHandlerAdapter.getDefaultReturnValueHandlers
方法中,封装成
HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor
,在其父类的writeWithMessageConverters()
->getAcceptableMediaTypes()
方法中,获取ContentNegotiationManager中所配置的接受的MediaType。父类的writeWithMessageConverters()
由子类的handleReturnValue()
方法调用RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor
,同上,在其父类中获取可以接受的MediaType,同上也在子类的handleReturnValue
方法中调用略
略
写在最后:之前没有好好学习,自己写了自己的Validator框架,写了自己的Converter框架,这两天看完SpringMVC后,得考虑摈弃自己写的那些框架了。
Spring:
HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver
:组合器模式,首先组合器继承Resolver接口,组合器有多个ArgumentResolver,每个Resolver有自己的Support方法,然后组合器对外统一接口(即Resolver对外的接口),然后内部遍历取合适的Resolver,做相应的操作。
HttpRequestHandlerAdapter implements HandlerAdapter
:适配器模式,将Spring封装的不同的Servlet处理器Handler(有各自不同的接口,比如HttpRequestHandler——handleRequest、Servlet——service、Controller(Spring的)——handleRequest),通过适配(在适配器中转换),对外统一开放同样的接口。重点来了:不同于其他Handler,Spring MVC中的HandlerMethod
(即你@RequestMapping声明的方法),没有实际的Handler,而是只有自己实际的适配器RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
,之前一直纠结的MessageConverter、ArgumentResolver啥的是啥关系,都从这里找到了根源。其中的handleInternal
方法是切入点。
HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest
:装饰模式,跟被装饰者继承相同的接口,然后对外调用时,做一定的处理,然后再调用被装饰者的方法。
之前都是基于XML配置Spring的,在阅读SpringMVC源码过程中发现了WebMvcConfigurationSupport。其是用于通过类方式来配置Spring的,只要自己的配置类继承该类即可。
annotation-driven,可以配置很多东西,截了个图放这边,方便查询。