博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何伪装成一个服务端开发(十) -- Spring MVC 源码
阅读量:6959 次
发布时间:2019-06-27

本文共 14961 字,大约阅读时间需要 49 分钟。

hot3.png

前言

    在我们已经聊过了一些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

177da8d5b522c7c787884c5d46cdd551857.jpg

    然后再External Libraries中查看依赖版本

a93e525c45e10543cc80dff7cb4d09f2f28.jpg    

    比如这里我们依赖了5.1.3.RELEASE版本。

    然后将我们从github下载的源码切换到对应的tag  (git checkout v5.1.3.RELEASE)

    然后再IDEA中打开项目的File -> Project Structure页面,选择Libraries页面,选择我们要调试的库,比如要DispatcherServlat所在的包在右侧显示大概是这样的

5c6f011c8fcc050c00b31802594aef1c30b.jpg

    我们看到Soures是红色的,表示找不到相关的Source,选中sources,然后点击下方的 + 按钮,最后选择下载下来的spring-framework的源码中响应的module,比如spring-webmvc对应的目录就是053503d0564fcdb995fee80df0f60d6de5b.jpg

    然后在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 {    }}

    拦截器的方法并不多,并且也不难理解。整体流程大概是这样

330c6ed331bef7408c951b118ed9e62522f.jpg

    我们定义一个简单的拦截器

//有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	protected 
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到底怎么从请求中获取参数的问题困扰很久了,这通源码跟踪下来,总算略有收获……不容易啊。

 

转载于:https://my.oschina.net/zzxzzg/blog/3005497

你可能感兴趣的文章
基于.NET平台常用的框架整理
查看>>
springmvc(3)拦截器HandlerInterceptor源码的简单解析
查看>>
初学Python(六)——输入输出
查看>>
用布局方式作出模仿微信选项卡 1
查看>>
Java虚拟机的启动与程序的运行
查看>>
几个有用的java 7特性
查看>>
jS数组
查看>>
php函数xml转化数组
查看>>
五分钟读懂UML类图
查看>>
sql case 函数与详细说明
查看>>
旋转矩阵(模拟)
查看>>
点头(1163)
查看>>
js传输图片路径
查看>>
HDU 6029 Graph Theory【水题】
查看>>
HDU 4053 or ZOJ 3541 The Last Puzzle【区间dp】【经典题】
查看>>
1163: [Baltic2008]Mafia
查看>>
物联网系统框架介绍
查看>>
centos6 安装 ansible_ui
查看>>
搭建区块链浏览器——基于hyperledger fabric 1.0,MySQL容器
查看>>
[BZOJ4372]烁烁的游戏
查看>>