前言
在我们已经聊过了一些Spring MVC的运行原理,当然大多数人应该还是和我一样迷迷糊糊,只知道一个大概的运行过程,这一篇,我想要从源码的角度更加进一步去了解Spring MVC的整个运行过程。
springframework源码调试
为了能够进一步了解spring的运行过程,debug源码当然是不二的选择。网上搜索的话还是能找到一些资料的,但是感觉方法都比较复杂。这里发现一种比较简单的源码调试方案,拿出来分享一下。
首先需要准备一个可以运行的Spring Boot项目,比如我们redis一篇中准备的db项目就是个不错的选择。当然还需要我们的调试环境,这里用的是idea作为调试环境。
然后还需要下载springframework的。
然后查看我们springframework依赖的版本。如果直接在pom.xml中查找,发现我们并没有明确指定版本。
org.springframework.boot spring-boot-starter-parent 2.1.1.RELEASE
这个东西帮我们自动选择了版本。
我们可以在左上角选择项目展示模式,选择project
然后再External Libraries中查看依赖版本
比如这里我们依赖了5.1.3.RELEASE版本。
然后将我们从github下载的源码切换到对应的tag (git checkout v5.1.3.RELEASE)
然后再IDEA中打开项目的File -> Project Structure页面,选择Libraries页面,选择我们要调试的库,比如要DispatcherServlat所在的包在右侧显示大概是这样的
我们看到Soures是红色的,表示找不到相关的Source,选中sources,然后点击下方的 + 按钮,最后选择下载下来的spring-framework的源码中响应的module,比如spring-webmvc对应的目录就是
然后在IDEA中打开DispatcherServlat类。CMD + O 输入类名可以跳转到类。如果你不指定源码,这个时候就会进入.class文件,但是如果已经制定源码,那么就会打开.java文件,这个时候就和普通的debug一样了,我们可以debug springframework中的源码了。
拦截器HandlerInterceptor
我们知道Spring MVC的核心类就是 DispatcherServlet 作为一个Servlet,核心方法就是onService用来接收请求提供服务。而onService 又会调用doDispatch方法。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { //multipart request处理,会使用默认提供的 StandardServletMultipartResolver 类 processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //解析request,获取 HandlerExecutionChain (他会包含一个处理器和HandlerInterceptor) mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } //根据 HandlerExecutionChain 选择一个HandlerAdapter 准备处理请求 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); 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; } } //调用 HandlerExecutionChain 中HandlerInterceptor 的 perHandler方法查看是否需要拦截 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //通过handlerAdapter调用HandlerExecutionChain中的处理器,返回ModelAndView视图处理器 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); //调用 HandlerExecutionChain 中HandlerInterceptor 的 postHandler mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } //对返回结果做最后处理 (内部会调用 HandlerInterceptor 的 afterCompletion) this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { //报错时调用 HandlerInterceptor 的 afterCompletion this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }
万幸,DispatcherServlet中核心方法的代码行数并不算爆炸。而且有了上一篇的了解,这里的调用过程也基本能够一一对应。
上面有整体步骤多次调用了HandlerInterceptor。所以我们先来分析下这个拦截器。
public interface HandlerInterceptor { // 处理器执行前方法 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } // 处理器处理后方法 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } // 处理器完成后方法 (包括返回渲染) default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }}
拦截器的方法并不多,并且也不难理解。整体流程大概是这样
我们定义一个简单的拦截器
//有default实现,不一定需要重写方法public class Interceptor1 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("处理器前方法"); // 返回true,不会拦截后续的处理 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("处理器后方法"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("处理器完成方法"); }}
然后,我们需要将它注册到Spring MVC框架中。
@Configurationpublic class WebAppConfigurer implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 可添加多个 registry.addInterceptor(new Interceptor1()) //interceptor的作用返回,只会作用在 /interceptor/* 地址下面 .addPathPatterns("/interceptor/*"); } ....}
多个拦截器
假设我们定义三个拦截器,并且按照 1 2 3的顺序进行注册。那么运行顺序大概如下
【MulitiInterceptor1】处理器前方法【MulitiInterceptor2】处理器前方法【MulitiInterceptor3】处理器前方法执行处理器逻辑【MulitiInterceptor3】处理器后方法【MulitiInterceptor2】处理器后方法【MulitiInterceptor1】处理器后方法视图渲染【MulitiInterceptor3】处理器完成方法【MulitiInterceptor2】处理器完成方法【MulitiInterceptor1】处理器完成方法
对于处理器前方法采用先注册先执行,而处理器后方法和完成方法则是先注册后执行的规则。
当我们的拦截器2 的preHandler返回 false的时候,运行就不太一样了。
【MulitiInterceptor1】处理器前方法【MulitiInterceptor2】处理器前方法【MulitiInterceptor1】处理器完成方法
处理器前(preHandle)方法会执行,但是一旦返回 false,则后续的拦截器、处理器和所有拦截器的处理器后(postHandle)方法都不会被执行。完成方法 afterCompletion 则不一样,它只会执行返回 true 的拦截器的完成方法,而且顺序是先注册后执行。
HttpMessageConverter
当一个请求来到时,在处理器执行的过程中,它首先会从 HTTP 请求和上下文环境来得到参数。如果是简易的参数它会以简单的转换器进行转换,而这些简单的转换器(Converter)是 Spring MVC 自身已经提供了的。但是如果是转换 HTTP 请求体(Body),它就会调用 HttpMessageConverter接口的方法对请求体的信息进行转换。
HttpMessageConverter 其实就是将 HttpServletRequest 中的数据, 根据 MediaType 转换成指定格式的数据, 比如我们常见的表单提交 或通过 Json字符串提交数据。
1. FormHttpMessageConverter 支持 MultiValueMap 类型, 并且 MediaType 类型是 "multipart/form-data", 从 InputStream 里面读取数据, 并通过&符号分割, 最后转换成 MultiValueMap, 或 将 MultiValueMap转换成 & 符号连接的字符串, 最后转换成字节流, 输出到远端2. BufferedImageHttpMessageConverter 支持 BufferedImgae 的 HttpMessageConverter, 通过 ImageReader 将 HttpBody 里面的数据转换成 BufferedImage, 或ImageWriter 将ImageReader 转换成字节流输出到 OutputMessage3. StringHttpMessageConverter 支持数据是 String 类型的, 从 InputMessage 中读取指定格式的 str, 或 将数据编码成指定的格式输出到 OutputMessage4. SourceHttpMessageConverter 支持 DOMSource, SAXSource, StAXSource, StreamSource, Source 类型的消息转换器, 在读取的时候, 从 HttpBody 里面读取对应的数据流转换成对应对应, 输出时通过 TransformerFactory 转换成指定格式输出5. ResourceHttpMessageConverter 支持数据类型是 Resource 的数据, 从 HttpBody 中读取数据流转换成 InputStreamResource|ByteArrayResource, 或从 Resource 中读取数据流, 输出到远端6. ProtobufHttpMessageConverter 支持数据类型是 com.google.protobuf.Message, 通过 com.google.protobuf.Message.Builder 将 HttpBody 中的数据流转换成指定格式的 Message, 通过 ProtobufFormatter 将 com.google.protobuf.Message 转换成字节流输出到远端7. ObjectToStringHttpMessageConverter 支持 MediaType是 text/plain 类型, 从 InputMessage 读取数据转换成字符串, 通过 ConversionService 将字符串转换成自定类型的 Object; 或将 Obj 转换成 String, 最后 将 String 转换成数据流8. ByteArrayHttpMessageConverter 支持格式是 byte 类型, 从 InputMessage 中读取指定长度的字节流, 或将 OutputMessage 转换成字节流9. AbstractXmlHttpMessageConverter及其子类 支持从 xml 与 Object 之间进行数据转换的 HttpMessageConverter10. AbstractGenericHttpMessageConverter 支持从 Json 与 Object 之间进行数据转换的 HttpMessageConverter (PS: 主要通过 JackSon 或 Gson)11. GsonHttpMessageConverter 支持 application/*++json 格式的数据, 并通过 Gson, 将字符串转换成对应的数据12. MappingJackson2XmlHttpMessageConverter 持 application/*++json/*+xml 格式的数据, 并通过 JackSon, 将字符串转换成对应的数据
接口源码如下
public interface HttpMessageConverter{ // 是否可读,其中clazz为Java类型,mediaType为HTTP请求类型 boolean canRead(Class clazz, MediaType mediaType);// 判断clazz类型是否能够转换为mediaType媒体类型// 其中clazz为java类型,mediaType为HTTP响应类型 boolean canWrite(Class clazz, MediaType mediaType); // 可支持的媒体类型列表 List getSupportedMediaTypes(); // 当canRead验证通过后,读入HTTP请求信息 T read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; //当canWrite方法验证通过后,写入响应 void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;}
关于HttpMessageConverter的调用逻辑实际上还是比较深的并且是有条件的,当参数又@RequestBody 才会调用read相关方法,当方法注解了@ResponseBody之后,才会调用相关的write方法。
假设我们现在运行了Controller中的一个方法
@PostMapping("/insertUser")@ResponseBodypublic User insertUser(@RequestBody User user){..}
不关心方法内部,而是看看spring是如何让运作起来的,我们从doDispatch mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 开始。
使用的ha是RequestMappingHandlerAdapter,进一步跟踪,会发现调用了其中的invokeHandlerMethod方法
/** * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView} * if view resolution is required. * @since 4.2 * @see #createInvocableHandlerMethod(HandlerMethod) */ @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { //创建一个参数解析工厂,用于解析参数 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); //保存最终需要调用的controller方法,包括bean,参数类型等。 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); //类似于构造一个调用controller的环境,配置一些必要组件 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } ....... // 通过构造的环境调用最终方法 invocableMethod.invokeAndHandle(webRequest, mavContainer); .... } finally { webRequest.requestCompleted(); } }
继续跟踪,发现调用了InvocableHandlerMethod.getMethodArgumentValues来创建参数列表。
/** * Get the method argument values for the current request, checking the provided * argument values and falling back to the configured argument resolvers. *The resulting array will be passed into {@link #doInvoke}. * @since 5.1.2 */ protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { if (ObjectUtils.isEmpty(getMethodParameters())) { return EMPTY_ARGS; } //MethodParameter 类用来描述一个参数 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } //核心方法,判断是否能够找到相应的HandlerMethodArgumentResolver来进行参数处理 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { //处理参数 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled.. if (logger.isDebugEnabled()) { String error = ex.getMessage(); if (error != null && !error.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, error)); } } throw ex; } } return args; }
关于 supportsParameter 实际上就是遍历所有注入的 HandlerMethodArgumentResolver 对象,调用他们的supportsParameter方法输入来判断是否能够处理这个参数。当遍历到RequestResponseBodyMethodProcessor的时候发现能够处理。顺带一提,它的supportsParameter方法很简单
@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); }
就是判断这个参数是不是带了RequestBody。
然后下面代码会进一步调用 RequestResponseBodyMethodProcessor.readWithMessageConverters。
关键来了
//魔法时刻,获取Request的 ServletInputStream ,然后传入HttpMessageConverter进行解析@Override protectedObject readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); //调用所有注入的 HttpMessageConverter 的canRead和 read方法 Object arg = readWithMessageConverters(inputMessage, parameter, paramType); if (arg == null && checkRequired(parameter)) { throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getExecutable().toGenericString(), inputMessage); } return arg; }
由于这里我post传入的json,所以选择了MappingJackson2HttpMessageConverter进行参数解析。
对于非POST的参数,比如get里面的userId参数,那么HandlerMethodArgumentResolver 就不会匹配到RequestResponseBodyMethodProcessor,而是会匹配到RequestParamMethodArgumentResolver。
关于canWrite和write差不多也是这个流程,这里就不再详解了。
PS: 被Spring到底怎么从请求中获取参数的问题困扰很久了,这通源码跟踪下来,总算略有收获……不容易啊。