共计 9266 个字符,预计需要花费 24 分钟才能阅读完成。
弱小的 DispatcherServlet
还记得在 web.xml 中配置的 DispatcherServlet 吗? 其实那个就是 SpringMVC 框架的入口,这也是 struts2 和 springmvc 不同点之一,struts2 是通过 filter 的,而 springmvc 是通过 servlet 的。看下 servlet 的结构图
从下面这张图很显著能够看出 DispatcherServlet 和 Servlet 以及 Spring 的关系。而咱们明天的重点就从 DispatchServlet 说起。
在剖析之前我用 SpringBoot 搭建了一个很简略的后盾我的项目,用于剖析。代码如下
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 办法中,此时查看整个调用栈如下
从这里咱们也晓得了申请时如何从 servlet 到了 DispatcherServlet 的,咱们先来看一下 DispatcherServlet 的 doDiapatch 的办法逻辑,这里把外围逻辑列出来了,把其余的一些非核心逻辑移除了
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
(参数组装),通过它来进行最终办法的调用
解析 multipart
那么是如何解析以后申请是文件上传申请呢?这里间接进入到 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
是什么时候初始化的呢?
咱们在 idea 中能够间接将断点定位到 multipartResolver 属性上,进行申请拜访这个时候会发现断点间接进入到了 initMultipartResolver 办法中,接着跟踪整个调用栈,能够发现调用关系如下:
图上表明了是在初始化 servlet 的时候对 multipartResolver 进行了初始化的。
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/ 来进行断定的。
DispatchServlet 初始化了哪些内容
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);
}
这些初始化的内容都会在前面被逐个应用,这里先有一个印象。
依据申请获取 mapperHandler
还是进入到 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 的实现类,来看看它的类图构造是怎么的
这几个 HandlerMapping 的作用如下:
SimpleUrlHandlerMapping : 容许明确指定 URL 模式和 Handler 的映射关系,外部保护了一个 urlMap 来确定 url 和 handler 的关系
BeanNameUrlHandlerMapping: 指定 URL 和 bean 名称的映射关系,不罕用,咱们的关注点也次要集中在RequestMappingHandlerMapping
中
这里也根本明确了 HandlerMapping 的作用: 帮忙 DispatcherServlet 进行 Web 申请的 URL 到具体类的匹配, 之所以称为 HandlerMapping 是因为在 SpringMVC 中并不局限于
必须应用注解的 Controller 咱们也能够继承 Controller 接口,也同样能够应用第三方接口,比方 Struts2 中的 Action
接着看下 getHandler 的实现:
@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 的调用逻辑如下:
其中保护了 url 到 handler 的映射,先通过 url 到 urlMap 中找对应的 handler,如果没有找到则尝试 pattenMatch,胜利则返回对应的 handler, 未匹配则返回 null。
会发现解决 HandlerMapping 这里使用了模板办法,在抽象类中定义好了业务逻辑,具体实现只须要实现本人的业务逻辑即可。同时也合乎开闭准则,齐全是面向接口编程,不得不让人叹服这里的波及逻辑。
剖析到这里的时候咱们会发现咱们之前定义的 Controller 显著是合乎 RequestMappingHandlerMapping
的策略的,所以返回的 HandlerExecutionChain 曾经蕴含了须要拜访的办法的全门路了。
对于 HandlerAdapter
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
的初始化过程是相似的
接着在看一下 HandlerAdapter 的类关系
同样的,依然通过适合的策略寻找对应的 Adapter,咱们次要关注的是 RequestMappingHandlerAdapter(其余的用得很少),所以这里就次要解说它。查看它 support 的实现代码:
下面对于 handler 的阐明中说了其实 Object handler 实际上是 HandlerMethod,所以这里对应的 HandlerAdapter 就是RequestMappingHandlerAdapter
。
找到对应的适配器之后,这个时候就能够调用真正的逻辑了。在这之前使用者能够通过拦截器做一些事儿,比方记录日志,打印执行工夫等,所以如果想要在执行的办法之前增加一条语句,咱们只须要配置本人的拦击器即可。
接下来咱们重点剖析 handle 办法,看看它到底会做什么?,先看一下 handle 办法的执行流程,同样的 adapter 同样应用了模板办法,先在父类外面定义流程,子类只须要实现逻辑即可,所以这里首先会调用 AbstracthandlerMethodAdapter 的 invokeHadlerMethod 办法,其中对 HandlerMethod 进行了封装。
咱们进入到第一步,看看 invokeForRequest 办法中次要做了什么
发现这个办法的调用逻辑实际上很简略,就是解析参数,而后调用办法。咱们来看一下如何进行参数解析的呢?
能够看到简直所有的外围逻辑都集中到了 argumentResovlers
中去,那么反对的 arguementResolver 有哪些?又是在哪里初始化的呢?
首先须要定位到这个属性是从哪里过去的,RequestMappingHandlerAdapter
实现了 InitializingBean
,所以在初始化的时候会执行afterPropertiesSet
办法,在这其中对 arguementResolvers
以及 returnValueHandlers
进行了初始化。
不同的 resovler 反对的参数解析不一样,比如说有反对 HttpServletRequest 注入的,有反对 HttpServletREsponse 注入的还有反对 body 体注入的等等。
通过参数解析之后就失去了反射须要的数据了,class,method 以及参数,最初通过 java 的反射 api 调用即可。
至此,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 来实现的
最初获取 ModalAndView 对象,然而这里因为没有 modalAndView 所以返回的 null. 最初在 DispatcherServlet 的 processDispatchResult 办法的调用逻辑如下
么对于这样的申请又时如何解析的呢?
@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 的解决等等,如果铺开来讲篇幅切实是太长了,我更置信把握了外围流程看其余的解决就会很简略了,所以这里也就不对其余枝节内容做剖析了。
一图胜千言