Spring是如何处理http请求的

1、 SpingMVC处理流程

  • 明确SptingMVC也是通过实现了 Servlet 来实现注解处理的
  • 熟悉整个 Servlet 的处理过程
  • SpringMVC 大致处理流程
  1. HttpServlet service方法(也被复写,请求主要是从这里开始) --> doXXX()方法
  2. ---> 1doXXX()方法被子类FrameworkServlet复写
  3. ---> 2processRequest(req,resp) 处理请求
  4. ---> 3Spring的尿性,所有实际处理都是以do开头,实际处理doService()
  5. ---> 4、子类 DispatcherServlet(所谓的前端处理器) 实现
  6. ---> 5、内部doDispatch 处理
  7. ---> 6、判断是不是文件上传 checkMultipart(request)
  8. ---> 7、获取Handler getHandler(processedRequest);,如果handler不存在,404
  9. ---> 8、根据handler获取handlerAdapter
  10. ---> 9、处理Spring拦截器的前置方法
  11. ---> 10、实际处理Controller的方法
  12. ---> 11、处理Spring拦截器的后置方法
  13. ---> 12、处理请求结果
  14. 本次分析完毕

主要分析是从5开始;

为了方便下文的理解,先看下边的这个方法和 DispatcherServlet.properties 文件

  1. protected void initStrategies(ApplicationContext context) {
  2. initMultipartResolver(context);//初始化文件上传
  3. initLocaleResolver(context);
  4. initThemeResolver(context);
  5. initHandlerMappings(context);//初始化handlerMapping,重点
  6. initHandlerAdapters(context); //初始化handlerAdapter 反射执行invoke(Controller)方法
  7. initHandlerExceptionResolvers(context);//异常解析器
  8. initRequestToViewNameTranslator(context);
  9. initViewResolvers(context);
  10. initFlashMapManager(context);
  11. }

DispatcherServlet.properties,主要的几个

  1. org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
  2. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
  3. org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
  4. org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
  5. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
  6. org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
  7. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
  8. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

Servlet init 的时候首先会将 properties 里边的key和value读取进来,然后初始化;

这时,我们假设一个 http 请求 跨越千山万水,终于到达 doDispatch() 方法了。

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. boolean multipartRequestParsed = false;
  5. //异步管理,servlet3.1的规范还不是很了解
  6. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  7. ModelAndView mv = null;
  8. Exception dispatchException = null;
  9. //检查是不是文件上传
  10. processedRequest = checkMultipart(request);
  11. multipartRequestParsed = (processedRequest != request);
  12. //获取HandlerMapping 重点
  13. mappedHandler = getHandler(processedRequest);
  14. if (mappedHandler == null) {
  15. //如果能够处理该请求的处理器,返回一个404
  16. noHandlerFound(processedRequest, response);
  17. return;
  18. }
  19. //根据处理器返回一个adapter
  20. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  21. String method = request.getMethod();
  22. boolean isGet = "GET".equals(method);
  23. //处理last-modufied 请求头,还是要看 adapter 支不支持这个请求头
  24. if (isGet || "HEAD".equals(method)) {
  25. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  26. //使用缓存
  27. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  28. return;
  29. }
  30. }
  31. //处理Spring拦截器的前置方法,如果返回的是false,那么!false,不能反射调用Controller方法了
  32. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  33. return;
  34. }
  35. //实际处理
  36. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  37. applyDefaultViewName(processedRequest, mv);
  38. //拦截器的后置处理
  39. mappedHandler.applyPostHandle(processedRequest, response, mv);
  40. //处理结果
  41. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  42. if (mappedHandler != null) {
  43. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  44. }
  45. else {
  46. //处理文件上传的
  47. if (multipartRequestParsed) {
  48. cleanupMultipart(processedRequest);
  49. }
  50. }
  51. }

1、文件上传处理,从 DispatcherServlet.properties 里边我们能知道 Spring 是没有字节去处理文件上传的,如果要使用文件上传,那么就需要加入其他的处理
具体的文件上传原理涉及到 input file 的处理,如果想进一步知道,可以看这里——> http 抓包和解析
文件上传,要求1、必须是 POST 请求;要求2、context-type必须是multipart/开头

2、获取 HandlerMapping
先看类图 HandlerMapping 类图,可以看出,这是一个典型的多级抽象实现,很平常的模板模式,
HandlerMapping的定义很简单,就是获取一个能够处理这个http请求的handler

  1. HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; 核心方法,获取Handler

