Spring是如何处理http请求的
1、 SpingMVC处理流程
- 明确SptingMVC也是通过实现了
Servlet来实现注解处理的 - 熟悉整个
Servlet的处理过程 SpringMVC大致处理流程
HttpServlet的 service方法(也被复写,请求主要是从这里开始) --> doXXX()方法---> 1、doXXX()方法被子类FrameworkServlet复写---> 2、processRequest(req,resp) 处理请求---> 3、Spring的尿性,所有实际处理都是以do开头,实际处理doService()---> 4、子类 DispatcherServlet(所谓的前端处理器) 实现---> 5、内部doDispatch 处理---> 6、判断是不是文件上传 checkMultipart(request)---> 7、获取Handler getHandler(processedRequest);,如果handler不存在,404---> 8、根据handler获取handlerAdapter---> 9、处理Spring拦截器的前置方法---> 10、实际处理Controller的方法---> 11、处理Spring拦截器的后置方法---> 12、处理请求结果本次分析完毕
主要分析是从5开始;
为了方便下文的理解,先看下边的这个方法和 DispatcherServlet.properties 文件
protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);//初始化文件上传initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);//初始化handlerMapping,重点initHandlerAdapters(context); //初始化handlerAdapter 反射执行invoke(Controller)方法initHandlerExceptionResolvers(context);//异常解析器initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
DispatcherServlet.properties,主要的几个
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
Servlet init 的时候首先会将 properties 里边的key和value读取进来,然后初始化;
这时,我们假设一个 http 请求 跨越千山万水,终于到达 doDispatch() 方法了。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;//异步管理,servlet3.1的规范还不是很了解WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);ModelAndView mv = null;Exception dispatchException = null;//检查是不是文件上传processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);//获取HandlerMapping 重点mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {//如果能够处理该请求的处理器,返回一个404noHandlerFound(processedRequest, response);return;}//根据处理器返回一个adapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);//处理last-modufied 请求头,还是要看 adapter 支不支持这个请求头if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());//使用缓存if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}//处理Spring拦截器的前置方法,如果返回的是false,那么!false,不能反射调用Controller方法了if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//实际处理mv = ha.handle(processedRequest, response, mappedHandler.getHandler());applyDefaultViewName(processedRequest, mv);//拦截器的后置处理mappedHandler.applyPostHandle(processedRequest, response, mv);//处理结果processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}else {//处理文件上传的if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
1、文件上传处理,从 DispatcherServlet.properties 里边我们能知道 Spring 是没有字节去处理文件上传的,如果要使用文件上传,那么就需要加入其他的处理
具体的文件上传原理涉及到 input file 的处理,如果想进一步知道,可以看这里——> http 抓包和解析
文件上传,要求1、必须是 POST 请求;要求2、context-type必须是multipart/开头
2、获取 HandlerMapping
先看类图 HandlerMapping 类图,可以看出,这是一个典型的多级抽象实现,很平常的模板模式,
HandlerMapping的定义很简单,就是获取一个能够处理这个http请求的handler
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; 核心方法,获取Handler
DispatcherServlet 获取 Handlermapping
@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {//this.handlerMappings 就是我们上边些的properties里边的配置文件项,5.0.7只有2项了,更加老的版本应该是3个for (HandlerMapping hm : this.handlerMappings) {//更具http请求获取 HandlerExecutionChain 执行链,这里又涉及到了责任链模式的处理HandlerExecutionChain handler = hm.getHandler(request);if (handler != null) {return handler;}}}return null;}
HandlerMapping 的直接实现如下
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request); // 重点,根据http请求获取到handlerif (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}//如果handler是字符串,那么从容器中找到这个Bean,obtainApplicationContext()还记得是ApplicationContext()把if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}//组装 HandlerExecutionChain ,chain内部就是我们定义的拦截器HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);//跨域处理if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}//返回执行链,后续先执行拦截器的前置方法,再执行具体的http请求方法,再执行后置方法,完成方法return executionChain;}
如何根据http请求获取到handler,看代码我们可以知道它这里是一个抽象方法,交给子类去实现了,子类也很简单,一个是根据方法,一个是根据url,根据我们平常使用的,我们只看前者,如果你喜欢,你都可以自定义自己的handler实现
@Overrideprotected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {//核心方法,根据http请求得到请求路径 , getUrlPathHelper() 是一个UrlPathHelper,路径处理的//涉及到Servlet-path,context-path,path-info的处理,不懂这几个,可以回头看看前一篇文章String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);//获取读锁,这里是读写锁,写锁是在写入的时候出现的,不熟悉的可以再去看看前文this.mappingRegistry.acquireReadLock();//根据请求路径,http请求获取到执行方法HandlerMethod,如果还记得Controller类的处理,那么就应该知道这是什么HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);this.mappingRegistry.releaseReadLock();// 移除了日志,将finally代码块移上来了//返回return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}
解析路径,首先是根据path-info处理,如何为null,再根据 servlet路径处理
public String getLookupPathForRequest(HttpServletRequest request) {// Always use full path within current servlet context? 这里默认是false,可以修改成true,true是根据servlet-pathif (this.alwaysUseFullPath) {return getPathWithinApplication(request);}//返回servlet映射下的请求路径,使用pathinfoString rest = getPathWithinServletMapping(request);if (!"".equals(rest)) {return rest;}else {return getPathWithinApplication(request);}}
这里就是servlet-path和pathinfo,以及context-path的处理了
public String getPathWithinServletMapping(HttpServletRequest request) {//获取pathinfoString pathWithinApp = getPathWithinApplication(request);//获取servlet路径String servletPath = getServletPath(request);//移除分号[;]以后的路径,因为http请求是可以带分号的String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);String path;//if (servletPath.contains(sanitizedPathWithinApp)) {path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);}else {path = getRemainingPath(pathWithinApp, servletPath, false);}if (path != null) {// Normal case: URI contains servlet path.return path;}else {String pathInfo = request.getPathInfo();if (pathInfo != null) {return pathInfo;}if (!this.urlDecode) 「path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);if (path != null) {return pathWithinApp;}}return servletPath;}}
getPathWithinApplication(request);
public String getPathWithinApplication(HttpServletRequest request) {//不懂可以看我上篇路径懂讲解String contextPath = getContextPath(request);String requestUri = getRequestUri(request);//返回 uri - context 部分String path = getRemainingPath(requestUri, contextPath, true);if (path != null) {return (StringUtils.hasText(path) ? path : "/");}else {return requestUri;}}
到这一步,请求路径获取完毕,开始根据路径获取 handlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();//根据url获取requestMappingInfo,不懂看controller注册部分List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);//添加到matchesif (directPathMatches != null) {//路径匹配addMatchingMappings(directPathMatches, matches, request);}//如果没有匹配中,让全部路径匹配一次if (matches.isEmpty()) {addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}//如果还是没有匹配中404if (!matches.isEmpty()) {//排序,选择最佳处理方法,比较Mapping的几个参数,Head,路径,参数等等Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);Match bestMatch = matches.get(0);if (matches.size() > 1) {//是不是跨域if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}Match secondBestMatch = matches.get(1);//两个方法一致,如果你写了2个相同的方法,启动不会报错,但是执行具体的情况会报错,不知道给谁处理if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");}}//handleMatch(bestMatch.mapping, lookupPath, request);//返回HandlerMethodreturn bestMatch.handlerMethod;}else {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}}
这里 handler 有了,执行的方法也有了,去找一个adapter就可以了,原则也很简单,只要你支持 support 即可,查看adapter的实现结构,最后找到了 AbstractHandlerMethodAdapter 也就只有 RequestMappingHandlerAdapter 的父类实现了这个接口,其余的要么是不符合,要么是跟不上时代了,例如继承 controller
@Overridepublic final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}
到这一步,啥都有了,处理缓存的请求头,这个我们不是重点,于是接下来执行拦截器的前置方法
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];//一个一个的执行前置方法,看到责任链模式没有,同志们if (!interceptor.preHandle(request, response, this.handler)) {//提前结束了,触发请求完毕triggerAfterCompletion(request, response, null);//falsereturn false;}this.interceptorIndex = i;}}return true;}
这个时候我们可以说说 Spring 的拦截器和 Filter 的区别了
* Filter一个是在Servlet前执行,是Servlet的规范* 拦截器是在Servlet里边执行,是Spring的实现
接下来就是实际处理了,内在原理是反射,method.invoke(object,args),后续介绍 Spring 声明式事务也会讲这里
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
到这一步Spring的处理基本上算上结束了,接下来的就是一些扫尾操作了。
总结
结合 controller 的注册和 request 的处理,我们基本上明白了,其实内在就是路径和handler的映射,只是Spring做到了大而全,就加大了我们理解的难度,不过只要我们抓住它的七寸,理解那至于实现一次都不是问题。
一开始,我很迷惑他的路由uri --> handler ,等到我真的理解了的时候,其实本质还是一个Map映射,uri是key,handler是value
责任链模式———> 代码在这里
2018-08-21
