引言
在Java服务端编程中,几乎没人能绕的开”Spring”这个单词。”Spring”发展至今,已经不是一个简单的IOC和AOP框架了,其中衍生出了非常多的组件,而其中一个非常重要的组件就是”Spring MVC”。Spring-webmvc解决了在网站开发时遇到的的很多常见问题,比如:
- 如何将特定的请求(url + 方法)分派到特定的对象处理方法中。
- 如何将请求中的request body映射为特定的类对象,又如何将返回的类对象映射到response body中
- 如何处理文件上传的请求
- 如何处理静态资源的缓存状态
- 如何处理在请求中可能发生的异常
webmvc,其实上述所指的更多的是c的部分,因为目前大多项目应用都实行前后端分离,因此笔者也并没有太过于关注m和v之间的绑定以及c和v之间的交互关系。spring通过注解、对象分离等等的方式,极大的简便了web开发的流程。
这篇文章将会主要会讲述两点,第一点是对DispatcherServlet做了一个简单介绍,包括其拥有的配置、功能,而第二点则是围绕着一个请求从到达Servlet直至处理完毕并且返回的流程来介绍spring-webmvc框架,其中包含有其请求分派机制,消息读写处理机制与异常处理机制。
Spring-webmvc的核心 —— DispatcherServlet
在org.springframework.web.servlet
这个包底下,有着spring-webmvc两个重要的Servlet实现——ResourceServlet
(主要用于静态资源)与DispatcherServlet
(主要用于动态请求)。我们将会重点讲解DispatcherServlet
。
DispatcherServlet的配置与功能
配置与功能实际上是相辅相成的,拥有特定的配置,就代表着它拥有特定的功能。所以我们首先关注一下DispatcherServlet的初始化函数。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); //用于文件处理
initLocaleResolver(context);//用于处理位置信息
initThemeResolver(context);
initHandlerMappings(context);//用于处理url与处理函数的映射
initHandlerAdapters(context);//用于将请求与Java类之间的转换
initHandlerExceptionResolvers(context);//统一的异常处理
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
可以看到,在DispatcherServlet
初始化的时候,会有很多相关的实例被注册到Servlet当中。这些实例实际上就决定了Servlet的功能。
以HandlerMapping为例,查看DispatcherServlet的初始化模式
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//默认情况下,detectAllHandlerMappings的值为true
if (this.detectAllHandlerMappings) {
// 在检测Handler的模式下,首先会找到所有实现了HandlerMapping接口的Bean
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
//对HandlerMapping进行排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
//如果不是检测所有Bean的话,就以handlerMapping这个key去找对应的Bean是否存在
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// 如果都不存在,则使用默认的HandlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
通过以上代码可以知道在DispatcherServlet中,首先会搜索由我们定义的HanderMapping实例,在找不到的情况下,则会使用默认的HandlerMapping。这也是spring-webmvc中比较常见的做法——如果扫描到用户定义的,则使用用户的,否则提供默认实例。
DispatcherServlet中的service方法
既然是Servlet,就必须实现service
方法,DispatcherServlet
继承了抽象类FrameworkServlet
,在其service
方法中,默认调用的是doService
方法,而在doService
方法中又调用了doDispatch
方法。
所以doDispatch
方法中才是包含了处理请求最核心的代码。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...//
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
...
//获取handler
mappedHandler = getHandler(processedRequest);
//...
// 获取handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
.....
// 处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
catch (Exception ex) {
//异常抛出及获取
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//将异常交由exceptionResolver进行处理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
....
}
忽略了部分代码之后,实际上我们就能看到请求的处理逻辑,首先获取对应的mappingHandler,然后由handlerAdapter进行请求参数的解析以及实际处理函数的调用,最后处理异常。
以上,也就是关于DispatcherServlet的简介了。接下来会讲述一些处理细节。
HandlerMapping —— Spring中的url分发机制
要说清楚url分发机制,我们首先应该了解其中的类继承关系。我们由RequestMappingHandlerMapping
出发,一探究竟。
从上图我们可以知道,在RequestMappingHandlerMapping
类中实际上持有了MappingRegistry
,而MappingRegistry
,则拥有所有url以及对应处理类/方法的映射关系。知道了这一层关系之后,实际上我们就能够找出这些映射关系是何时注册的。
实际上,映射关系的注册是由AbstractHandlerMethodMapping
中的initHandlerMethods
方法完成的。
protected void initHandlerMethods() {
...
//扫描所有bean,找出合适的(拥有注释@RequestMapping或者是@Controller,进行映射注册。
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
//异常处理
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
在HandlerMapping中,完成了url+方法到具体类方法的映射。但是如果需要将我们的POST请求里面的body转换到具体Java方法参数以及将我们返回的Java对象映射到响应的Body中,其实还有一段路要走,而这一段路,是交给HandlerAdapter
来完成的。
HandlerAdapter —— 将请求与Java方法中的参数适配
对于HandlerAdapter
,我们采用的例子是RequestMappingHandlerAdapter
。
首先查看这个类的属性:
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private HandlerMethodArgumentResolverComposite argumentResolvers;
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
private List<ModelAndViewResolver> modelAndViewResolvers;
以上,实际上就是我们能够进行配置的。而在afterPropertiesSet
方法中,我们还能够看到又一次的支持配置,同时保留默认。三个方法分别设置了三种类型的resolver,argumentResolvers
,initBinderArgumentResolvers
,以及returnValueHandlers
。
在这三个方法中,我们能够知道spring-webmvc具体支持哪些变量的适配。更为常用的是变量的适配,因此我们从这里先入手。
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// 基于注解的变量
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
//注意,这里使用了messageCoverter作为covert message的执行实例,用于将请求的内容映射到被@Request标注Java对象
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
//同上,但这里是映射以@RequestPart标记的对象。
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// 基于类型的变量
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// 自定义的变量
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// 其它
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
我们再往下看HttpMessageConverter
的接口。
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
显然,从请求到具体方法中的Java参数,实际上也是分成了几个部分,首先会被分派到Resolver中,然后具体resolver执行read函数返回实际对象。
而返回也是类似的,具体我们可以去查看Handler中的returnValueHandlers
。
Servlet中的异常处理
至于异常处理,则相对来说是比较好理解的一块,在DispatcherServlet
中的processHandlerException
就可以直接看到异常时如何被处理的。
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
//逐个ExceptionResolver遍历,直到异常被处理(返回的ModelView不为空)
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
...//处理返回的异常对应的ModelView
return exMv;
}
throw ex;
}
所以,我们只需要增加自己的ExceptionResolver声明(Bean),并且在处理异常完毕后返回特定的modelView就可以了。
实例
一个简单的集成了spring-webmvc的实例。
首先,将相关的依赖加入到POM文件中。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.3</version>
</dependency>
<!--用于处理输入输出的数据 -->
然后,声明一个Controller
@RestController
@RequestMapping("/hello")
public class HelloWordController {
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<String> Hello() {
return new ResponseEntity<String> ("Hello World", HttpStatus.OK);
}
}
然后,声明servlet
以及添加对应的XML文件。
<servlet>
<servlet-name>SpringDispatchServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/META-INF/spring/sample-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="web.rest"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
<bean class=" org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="jsonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</beans>
更详细的内容,可以到我的github上查看。
小结
spring-webmvc框架主要解决了从请求到实例方法调用的问题,包括中间的对象路由,参数转换以及地理位置、session、cookie等信息的获取。配合注释,我们能够迅速地创建一个后端应用。