1. 程式人生 > >Spring mvc 學習筆記

Spring mvc 學習筆記

映射 finall 解析 num 2個 接口 ltview 必須 determine

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 學習筆記