✋點擊「面試交流」加入交流群✋
「置頂公眾號」,每天推送面試專題
在 SpringBoot 之前,幾乎所有的 Web 應用都是已 web.xml 為入口的,Spring MVC 也不例外,學習過 Servlet 的應該都理解,Spring MVC 其實就是對 Servlet 接口,Servlet 規範的一種實現。Servlet 提供了五個接口,其中兩個接口最為核心,分別是 init 方法和 service 方法。init方法是在伺服器裝入 Servlet 時執行,在 Servlet 的生命周期中,它只執行一次。service方法和客戶端的請求相關,每當一個客戶端發生一次請求,請求一個 HttpServlet 對象,該對象的 service() 方法就會被調用。Spring MVC 的入口是從 web.xml 中配置的<servlet>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
由此得知,該請求該由哪個Controller來處理。
1. DispatcherServlet調用HandlerAdapter處理器適配器,告訴處理器適配器應該要去執行哪個Controller。2. HandlerAdapter處理器適配器去執行Controller並得到ModelAndView,並層層返回給DispatcherServlet。3. DispatcherServlet將ModelAndView交給ViewReslover視圖解析器解析,然後返回真正的視圖。4. DispatcherServlet將模型數據填充到視圖中。5. DispatcherServlet將結果響應給用戶。
其中,對於 HandlerMapping 和 HandlerAdapter 在閱讀源碼的過程中可能是不太好理解的。HandlerMapping是一個接口,這個接口返回的是一個請求訪問時處理器映射器會返回具體的執行鏈(HandlerExecutionChain),其中包括攔截器和映射器,只是找到並不執行。
執行鏈這裡用到了設計模式中的責任鏈模式,每一個責任鏈的負責人只需要把自己的任務處理好就好。
而HandlerMapping為什麼是個接口呢,是因為 Spring MVC提供了三種不同的書寫處理器映射器的方法,只不過我們最常用的是通過註解,通過@Controller,@RequestMapping的方式去寫我們的Controller。
HandlerAdapter:
和 HandlerMapping 一樣,HandlerAdapter 也是個接口。HandlerAdapter 的意思的是適配器,用到的也是設計模式中的適配器模式,在 Spring MVC 中針對不同的 Handler 需要不同的適配器,例如對於@RequestMapping類型的 Handler 需要 RequestMappingHandlerAdapter 來處理,適配之後通過調用接口的 handle 方法就可以執行對應的方法了。
代碼實現不管是 Spring 還是 Spring MVC,又或是 Mybatis,Spring Data等等。其實在閱讀源碼或者自己實現的過程中會發現,這些提高開發效率的,封裝型的框架,從頭到尾離不開的就是 Java 的反射以及 Java 的動態代理。結合反射以及AOP的思想,根據上面 Servlet 接口和 Spring mvc 流程的介紹以及平時對 Spring MVC 的使用,即使我們不看 Spring MVC 的源碼其實也能把我們經常使用的功能簡單的實現了。這裡對於HandlerMapping 和 HandlerAdapter 我們也不需要設計的如此複雜,只需要實現我們平時最常用的一種就好。流程設計想像一下,我們在開發某個系統,我們寫好了我們的 Spring MVC 控制層的代碼,但是我們沒有引入任何依賴,接下來怎麼讓我們的代碼 Run 起來呢?1. 第一步當然還是要從web.xml入手,和 Spring MVC 一樣,我們需要配置一個我們自己的 DispatcherServlet,這個 Servlet 繼承自 HttpServlet。2. 創建完自己的 Servlet 之後就是重寫上面提到過的兩個核心方法:在 Servlet 的生命周期中,init 只執行一次,對於我們編寫好的代碼,我們需要把所有的urlPath以及我們的控制器和控制器內的方法做一個映射,這樣每次客戶端發起一次請求,調用 service 方法的時候可以通過這個 mapping 映射找到對應它該執行的方法。(由於功能比較簡單另外沒有實現 Interceptor 攔截器的功能,所以沒有使用 Spring MVC 使用的執行鏈的形式)
3. 找到方法後就該執行了,但是執行前,方法需要的參數我們還沒有填充。
參數分為很多種,有用@RequestParam修飾的基本數據類型,有數組,有Map,有對象等等。這裡 Spring mvc 用到的是設計模式中的策略模式,針對不同類型的參數會有不同的 Resolver。策略模式的使用場景是對於系統中的多個類或是說多個場景,用來區分它們的只是他們的行為不同,像我們要做的參數的解析,數據源都是 HttpServletRequest 的 Attribute,只是我們對於 Attribute 的處理行為不同,我們需要把它填充到不同類型的參數上而已。4. 在對 Method 的參數進行填充後,一切準備就緒了,這時候執行 Method 就可以得到相應的返回值了,返回值就是我們需要的視圖。我們常用的返回類型有兩種,一種是根據路徑直接返回一個指定的視圖,另外一種是我們平時在Spring MVC中用@ResponseBody或者@RestController修飾的直接返回給前端一個JSON形式的串。這裡很簡單,其實就是根據不同的情況調用 Servlet 給我們提供的方法。
流程圖代碼結構HandleMappingHandleMapping 的功能如上面所說,只是根據請求找到我們對應處理請求的 handler。這個類主要有兩個函數,第一個是初始化,將代碼中所有被@Controller,@RequestMapping修飾的類和方法,以@RequestMapping的值作為key,以 Method 作為 value,初始化一個map。第二個就是根據 key 在剛才初始化的map中獲取對應的 Method。public class HandleMapping {
private static final Map<String, HandlerMethod> mappings = new HashMap<>();
public static void init() {
Set<Class<?>> controllerSet = ReflectionUtils.getAllClass(Controller.class);
controllerSet.forEach((controller) -> {
RequestMapping requestMappingAnnotation = controller.getAnnotation(RequestMapping.class);
if (requestMappingAnnotation == null) {
throw new DumpException("controller '" + controller.getName() + "' must have a '@RequestMapping' annotation");
}
String parentPath = requestMappingAnnotation.value();
Method[] methods = controller.getMethods();
for (Method method : methods) {
RequestMapping methodRequestMappingAnnotation = method.getAnnotation(RequestMapping.class);
if (methodRequestMappingAnnotation == null) {
continue;
}
String path = methodRequestMappingAnnotation.value();
try {
mappings.put(parentPath + path, new HandlerMethod(controller.newInstance(), method));
} catch (Exception e) {
throw new DumpException("init controller failed,can not create instance for controller '" + controller.getName() + "'", e);
}
}
});
}
public static HandlerMethod getHandler(String url) {
HandlerMethod handleMethod = mappings.get(url);
if (handleMethod == null) {
throw new DumpException("path '" + url + "' can not find handle");
}
return handleMethod;
}
}
HandlerMethodArgumentResolver
public interface HandlerMethodArgumentResolver {
Boolean support(Parameter parameter);
Object resolveArgument(HttpServletRequest request, Class<?> requiredType, Parameter parameter);
}
1. 方法用了@ResponseBody修飾。
對於這種返回值,通過調用response.getWriter().print()來將返回值寫入 response 中。2. 方法沒用@ResponseBody修飾,返回值含有redirect:前綴。
這代表該請求是一個重定向請求,這裡用response.sendRedirect()將請求重定向。
3. 方法沒用@ResponseBody修飾,返回值也不含有redirect:前綴。
這種返回值代表直接返回一個具體的視圖,直接調用requestDispatcher.forward()即可。DumpServletDispatcher準備完成,在自己的 Servlet(繼承自 HttpServlet) 中完成上述流程即可。其中 HandleMapping 的初始化放在 init() 中執行。public class DumpServletDispatcher extends HttpServlet {
@Override
public final void init() {
HandleMapping.init();
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI();
HandlerMethod handleMethod = HandleMapping.getHandler(uri);
HandleMethodAdapter handleMethodAdapter = new HandleMethodAdapter();
handleMethodAdapter.handle(req, resp, handleMethod);
}
}
本文作者「袁廣鑫」,歡迎關注作者的知乎:
https://zhuanlan.zhihu.com/p/139751932 專注於 Java 技術分享,點擊閱讀原文即可關注。
公眾號運營至今,離不開小夥伴們的支持。為了給小夥伴們提供一個互相交流的平臺,特地開通了官方交流群。掃描下方二維碼備註 進群 後獲取進群通道。