上一章节笔者对SpringMVC的核心原理做了详细介绍,本章将结合源码的方式对SpringMVC的原理进行解析,在上个章节笔者提到了一个Spring核心类DispatcherServlet类,我们先看下这个类的继承关系
该类如笔者所说继承了我们的HttpServelt,如果我们实现HttpServelt的doGet()方法或者doPost(),那么我们根据映射关系可以将方法和url进行绑定,对于HttpServelt之上的内容,笔者不进行描述,因为这是Tomcat的知识,那么现在我们的核心关键就是回到doGet()方法或者doPost(),当我们在DispatcherServlet搜索这两个方法的时候会发现搜索不到,这时候我们回到他的父类FrameworkServlet,可以看到我们所提到的doGet和doPost方法
找到后我们打上断点,在新建一个类
@Controller
public class UserController{
@RequestMapping("/home")
public String home(){
return "home";
}
}
然后调试代码,将代码停在processRequest(request, response);然后在进入这个方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
这些都不用太关注,直接看到 doService(request, response);点进去
FrameworkServlet在调用service方法的时候会回调进入子类的方法,
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// 核心代码
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
前面都是一些初始化操作,找到doDispatch(request, response);跟踪进去找到mappedHandler = getHandler(processedRequest);这行代码,前面的一切操作都是判断是否异步、校验等操作,我们不需要太关注,我们进入 getHandler(processedRequest);
这里可以看到handlerMappings,如果了解SpringMVC过原理可能听到过这个属性,我们从网上复制一张网上的图
步骤一DispatcherServlet为什么进来,占什么角色笔者已经在上文说明,看到第二步获取handler,这里会有个问题,对比我们上面的截图我们看到handlerMappings是两个,这handlerMappings为什么是2,这并不是我们配置的,而是两种handlers来源,就是两种数据来源,一种来源@RequestMapping,如图
它的两个handler来源如下:
@Controller
public class WeCat {
@RequestMapping("/wecat")
public String weCat(){
return "wecat";
}
}
@Controller
public class UserController {
@RequestMapping("/home")
public String home(){
System.out.println("-------------");
return "home";
}
}
第二种来源如下:
@Component("/person")
public class PersonController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("------new ModelAndView()-----");
return new ModelAndView();
}
}
我们再次调试代码:
可以看到我们已经通过BeanNameUrlHandlerMapping找到了我们配置的person,这就证明了笔者所提到的来源问题,所以可以说上面的流程图并不完全或者说不太全面,好的我们回到流程图,在获取handler后,我们要通过handler获取HandlerAdapter,看下图
可以看到我们获取handler之后确实是通过它获取HandlerAdapter,我们点进去看下
这里看到我们的HandlerAdapter有三种,分别是RequestMappingHandlerAdapter,SimpleControllerHandlerAdapterBeanNameUrlHandlerMapping,前面两种笔者已经提到,分别是@Controller和接口Controller,第三种就比较麻烦,它的方式有点类似struct框架的方式,通过配置bean然后映射url,这种方式有点回到了原来的处理机制,并不多见,所以了解即可,我们回到IDEA
通过循环比对我们是采用的那三种方式,这里我们使用的是注解方式
@Controller
public class UserController {
@RequestMapping("/home")
public String home(){
System.out.println("-------------");
return "home";
}
}
然后成功后就返回我们的HandlerAdapter,返回类型RequestMappingHandlerAdapter,我们再次回到代码:
//返回HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 反射执行映射方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
获取HandlerAdapter后做一些判断然后进入核心方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这里有个细节
我们的mappedHandler.getHandler()获取的其实就是Url映射的方法,通过这个方法是不是我们就可以反射执行呢?答案是肯定的,当然SpringMVC在这里还做了一个十分重要的操作,细心的读者发现了如果我们的方法是这样的
@Controller
public class UserController {
@RequestMapping(value = "/home")
public String home(String s){
System.out.println("-------------");
return "index";
}
}
然后我们采用POSTMAN工具进行请求,
可以看到我们的参数被拿到,如果我们的参数是一个实体类对象,SpringMVC会通过对象进行绑定,这种实现读者称为数据绑定或数据转换,在后面的章节读者会单独对数据绑定做出详细描述,这也是Spring的核心功能之一,好了我们执行到我们的方法后会返回字符串,SpringMVC得到我们返回的内容后会帮我们封装成ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
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);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
看到最后一行代码processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);这里明显就是处理我们的结果了,这里笔者就略过了,毕竟我想表达的SpringMVC流程已经表达完了,后面的几个章节会对SpringMVC初始化、数据绑定、事件以及SpringBoot如何做到内置Tomcat、如何实现不基于web.xml实现SpringMVC的初始化等进行描述。