还记得在web.xml中配置的DispatcherServlet吗?其实那个就是SpringMVC框架的入口,这也是struts2和springmvc不一样点之一,struts2是经过filter的,而springmvc是经过servlet的。看下servlet的结构图 html
从上面这张图很明显能够看出DispatcherServlet和Servlet以及Spring的关系。而咱们今天的重点就从DispatchServlet提及。java
在分析以前我用SpringBoot搭建了一个很简单的后台项目,用于分析。代码以下web
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String address;
public User() {
}
}
/** * @author generalthink */
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public User getUser(HttpServletRequest request,@PathVariable Integer id) {
//建立一个user,不走数据库只是为了分析springmvc源码
User user = User.builder()
.id(id)
.age(ThreadLocalRandom.current().nextInt(30))
.name("zzz" + id)
.address("成都市").build();
return user;
}
@RequestMapping(value = "/condition",method = RequestMethod.GET)
public User getByNameOrAge(@RequestParam String name,@RequestParam Integer age) {
User user = User.builder().name(name).age(age).address("成都市").id(2).build();
return user;
}
@PostMapping
public Integer saveUser(@RequestBody User user) {
Integer id = user.getName().hashCode() - user.getAge().hashCode();
return id > 0 ? id : -id;
}
}
复制代码
这里为了方便调试把关注点更多集中在SpringMVC源码中,因此这里的数据都是伪造的。并且这里的关注点也集中到使用注解的Controller(org.springframework.stereotype.Controlle
r),而不是Controller接口(org.springframework.web.servlet.mvc.Controller
),这二者的区别主要在乎一个只用标注注解,一个须要实现接口,可是它们都能完成处理请求的基本功能。咱们都知道访问servlet的时候默认是访问service方法的,因此咱们将断点打在HttpServlet的service方法中,此时查看整个调用栈以下 spring
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
//注意这里放回的是HandlerExecutionChain对象
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
ModelAndView mv = null;
Exception dispatchException = null;
//检查是否存在文件上传
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 根据当前request获取handler,handler中包含了请求url,以及最终定位到的controller以及controller中的方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 经过handler获取对应的适配器,主要完成参数解析
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用Controller中的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
复制代码
能够看到核心逻辑其实很是简单,首先检查是否是multipart request
,若是是则对当前的request进行必定的封装(提取文件等),而后获取对应的handler(保存了请求url对应的controller以及method以及一系列的Interceptor),而后在经过handler获取到对应的handlerAdapter
(参数组装),经过它来进行最终方法的调用数据库
那么是如何解析当前请求是文件上传请求呢?这里直接进入到checkMultipart方法看看是如何解析的:编程
//我精简了下代码,只提取了核心逻辑
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
return this.multipartResolver.resolveMultipart(request);
}
return request;
}
复制代码
从这里能够看出经过multipartResolver
判断当前请求是不是文件上传请求,若是是则返回MultipartHttpServletRequest
(继承自HttpServletRequest).不是则返回本来request对象。 那么问题来了multipartResolver
是何时初始化的呢?json
咱们在idea中能够直接将断点定位到multipartResolver属性上,进行请求访问这个时候会发现断点直接进入到了initMultipartResolver方法中,接着跟踪整个调用栈,能够发现调用关系以下: api
private void initMultipartResolver(ApplicationContext context) {
//从Spring中获取id为multipartResolver的类
this.multipartResolver = context.getBean("multipartResolver", MultipartResolver.class);
}
复制代码
MultipartResolver接口有CommonsMultipartResolve
以及StandardServletMultipartResolver
2种实现,CommonsMultipartResolver接口是依赖于commons-upload组件实现的,而 StandardServletMultipartResolver是依赖于Servlet的part(servlet3才存在)实现的.二者判断是不是文件上传请求的方法isMultipart均是经过断定请求方法是否为post以及content-type头是否包含multipart/来进行断定的。数组
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); //初始化multipartResolver
initLocaleResolver(context);//初始化localeResolver
initThemeResolver(context);//初始化themResolver
initHandlerMappings(context);//初始化handerMappings
initHandlerAdapters(context);//初始化handlerAdapters
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);//初始化试图解析器
initFlashMapManager(context);
}
复制代码
这些初始化的内容都会在后面被逐一使用,这里先有一个印象。mvc
仍是进入到getHander方法中看看到底作了什么?
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
复制代码
根据HandlerMapping来查看对应的handler,那么进入到initHandlerMappings方法中查看如何初始化handlerMappings
其中获取默认的handlerMappings是去spring-webmvc的org.springframework.web.servlet
中的DispatcherServlet.properties中查找,文件内容是这样的
detechAllhanderMappings
默认为true,因此会获取到全部HanderMapping的实现类,来看看它的类图结构是怎样的
RequestMappingHandlerMapping
中
这里也基本明确了HandlerMapping的做用:帮助DispatcherServlet进行Web请求的URL到具体类的匹配,之因此称为HandlerMapping是由于在SpringMVC中并不局限于 必须使用注解的Controller咱们也能够继承Controller接口,也一样可使用第三方接口,好比Struts2中的Action
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
复制代码
返回的handler是HandlerExecutionChain,这其中包含了真实的handler以及拦击器,能够在执行前,执行后,执行完成这三个阶段处理业务逻辑。 RequestMappingHandlerMapping
的getHandler的调用逻辑以下:
会遍历全部Controller的url查看是否有符合条件的match(head,url,produce,consume,method都要知足要求),采用antMatcher的方式来进行url匹配,若是匹配上了则返回对应的handler,不然返回null,若是映射发现有重复的映射(url映射相同,请求方法相同,参数相同,请求头相同,consume相同,produce相同,自定义参数相同),则会抛出异常。
而SimpleUrlHandlerMapping的调用逻辑以下:
会发现处理HandlerMapping这里运用了模板方法,在抽象类中定义好了业务逻辑,具体实现只须要实现本身的业务逻辑便可。同时也符合开闭原则,彻底是面向接口编程,不得不让人叹服这里的涉及逻辑。
分析到这里的时候咱们会发现咱们以前定义的Controller明显是符合RequestMappingHandlerMapping
的策略的,因此返回的HandlerExecutionChain已经包含了须要访问的方法的全路径了。
HandlerMapping会经过HandlerExecutionChain
返回一个Object类型的Handler对象,用于Web请求处理,这里的Handler并无限制具体是什么类型,通常来讲任何类型的Handler均可以在 SpringMVC中使用,只要它是用于处理Web请求的处理对象就行。
不过对于DispatcherServlet来讲就存在问题了,它没法判断到底使用的是什么类型的Handler,也没法知道是调用Handler的哪一个方法来处理请求,为了以赞成的方式来调用各类类型的Handler, DispatcherServlet将不一样Handler的调用职责转交给了一个成为HandlerAdapte
r的角色。
先看一下HandlerAdpter接口的定义
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
复制代码
主要关注supports和handle方法。先看下DispatcherServlet
中handlerAdapters
的初始化过程,和handlerMappings
的初始化过程是相似的
RequestMappingHandlerAdapter
。
找到对应的适配器以后,这个时候就能够调用真正的逻辑了。在这以前使用者能够经过拦截器作一些事儿,好比记录日志,打印执行时间等,因此若是想要在执行的方法以前添加一条语句,咱们只须要配置本身的拦击器便可。
发现这个方法的调用逻辑实际上很简单,就是解析参数,而后调用方法。咱们来看一下如何进行参数解析的呢?
argumentResovlers
中去,那么支持的arguementResolver有哪些?又是在哪里初始化的呢?
首先须要定位到这个属性是从哪里过来的,RequestMappingHandlerAdapter
实现了InitializingBean
,因此在初始化的时候会执行afterPropertiesSet
方法,在这其中对arguementResolvers
以及returnValueHandlers
进行了初始化。 不一样的resovler支持的参数解析不同,好比说有支持HttpServletRequest注入的,有支持HttpServletREsponse注入的还有支持body体注入的等等。
至此,springmvc的整个调用流程基本就清晰了。 可是到了这里问题仍然没有结束,由于咱们还不知道参数具体是如何解析的。好比get方式提交的数据?post方式提交的数据?如何转换成对象的?这写问题都还存在,那咱们继续研究。 这里我使用postman工具来发起请求,首先访问 Get http://localhost:8080/user/condition?name=zhangsan&age=25,定位到resolveArgument
方法
接着又执行revolver.resolveArgument
方法,一样的这里仍是使用的模板方法,在抽象类AbstractNamedValueMethodArgumentResolver
中定义流程,各个子类只须要实现本身的逻辑便可。RequestParamMethodArgumentResolver
的参数就是经过request.getParameter来获取到的。获取到了参数以后就执行反射调用,这个时候就执行了咱们写的UserController的对应方法,获取到了User对象,接下来就是处理返回值了,经过returnValueHandlers进行处理
handler会根据返回的类型对数据进行处理,好比说这里就经过response向请求方输出数据,输出数据也是经过messageConverter来实现的
么对于这样的请求又时如何解析的呢?
@PostMapping
public Integer saveUser(@RequestBody User user) {
Integer id = user.getName().hashCode() - user.getAge().hashCode();
return id > 0 ? id : -id;
}
复制代码
一样咱们聚焦在解析参数的时候,在上一个get请求的示例中我说了会先访问AbstractNamedValueMethodArgumentResolver
,可是在处理@RequestBody
的参数中它使用的是RequestResponseBodyMethodProcessor
,它复写了resolveArgument
方法。因此不会去执行父类的逻辑。
这里最后会定位到jakson的objectMapper中, 在spring boot中,默认使用Jackson来实现java对象到json格式的序列化与反序列化。固然是能够配置messageConvert的,只须要实现Spring的HttpMessageConverter
便可。
源码分析到这里就结束了,固然其中还存在一些没有讲的地方,好比View的渲染呀,通常视图是多种多样的,又html,xml,jsp等等,因此springmvc也提供了接口供用户选择本身须要的模板,只须要实现ViewResolver接口便可。还有关于Theme,MessageResource,Exception的处理等等,若是铺开来说篇幅实在是太长了,我更相信掌握了核心流程看其余的处理就会很简单了,因此这里也就不对其余枝节内容作分析了。