如何假装成一个服务端开发(十) -- Spring MVC 源码

前言

    在第七篇咱们已经聊过了一些Spring MVC的运行原理,固然大多数人应该仍是和我同样迷迷糊糊,只知道一个大概的运行过程,这一篇,我想要从源码的角度更加进一步去了解Spring MVC的整个运行过程。java

springframework源码调试

    为了可以进一步了解spring的运行过程,debug源码固然是不二的选择。网上搜索的话仍是能找到一些资料的,可是感受方法都比较复杂。这里发现一种比较简单的源码调试方案,拿出来分享一下。git

    首先须要准备一个能够运行的Spring Boot项目,好比咱们redis一篇中准备的db项目就是个不错的选择。固然还须要咱们的调试环境,这里用的是idea做为调试环境。github

    而后还须要下载springframework的源码web

    而后查看咱们springframework依赖的版本。若是直接在pom.xml中查找,发现咱们并无明确指定版本。redis

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    这个东西帮咱们自动选择了版本。spring

    咱们能够在左上角选择项目展现模式,选择projectjson

    而后再External Libraries中查看依赖版本mvc

    

    好比这里咱们依赖了5.1.3.RELEASE版本。app

    而后将咱们从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框架中。

@Configuration
public 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 转换成字节流输出到 OutputMessage
3. StringHttpMessageConverter
    支持数据是 String 类型的, 从 InputMessage 中读取指定格式的 str, 或 将数据编码成指定的格式输出到 OutputMessage
4. 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 之间进行数据转换的 HttpMessageConverter
10. AbstractGenericHttpMessageConverter
    支持从 Json 与 Object 之间进行数据转换的 HttpMessageConverter (PS: 主要经过 JackSon 或 Gson)
11. GsonHttpMessageConverter
    支持 application/*++json 格式的数据, 并经过 Gson, 将字符串转换成对应的数据
12. MappingJackson2XmlHttpMessageConverter
    持 application/*++json/*+xml 格式的数据, 并经过 JackSon, 将字符串转换成对应的数据

    接口源码以下

public interface HttpMessageConverter<T> {
    // 是否可读,其中clazz为Java类型,mediaType为HTTP请求类型
    boolean canRead(Class<?> clazz, MediaType mediaType);

// 判断clazz类型是否可以转换为mediaType媒体类型
// 其中clazz为java类型,mediaType为HTTP响应类型
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // 可支持的媒体类型列表
    List<MediaType> getSupportedMediaTypes();

    // 当canRead验证经过后,读入HTTP请求信息
    T read(Class<? extends T> 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")
@ResponseBody
public 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.
	 * <p>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
	protected <T> Object 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到底怎么从请求中获取参数的问题困扰好久了,这通源码跟踪下来,总算略有收获……不容易啊。

相关文章
相关标签/搜索