背八股文和 DEBUG 原始碼,差別在哪?
來源:江南一點雨
首先這個小夥伴提的這個問題很好懂:SpringMVC 工作流程是面試八股文中的經典,上面這一套流程看起來是前後端不分時候的工作流程(因為涉及到了頁面渲染),現在都流行前後端分離架構,那麼前後端分離之後,SpringMVC 工作流程還是這樣嗎?
如果你懂一點原始碼分析技巧,這個問題其實可以自己分析去解決,但是如果你只會背八股文,那這個問題就有點棘手了。
1. 知識儲備
首先,想要自己 DEBUG 去解決問題,必須要有知識儲備。不能啥都不懂,就掌握一點 IDEA 上的 DEBUG 技巧,上來就想解決問題,那無疑是天方夜譚。
對於上面這個問題,我們至少需要如下兩個知識儲備。
HandlerAdapter
首先我們需要明白 HandlerAdapter 的作用,是真真正正的瞭解,不是背誦八股文那種瞭解。HandlerAdapter 是一個介面,這個介面中最重要的方法就是 handle 方法。
為什麼會有 HandlerAdapter 存在呢?這是因為我們在 SpringMVC 中定義介面的方式有很多種,大家日常開發用的最多的就是透過 @Controller
或者 @RestController
註解來標記介面,但是這並不是介面唯一的定義方式,我們也可以透過實現 Controller 介面、HttpRequestHandler 介面甚至實現 Servlet 介面來完成介面的定義。
這些不同的介面定義方式,自然就對應了不同的呼叫方式,所以需要一個介面卡,對於框架來說,總是透過呼叫 HandlerAdapter#handle 方法來呼叫介面方法,而不同的介面定義方式則需要分別提供各自的 HandlerAdapter。
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
@Deprecated
long getLastModified(HttpServletRequest request, Object handler);
}
我們看到預設的 HandlerAdapter 有如下實現類,基本上每種實現類都對應了一個介面呼叫方式:
HandlerAdapter#handle 方法的返回值是 ModelAndView,也就是按理說每個介面都應該返回一個 ModelAndView,但是有時候我們的介面並不是返回這個,最典型的就是如果我們透過實現 Servlet 介面來定義介面,Servlet 介面中的方法返回值是 void,顯然就不是 ModelAndView,那麼對於這種情況我們該怎麼處理呢?我們不妨來看下 SimpleServletHandlerAdapter,這個介面卡專門用來處理透過 Servlet 定義的介面:
public class SimpleServletHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
return null;
}
@Override
@SuppressWarnings("deprecation")
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
可以看到,這個原始碼可太簡單了,直接把介面類強轉為 Servlet 然後進行呼叫。至於 handle 方法的返回值,直接返回 null 算了。
這是我們需要的知識儲備一,如果你懂得上面的內容,大概也就能猜出來,如果前後端分離中介面返回了 JSON,那麼執行目標介面的 HandlerAdapter#handle 方法估計也是返回 null。
HttpMessageConverter
第二個知識儲備就是需要明白在 SpringMVC 中 JSON 的生成、解析是誰來完成的。
SpringMVC 返回 JSON 引數特別方便,介面方法直接返回物件就可以了,系統會自動將之轉為 JSON 字串然後寫回去;如果提交的引數是 JSON 字串,我們也只需要在介面中新增 @RequestBody 註解,這樣系統就會自動將 JSON 字串轉為 Java 物件了。
這一切的實現,離不開 HttpMessageConverter。我們先來看看 HttpMessageConverter 介面:
public interface HttpMessageConverter<T> {
// 省略。。。
/**
* Read an object of the given type from the given input message, and returns it.
* @param clazz the type of object to return. This type must have previously been passed to the
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write a given object to the given output message.
* @param t the object to write to the output message. The type of this object must have previously been
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
* default content type of the converter must be used. If not {@code null}, this media type must have
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
* returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
這個介面中最重要的就是 read 和 write 方法。其中 read 方法是將請求引數中的 JSON 字串轉為 Java 物件,write 方法是將請求響應中的 Java 物件轉為 JSON 字串。
每一個 JSON 處理工具都會提供自身的 HttpMessageConverter,以 Spring Boot 中的 jackson 為例,它的 HttpMessageConverter 是 MappingJackson2HttpMessageConverter。
好了,有了如上兩點知識儲備,接下來我們就可以結合 IDEA 中的 DEBUG 技能,快速梳理出問題的答案了。
2. 問題分析
那麼這個問題從哪裡切入呢?
既然服務端要返回 JSON,就必然呼叫到 HttpMessageConverter#write 方法,那麼我們就寫一個返回 JSON 的介面,然後在 MappingJackson2HttpMessageConverter#write 方法上打斷點,因為最終要生成 JSON 必然會經過該方法。然後結合 IDEA 中 DEBUG 的方法呼叫棧,就能大致分析出來。
從這個方法呼叫棧我們可以看出來,確實是呼叫了 HandlerAdapter#handle 方法,從這個位置依次往上,我們就找到了觸發 JSON 生成的方法:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//省略。。。
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
順著這個方法的呼叫棧,我們發現 JSON 的生成是在 invocableMethod.invokeAndHandle
方法中被觸發的,包括 JSON 的寫出都是在這個方法中完成的,這塊程式碼簡單,我就不貼圖了。
那問題來了,JSON 已經寫回去了,現在 handle 方法需要返回 ModelAndView 該怎麼辦呢?這就是接下來 getModelAndView 方法的作用了:
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
//省略。。。
}
這個方法有一句判斷 mavContainer.isRequestHandled()
,看方法名就知道表示檢查請求是否已經被處理了,由於前面已經處理完 JSON 了,所以這個方法就返回 true,這就進而導致返回的 ModelAndView 是一個 null。
繼續跟進方法呼叫棧的提示,看接下來的處理。
在這個位置呼叫了 HandlerAdapter#handle 方法,該方法返回了 null,解下來該去找試圖解析器進行檢視渲染了,檢視渲染則是在接下來的 processDispatchResult 方法上:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
//省略
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
//省略。。。
}
大家可以看到,這裡會判斷這個 ModelAndView 是否為 null,不為 null 的話,會去呼叫 render 方法進行檢視的渲染,這個時候就會去找到檢視解析器,分析檢視,渲染檢視,這個松哥之前也都和大家聊過了。但我們這裡由於 mv 是 null,所以這一步其實是跳過了,也就是沒有去找試圖解析器也沒有去渲染檢視了。
現在再回到本文一開始的問題,相信各位心中已經有答案了。
從這個問題的分析中大家也能看出來,單純的背八股文真的不如自己去讀一讀原始碼理解一下,因為八股文只能解決面試問題,對於工作,對於自身技能的提升作用是有限的。
來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024923/viewspace-3003431/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 別人眼中的程式猿和現實中的程式猿差別在哪?
- 你和牛人到底差在哪裡?
- Debug和Release的區別
- 瀑布式DEBUG Spring MVC原始碼SpringMVC原始碼
- 線上直播原始碼的開發較和其他APP的成本區別在哪?原始碼APP
- 自學程式設計和計算機科班出身的差別在哪?程式設計計算機
- IDEA之如何Debug原始碼跟蹤Idea原始碼
- debug:am trace-ipc原始碼分析原始碼
- MariaDB 和 GreatSQL 效能差異背後的真相SQL
- 網站有沒有安裝SSL證書差別在哪網站
- 看不懂原始碼,一條路debug嗎?原始碼
- 為什麼MacBook要比普通電腦貴?差別在哪裡呢?Mac
- SOLIDWORKS正版軟體與盜版軟體的差別在哪裡?Solid
- 原始碼包和rpm包的區別原始碼
- vue原始碼分析系列之入debug環境搭建Vue原始碼
- dyld背後的故事&原始碼分析原始碼
- 前端基礎:Session 和 Cookie 差別前端SessionCookie
- Python指令碼和網頁有什麼區別?差異介紹!Python指令碼網頁
- c#中Debug和Release的區別實驗C#
- Timer和ScheduledThreadPoolExecutor的區別及原始碼分析thread原始碼
- vscode 如何debug第三方模組原始碼VSCode原始碼
- Cache 和 Buffer 的區別在哪裡?
- 前端和後端的區別在哪?前端後端
- python中分辨int和float的差別Python
- 月入5000和20000的程式設計師,差距到底差在哪裡?程式設計師
- lodash原始碼分析之陣列的差集原始碼陣列
- JVM原始碼分析之MetaspaceSize和MaxMetaspaceSize的區別JVM原始碼
- Spring原始碼--debug分析迴圈依賴--構造器注入Spring原始碼
- Spring Cloud:Zookeeper和Eureka的區別在哪?SpringCloud
- Fabric 1.0原始碼分析(39) policy(背書策略)原始碼
- [原始碼解析] Flink UDAF 背後做了什麼原始碼
- 同樣是搞IT,年薪 15W 和 50W+ 差在哪裡?
- 月薪3000和30000的人差在哪?知道答案後其實很簡單
- 小程式和APP的差別是什麼?APP
- @Component和@Configuration作為配置類的差別
- 大公司和小公司的程式設計師差別在哪?程式設計師能去小公司嗎?程式設計師
- 程式碼差別不大的多專案如何管理?
- 國內外的ERP系統存在顯著的差異,差在哪?