Spring mvc 學習筆記
Spring MVC的發展歷程
所有的mvc框架都是從Servlet模型發展而來,因此,先了解Servlet模型
Servlet
在Servlet模型中,請求-響應模型實現依賴於兩大元素:一個繼承於HttpServlet的實現類和一個Servlet Mapping(url mapping)。對於簡單的請求響應需求,Servlet模型的實現簡單直觀,可以滿足需求。
但是,當項目擴大,Servlet增多,相應的配置膨脹,漸漸力不從心。針對這個問題,Spring MVC的解決方案是:提煉一個核心的Servlet覆蓋所有的Http請求。
Spring MVC的DispatchServlet
這個提煉出來的核心Servlet又稱為核心分發器,就是org.springframework.web.servlet.DispatcherServlet,它也是一個Servlet。
DispatchServlet兩大功能:
- 根據規則分發Http請求到不同的Servlet對象上進行處理
- 使用規範化的流程處理Http請求
DispatchServlet對請求的處理流程:
流程化的處理和標準化的組件接口(HanlderMapping, HandlerAdapter, HandlerExceptionResolver, ViewResolver)
- 請求進入框架後會首先到達Spring的DispatcherServlet,它是單例的前端控制器(front controller),被映射到它上面的請求(通過getServletMappings方法)的其後的所有調度工作和流程控制由它負責。
- 為了知道需要把請求發送到哪個控制器,DispatcherServlet會查詢一個或多個處理器映射(HandlerMapping),如果查詢成功,會返回一個HandlerExecutionChain對象(包含一個Handler處理器對象/Controller實例、多個HandlerInterceptor攔截器)
- DispatcherServlet在得到handler/controller實例後,會通過getAdapter方法和handler/controller實例得到一個HandlerAdapter,這個HandlerAdapter會負責調用handler的對應方法處理request,產生response。註意,攔截器就是在這裏被調用的,在HandlerAdapter調用handler處理request之前,會調用前置攔截器,在之後會調用後置攔截器
- handler處理request,產生response的過程是,調用邏輯處理request,如果需要返回視圖,那麽結果會產生一個model和一個視圖名,交給視圖解析器(ViewResolver)來生成一個具體的視圖實現,最後視圖通過model數據渲染輸出,並通過response對象返回給客戶端;如果不需要返回視圖(如返回json),則直接將結果寫入response對象,返回客戶端。
- 在執行過程遇到異常,會交給HandlerExceptionResolver來處理。
DispatcherServlet的init和service方法
根據Servlet規範的定義,Servlet中的兩大核心方法init方法和service方法:
- init方法:在整個系統啟動時運行一次,在這個方法中,往往會對應用程序進行初始化操作,可能包括對容器(WebApplicationContext)的初始化、組件和外部資源的初始化...
- service方法:在整個系統運行的過程中處於偵聽狀態,偵聽並處理所有web請求。因此,在這個方法中會看到對http請求的處理流程。
DispatcherServlet創建並持有自己的WebApplicationContext(實際由FrameworkServlet創建,在init方法中會初始化),WebApplicationContext會讀取配置(默認[servlet-name]-servlet.xml或GetServletConfigClasses()方法返回的@Configuration類)創建並保存如控制器、視圖解析器以及處理器映射等Spring mvc的bean。在doService方法中控制請求流程(主線控制器),並根據配置調用WebApplicationContext中的組件實例。
如果應用程序中有多個Servlet,那麽每個Servlet創建的WebApplicationContext是相互獨立的,但共享一個父容器Root ApplicationContext(由ContextLoaderListener持有)。
1 @Override 2 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { 3 if (logger.isDebugEnabled()) { 4 String requestUri = urlPathHelper.getRequestUri(request); 5 logger.debug("DispatcherServlet with name ‘" + getServletName() + "‘ processing " + request.getMethod() + 6 " request for [" + requestUri + "]"); 7 } 8 9 // Keep a snapshot of the request attributes in case of an include, 10 // to be able to restore the original attributes after the include. 11 Map<String, Object> attributesSnapshot = null; 12 if (WebUtils.isIncludeRequest(request)) { 13 logger.debug("Taking snapshot of request attributes before include"); 14 attributesSnapshot = new HashMap<String, Object>(); 15 Enumeration<?> attrNames = request.getAttributeNames(); 16 while (attrNames.hasMoreElements()) { 17 String attrName = (String) attrNames.nextElement(); 18 if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) { 19 attributesSnapshot.put(attrName, request.getAttribute(attrName)); 20 } 21 } 22 } 23 24 // Make framework objects available to handlers and view objects. 25 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); 26 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 27 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 28 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); 29 30 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); 31 if (inputFlashMap != null) { 32 request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); 33 } 34 request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); 35 request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); 36 37 try { 38 doDispatch(request, response); //這邊最終也是調用了doDispatch方法,該方法主要用來處理SPring框架的具體業務分發邏輯。 39 } 40 finally { 41 // Restore the original attribute snapshot, in case of an include. 42 if (attributesSnapshot != null) { 43 restoreAttributesAfterInclude(request, attributesSnapshot); 44 } 45 } 46 } 47 48 //Spring框架最終的分發都是通過該方法的 49 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 50 HttpServletRequest processedRequest = request; 51 HandlerExecutionChain mappedHandler = null; 52 int interceptorIndex = -1; 53 54 try { 55 ModelAndView mv; 56 boolean errorView = false; 57 58 try { 59 processedRequest = checkMultipart(request); 60 61 // Determine handler for the current request. 62 mappedHandler = getHandler(processedRequest, false); 63 if (mappedHandler == null || mappedHandler.getHandler() == null) { 64 noHandlerFound(processedRequest, response); 65 return; 66 } 67 68 // Determine handler adapter for the current request. 69 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 70 71 // Process last-modified header, if supported by the handler. 72 String method = request.getMethod(); 73 boolean isGet = "GET".equals(method); 74 if (isGet || "HEAD".equals(method)) { 75 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 76 if (logger.isDebugEnabled()) { 77 String requestUri = urlPathHelper.getRequestUri(request); 78 logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); 79 } 80 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 81 return; 82 } 83 } 84 85 // 這裏是處理前置攔截器 86 HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); 87 if (interceptors != null) { 88 for (int i = 0; i < interceptors.length; i++) { 89 HandlerInterceptor interceptor = interceptors[i]; 90 if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { 91 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); 92 return; 93 } 94 interceptorIndex = i; 95 } 96 } 97 98 //處理最終的Action邏輯 99 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 100 101 // Do we need view name translation? 102 if (mv != null && !mv.hasView()) { 103 mv.setViewName(getDefaultViewName(request)); 104 } 105 106 //處理後置攔截器 107 if (interceptors != null) { 108 for (int i = interceptors.length - 1; i >= 0; i--) { 109 HandlerInterceptor interceptor = interceptors[i]; 110 interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv); 111 } 112 } 113 } 114 catch (ModelAndViewDefiningException ex) { 115 logger.debug("ModelAndViewDefiningException encountered", ex); 116 mv = ex.getModelAndView(); 117 } 118 catch (Exception ex) { 119 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 120 mv = processHandlerException(processedRequest, response, handler, ex); 121 errorView = (mv != null); 122 } 123 124 // Did the handler return a view to render? 125 if (mv != null && !mv.wasCleared()) { 126 render(mv, processedRequest, response); 127 if (errorView) { 128 WebUtils.clearErrorRequestAttributes(request); 129 } 130 } 131 else { 132 if (logger.isDebugEnabled()) { 133 logger.debug("Null ModelAndView returned to DispatcherServlet with name ‘" + getServletName() + 134 "‘: assuming HandlerAdapter completed request handling"); 135 } 136 } 137 138 // Trigger after-completion for successful outcome. 139 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); 140 } 141 142 catch (Exception ex) { 143 // Trigger after-completion for thrown exception. 144 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); 145 throw ex; 146 } 147 catch (Error err) { 148 ServletException ex = new NestedServletException("Handler processing failed", err); 149 // Trigger after-completion for thrown exception. 150 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); 151 throw ex; 152 } 153 154 finally { 155 // Clean up any resources used by a multipart request. 156 if (processedRequest != request) { 157 cleanupMultipart(processedRequest); 158 } 159 } 160 }View Code
3. ContextLoaderListener
ContextLoaderListener會讀取applicationContext.xml(由context-param節點的ContextConfigLocation參數指定)或getRootConfigClasses()方法返回的@Configuration類,管理/持有Root WebApplicationContext。Root WebApplicationContext會創建並保存應用程序中的其他bean,如Dao, Service, Log bean。Root ApplicationContext是其他WebApplicationContext的父容器,被它們所共享,子容器可以獲取父容器中的bean,而反之則不可以,因此在子容器自身中找不到的bean,會到父容器中獲取。
ContextLoaderListener在Spring mvc項目中並非必須的,使用它的目的是為了有一個分層架構的容器(parent-child WebapplicationContext),配置它以後可以擁有一個共享的父ApplicationContext容器,被所有child WebApplicationContext共享,避免bean在每個Servlet中重復構造。所以,如果應用程序只有一個dispatcherServlet,ContextLoaderListener是完全可以不用配置的。
可以參閱官方文檔(https://docs.spring.io/spring/docs/5.0.3.BUILD-SNAPSHOT/javadoc-api/org/springframework/web/context/ContextLoaderListener.html)中介紹其作用和2個構造函數部分。
從2個構造函數和官方示例(https://docs.spring.io/spring/docs/5.0.3.BUILD-SNAPSHOT/javadoc-api/org/springframework/web/WebApplicationInitializer.html)可以看出,Root WebApplicationContext可以被ContextLoaderListener創建,也可以被WebApplicationInitializer創建並傳遞給ContextLoaderListener。
4. WebApplicationInitializer
dispatcherServlet和ContextLoaderListener是由誰創建的呢,答案是WebApplicationInitializer,由於WebApplicationInitializer是一個接口,所以準確說是它的實現類或依據配置生成的類。下面是官方示例代碼:
1 public class MyWebAppInitializer implements WebApplicationInitializer { 2 3 @Override 4 public void onStartup(ServletContext container) { 5 // Create the ‘root‘ Spring application context 6 AnnotationConfigWebApplicationContext rootContext = 7 new AnnotationConfigWebApplicationContext(); 8 rootContext.register(AppConfig.class); 9 10 // Manage the lifecycle of the root application context 11 container.addListener(new ContextLoaderListener(rootContext)); 12 13 // Create the dispatcher servlet‘s Spring application context 14 AnnotationConfigWebApplicationContext dispatcherContext = 15 new AnnotationConfigWebApplicationContext(); 16 dispatcherContext.register(DispatcherConfig.class); 17 18 // Register and map the dispatcher servlet 19 ServletRegistration.Dynamic dispatcher = 20 container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext)); 21 dispatcher.setLoadOnStartup(1); 22 dispatcher.addMapping("/"); 23 } 24 25 }View Code
Web.xml或自定義WebApplicationInitializer作為程序的入口,它會根據配置或者實現類代碼創建DispatcherServlet和ContextLoaderListener,並將他們放入到ServletContext(容器)中。
5. ServletContainerInitializer和ServletContext
ServletContext是被誰創建和初始化的呢?ServletContainerInitializer會實例化ServletContext,並將ServletContext委托/傳遞給WebApplicationInitializer,WebApplicationInitializer會在OnStartup中去初始化ServletContext,如上面的示例(添加DispatcherServlet,添加ContextLoaderListener)。
Spring MVC 的配置
1. web.xml or AbstractAnnotationConfigDispatcherServletInitializer
2. SpringMVC的核心配置文件[servlet-name]-servlet.xml or Config class
擴展自AbstractAnnotationConfigDispatcherServletInitializer的任意類會自動地配置DispatcherServlet和Spring應用上下文位於應用程序的Servlet上下文之中。-- 深入:在Servlet3.0環境中,容器會在類路徑中查找實現了javax.servlet.ServletContainerInitializer接口的類用來配置Servlet容器。Spring提供了這個接口的實現(SpringServletContainerInitializer),這個類會又會查找實現WebApplicationInitializer的類,並將配置任務交給它們。而Spring3.2引入了AbstractAnnotationConfigDispatcherServletInitializer類,作為WebApplicationInitializer的一個便利的基礎實現。所以,擴展自AbstractAnnotationConfigDispatcherServletInitializer的類會被容器發現,並用來配置Servlet上下文。
AbstractAnnotationConfigDispatcherServletInitializer會同時創建DispatcherServlet(並註冊到Servlet容器中)和ContextLoaderListener,GetServletConfigClasses()方法返回的帶有@Configuration註解的類將會用來定義DispatcherServlet創建的應用(Spring mvc)上下文中的bean(加載Spring mvc的bean,如控制器、視圖解析器以及處理器映射)。getRootConfigClasses()方法返回的帶有@Configuration註解的類將會用來配置ContextLoaderListener創建的應用上下文中的bean(應用程序中的其他bean,如Dao, Service, Log)。
import javax.servlet.Filter; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletRegistration.Dynamic; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class MyDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /* * 指定ContextLoaderListener的配置類,用於配置ContextLoaderListener創建的應用上下文中的Bean, * 如Dao,Service,Log... */ @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } /* * 指定DispatcherServlet的配置類,用於定義由DispatcherServlet創建的應用上下文中的Bean, * 如Controller,ViewResolver, Handler mapping */ @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { WebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] {"/"}; //所有請求會被映射到DispatcherServlet } //註冊只是映射到DispatcherServlet上的Filter @Override protected Filter[] getServletFilters() { return new Filter[] { new MyFilter() }; } /* * AbstractAnnotationConfigDispatcherServletInitializer將DispatcherServlet註冊到Servlet容 * 器中之後,就會調用customizeRegistration(),並將Servlet註冊後得到的Registration.Dynamic傳遞進來 * 通過重載customizeRegistration()方法,可以對DispatcherServlet進行額外的配置. */ @Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement("/tmp/upload")); } }
添加其他的Servlet和Filter
實現Spring的WebApplicationInitializer接口
1 package com.scott.springboot.demo.config; 2 3 import javax.servlet.ServletContext; 4 import javax.servlet.ServletException; 5 import javax.servlet.ServletRegistration.Dynamic; 6 7 import org.springframework.web.WebApplicationInitializer; 8 9 public class MyServletInitializer implements WebApplicationInitializer { 10 11 @Override 12 public void onStartup(ServletContext servletContext) throws ServletException { 13 // 註冊Servlet 14 Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class); 15 // 映射Servlet 16 myServlet.addMapping("/customer/**"); 17 18 //註冊Filter 19 javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class); 20 //添加Filter映射路徑 21 filter.addMappingForUrlPatterns(null, false, "/customer/*"); 22 } 23 }
總結:
Spring MVC的核心是dispatcherServlet、容器和組件。dispatcherSevlet管理整個請求響應流程,在流程中的各個步驟是接口聲明形式的。繼承於這些接口的組件或能夠被Spring偵測到的組件會根據配置被調用。(IoC)容器管理持有組件。
Ioc
//to do Spring 的包掃描是如何實現的
AOP
連接點(join point):定義何時,做什麽。
切點(pointcut):定義何地。
切面(aspect):連接點+切點,何時何地做什麽。
引入(Introduction):引入允許我們動態地向現有的類添加新方法或屬性。
實現AOP的織入有3種方式:
- 編譯期:切面在目標被編譯時織入。會需要特殊的編譯器,AspectJ是采用這種方式。
- 類加載期:切面在目標類被JVM加載時織入。會需要特殊的類加載器,AspectJ5的加載時織入(LTW,load-time weaving)支持這種方式。
- 運行期:切面在運行時被織入。會需要動態創建代理類,Spring AOP就是采用這種方式。
參考:http://downpour.iteye.com/blog/1341459
Spring mvc 學習筆記