DispatcherServlet 获取 Handlermapping

  1. @Nullable
  2. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  3. if (this.handlerMappings != null) {
  4. //this.handlerMappings 就是我们上边些的properties里边的配置文件项,5.0.7只有2项了,更加老的版本应该是3个
  5. for (HandlerMapping hm : this.handlerMappings) {
  6. //更具http请求获取 HandlerExecutionChain 执行链,这里又涉及到了责任链模式的处理
  7. HandlerExecutionChain handler = hm.getHandler(request);
  8. if (handler != null) {
  9. return handler;
  10. }
  11. }
  12. }
  13. return null;
  14. }

HandlerMapping 的直接实现如下

  1. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. Object handler = getHandlerInternal(request); // 重点,根据http请求获取到handler
  3. if (handler == null) {
  4. handler = getDefaultHandler();
  5. }
  6. if (handler == null) {
  7. return null;
  8. }
  9. //如果handler是字符串,那么从容器中找到这个Bean,obtainApplicationContext()还记得是ApplicationContext()把
  10. if (handler instanceof String) {
  11. String handlerName = (String) handler;
  12. handler = obtainApplicationContext().getBean(handlerName);
  13. }
  14. //组装 HandlerExecutionChain ,chain内部就是我们定义的拦截器
  15. HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  16. //跨域处理
  17. if (CorsUtils.isCorsRequest(request)) {
  18. CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
  19. CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
  20. CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
  21. executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  22. }
  23. //返回执行链,后续先执行拦截器的前置方法,再执行具体的http请求方法,再执行后置方法,完成方法
  24. return executionChain;
  25. }

如何根据http请求获取到handler,看代码我们可以知道它这里是一个抽象方法,交给子类去实现了,子类也很简单,一个是根据方法,一个是根据url,根据我们平常使用的,我们只看前者,如果你喜欢,你都可以自定义自己的handler实现

  1. @Override
  2. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  3. //核心方法,根据http请求得到请求路径 , getUrlPathHelper() 是一个UrlPathHelper,路径处理的
  4. //涉及到Servlet-path,context-path,path-info的处理,不懂这几个,可以回头看看前一篇文章
  5. String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
  6. //获取读锁,这里是读写锁,写锁是在写入的时候出现的,不熟悉的可以再去看看前文
  7. this.mappingRegistry.acquireReadLock();
  8. //根据请求路径,http请求获取到执行方法HandlerMethod,如果还记得Controller类的处理,那么就应该知道这是什么
  9. HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
  10. this.mappingRegistry.releaseReadLock();// 移除了日志,将finally代码块移上来了
  11. //返回
  12. return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
  13. }

解析路径,首先是根据path-info处理,如何为null,再根据 servlet路径处理

  1. public String getLookupPathForRequest(HttpServletRequest request) {
  2. // Always use full path within current servlet context? 这里默认是false,可以修改成true,true是根据servlet-path
  3. if (this.alwaysUseFullPath) {
  4. return getPathWithinApplication(request);
  5. }
  6. //返回servlet映射下的请求路径,使用pathinfo
  7. String rest = getPathWithinServletMapping(request);
  8. if (!"".equals(rest)) {
  9. return rest;
  10. }
  11. else {
  12. return getPathWithinApplication(request);
  13. }
  14. }

这里就是servlet-path和pathinfo,以及context-path的处理了

  1. public String getPathWithinServletMapping(HttpServletRequest request) {
  2. //获取pathinfo
  3. String pathWithinApp = getPathWithinApplication(request);
  4. //获取servlet路径
  5. String servletPath = getServletPath(request);
  6. //移除分号[;]以后的路径,因为http请求是可以带分号的
  7. String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
  8. String path;
  9. //
  10. if (servletPath.contains(sanitizedPathWithinApp)) {
  11. path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
  12. }
  13. else {
  14. path = getRemainingPath(pathWithinApp, servletPath, false);
  15. }
  16. if (path != null) {
  17. // Normal case: URI contains servlet path.
  18. return path;
  19. }
  20. else {
  21. String pathInfo = request.getPathInfo();
  22. if (pathInfo != null) {
  23. return pathInfo;
  24. }
  25. if (!this.urlDecode)
  26. path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
  27. if (path != null) {
  28. return pathWithinApp;
  29. }
  30. }
  31. return servletPath;
  32. }
  33. }

