您好,登錄后才能下訂單哦!
如何利用springMVC容器實現一個源碼加載功能?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
springmvc是一個基于servlet容器的輕量靈活的mvc框架,在它整個請求過程中,為了能夠靈活定制各種需求,所以提供了一系列的組件完成整個請求的映射,響應等等處理。這里我們來分析下springMVC的源碼。
首先,spring提供了一個處理所有請求的servlet,這個servlet實現了servlet的接口,就是DispatcherServlet。把它配置在web.xml中,并且處理我們在整個mvc中需要處理的請求,一般如下配置:
<servlet> <servlet-name>spring-servlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-servlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
DispatcherServlet也繼承了FrameworkServlet抽象類,這個抽象類為整個servlet的處理提供了spring容器的支持,讓原本servlet容器的DispatcherServlet擁有能夠利用spring容器組件的能力。上面servlet的初始化參數contextConfigLocation就是DispatcherServlet獲取spring容器的配置環境。FrameworkServlet繼承自org.springframework.web.servlet.HttpServletBean。HttpServletBean實現了servlet容器初始化會調用的init函數。這個init函數會調用FrameworkServlet的初始化加載spring容器方法。方法源碼:
@Override protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { // 初始化spring-servlet容器 this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }
可以看到這里就觸發了spring的對web支持的容器初始化,這里使用的容器為WebApplicationContext.接下來我們就來分析一下整個容器的初始化過程:
protected WebApplicationContext initWebApplicationContext() { // 查看是否在servlet上下文有所有容器需要繼承的根Web容器 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // 如果容器已經加載 if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } if (wac == null) { // 創建容器 wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }
如果已經有容器被創建那就初始化。否則創建容器,創建邏輯:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "'" + ", using parent context [" + parent + "]"); } // 判斷是否是可配置的容器 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } // 如果找到目標容器,并且可配置,然后就開始獲取配置文件并開始初始化 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation()); // 這里是獲取配置文件和完成初始化web容器的入口 configureAndRefreshWebApplicationContext(wac); return wac; }
這里完成了 web容器的相關準備工作,開始正式讀取配置文件加載和初始化容器。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { // 給容器設置一個id if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information if (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } /* 這里在容器被激活后, 并且容器還沒完成初始化之前可以對容器的相關配置做一些修改, 默認給了空實現, 這里子類可以去重寫,能夠獲得在容器初始化之前做 一些處理*/ postProcessWebApplicationContext(wac); // 這里講容器的初始化信息放到一個列表 applyInitializers(wac); // 這里就開始web容器的初始化 wac.refresh(); }
容器的初始化在AbstractApplicationContext,無論是其他的容器,最終都會調用到refresh()函數,這個函數基本定義了整個容器初始化的整個脈絡,這里不展開講,本博客之后應該會詳細分析這塊的邏輯,這里大概的注釋一下每一個函數完成的操作:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 這里主要加載了容器當中一些從其他配置文件讀取的變量 prepareRefresh(); // 獲取容器本身 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 這里完成一些基礎組件的依賴 prepareBeanFactory(beanFactory); try { // 添加 容器初始化之前的前置處理 postProcessBeanFactory(beanFactory); // 調用 前置處理器,其中包含invokeBeanDefinitionRegistryPostProcessors與invokeBeanFactoryPostProcessors兩類前置處理器的調用 invokeBeanFactoryPostProcessors(beanFactory); // 注冊bean被創建之前的前置處理器 registerBeanPostProcessors(beanFactory); // 初始化容器的編碼源 initMessageSource(); // 初始化一些事件監聽器 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // 注冊容器監聽器 registerListeners(); // 初始化所有非懶加載的beans finishBeanFactoryInitialization(beanFactory); // Last step: 事件通知關心容器加載的相關組件 finishRefresh(); } // 部分代碼省略 } } }
自此加載完畢核心容器,然后回到FramewordServlet的initWebApplicationContext函數,在調用createWebApplicationContext完成一系列上面的操作之后,需要mvc servlet組件,入口就是onRefresh(ApplocationContext context)方法。它會調用另一個方法initStrategies(ApplicationContext context)。該方法如下:
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); // 獲取所有的RequestMappings initHandlerMappings(context); // 不同handler的適配器 initHandlerAdapters(context); // 異常解析器 initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
這里我們重點講解initHandlerMappings與initHandlerAdapters函數,因為這兩個是處理servlet請求的入口。
在spring mvc中任何類都可以處理request請求,因為DispacherServlet也是實現了HttpServlet的接口,所以處理請求也是doService里。doService會將請求交給doDispatcher函數處理。然后doDispacher的源碼:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 這里獲取當前請求的處理handler mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } /* 因為handler可以是任何類, 但是我們的DispacherServlet需要一個統一的處理接口,這個接口就是HandlerAdapter, 不同的HandlerMapping可以獲取到各種各樣的Handler, 這個handler最后都必須得到HandlerAdapter的支持才能被DispacherServlet所調用。 擴充一個新的HandlerMapping只需要添加一個新的HandlerAdatper即可,有點抽象工廠模式的味道*/ HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
但是我們發現獲取到的handler并不是Object而是一個HandlerExecutionChain,這個類可以進去查看發現是一堆攔截器和一個handler,主要就是給每一個請求一個前置處理的機會,這里值得一提的是一般來說攔截器和過濾器的區別就是攔截器可以終止后續執行流程,而過濾器一般不終止。過濾器一般是容器級別的,這個handler前置攔截器可以做到更細級別的控制,例如過濾器只定義了init和doFilter,但是這個handler攔截器定義了preHandle和postHandle還有afterCompletion函數,不難理解分別對應不同請求階段的攔截粒度,更加靈活。
獲取處理handler的getHandler代碼:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 這里獲取多個映射方式,一旦找到合適的處理請求的handler即返回 for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } // HandlerExecutionChain包含了 HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
HandlerAdapter處理后返回統一被封裝成ModelAndView對象,這個就是包含試圖和數據的對象,在經過適當的處理:
processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
將頁面與返回的數據返回給瀏覽器完成整個請求過程。以上就是springMVC大概的啟動過程和請求的處理過程,之后本博客還會陸續分析核心的spring源碼,博主一直認為精讀著名框架源碼是在短時間提升代碼能力的捷徑,因為這些輪子包含太多設計思想和細小的代碼規范,這些讀多了都會潛移默化的形成一種本能的東西。設計模式也不斷貫穿其中。
看完上述內容,你們掌握如何利用springMVC容器實現一個源碼加載功能的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。