getPathWithinApplication(request);

  1. public String getPathWithinApplication(HttpServletRequest request) {
  2. //不懂可以看我上篇路径懂讲解
  3. String contextPath = getContextPath(request);
  4. String requestUri = getRequestUri(request);
  5. //返回 uri - context 部分
  6. String path = getRemainingPath(requestUri, contextPath, true);
  7. if (path != null) {
  8. return (StringUtils.hasText(path) ? path : "/");
  9. }
  10. else {
  11. return requestUri;
  12. }
  13. }

到这一步,请求路径获取完毕,开始根据路径获取 handlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

  1. protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  2. List<Match> matches = new ArrayList<>();
  3. //根据url获取requestMappingInfo,不懂看controller注册部分
  4. List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
  5. //添加到matches
  6. if (directPathMatches != null) {
  7. //路径匹配
  8. addMatchingMappings(directPathMatches, matches, request);
  9. }
  10. //如果没有匹配中,让全部路径匹配一次
  11. if (matches.isEmpty()) {
  12. addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
  13. }
  14. //如果还是没有匹配中404
  15. if (!matches.isEmpty()) {
  16. //排序,选择最佳处理方法,比较Mapping的几个参数,Head,路径,参数等等
  17. Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  18. matches.sort(comparator);
  19. Match bestMatch = matches.get(0);
  20. if (matches.size() > 1) {
  21. //是不是跨域
  22. if (CorsUtils.isPreFlightRequest(request)) {
  23. return PREFLIGHT_AMBIGUOUS_MATCH;
  24. }
  25. Match secondBestMatch = matches.get(1);
  26. //两个方法一致,如果你写了2个相同的方法,启动不会报错,但是执行具体的情况会报错,不知道给谁处理
  27. if (comparator.compare(bestMatch, secondBestMatch) == 0) {
  28. Method m1 = bestMatch.handlerMethod.getMethod();
  29. Method m2 = secondBestMatch.handlerMethod.getMethod();
  30. throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
  31. }
  32. }
  33. //
  34. handleMatch(bestMatch.mapping, lookupPath, request);
  35. //返回HandlerMethod
  36. return bestMatch.handlerMethod;
  37. }
  38. else {
  39. return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
  40. }
  41. }

这里 handler 有了,执行的方法也有了,去找一个adapter就可以了,原则也很简单,只要你支持 support 即可,查看adapter的实现结构,最后找到了 AbstractHandlerMethodAdapter 也就只有 RequestMappingHandlerAdapter 的父类实现了这个接口,其余的要么是不符合,要么是跟不上时代了,例如继承 controller

  1. @Override
  2. public final boolean supports(Object handler) {
  3. return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
  4. }

到这一步,啥都有了,处理缓存的请求头,这个我们不是重点,于是接下来执行拦截器的前置方法

  1. boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HandlerInterceptor[] interceptors = getInterceptors();
  3. if (!ObjectUtils.isEmpty(interceptors)) {
  4. for (int i = 0; i < interceptors.length; i++) {
  5. HandlerInterceptor interceptor = interceptors[i];
  6. //一个一个的执行前置方法,看到责任链模式没有,同志们
  7. if (!interceptor.preHandle(request, response, this.handler)) {
  8. //提前结束了,触发请求完毕
  9. triggerAfterCompletion(request, response, null);
  10. //false
  11. return false;
  12. }
  13. this.interceptorIndex = i;
  14. }
  15. }
  16. return true;
  17. }

这个时候我们可以说说 Spring 的拦截器和 Filter 的区别了

  1. * Filter一个是在Servlet前执行,是Servlet的规范
  2. * 拦截器是在Servlet里边执行,是Spring的实现

接下来就是实际处理了,内在原理是反射,method.invoke(object,args),后续介绍 Spring 声明式事务也会讲这里

  1. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

到这一步Spring的处理基本上算上结束了,接下来的就是一些扫尾操作了。

总结

结合 controller 的注册和 request 的处理,我们基本上明白了,其实内在就是路径和handler的映射,只是Spring做到了大而全,就加大了我们理解的难度,不过只要我们抓住它的七寸,理解那至于实现一次都不是问题。

一开始,我很迷惑他的路由uri --> handler ,等到我真的理解了的时候,其实本质还是一个Map映射,uri是key,handler是value

责任链模式———> 代码在这里

2018-08-21