Spring Web框架架構的主要部分是DispatcherServlet
。也就是本文中重點介紹的對象。
在本文的第一部分中,咱們將看到基於Spring的DispatcherServlet
的主要概念:前端控制器模式。第二部分將專門介紹Spring應用程序中的執行鏈。接下來是DispatcherServlet類
的解釋。在最後一部分,咱們將嘗試開發一個自定義的dispatcher servlet
。html
請注意,本文分析的DispatcherServlet來自Spring的5.0.0.RC3版本。若是使用不一樣的版本,則可能須要進行幾個調整,其實由於分析的都是比較固定的東西,不多有改的。前端
在進入DispatcherServlet
以前,咱們須要瞭解一些關於它的概念基礎。DispatcherServlet
所隱含的關鍵概念其實就是前端控制器模式。java
此模式爲Web應用程序提供了一箇中心入口點。該集中入口點將系統組件的共同特徵進行從新組合。咱們能夠在那裏找到安全資源,語言切換,會話管理,緩存或輸入過濾的處理程序。這樣作的一個很大的好處是:這個共同的入口點有助於避免代碼重複。ios
所以,從技術上講,前端控制器模式由一個捕獲全部傳入請求的類組成。以後,分析每一個請求以知道哪一個控制器以及哪一個方法應該來處理該請求。git
前端控制器模式有助於對如下詢問作出最佳響應:github
這個前臺控制器模式包含5名參與者:web
由標題能夠看到,前端控制器模式有本身的執行鏈。這意味着它有本身的邏輯來處理請求並將視圖返回給客戶端:spring
請求由客戶端發送。它到達做爲Spring的默認前端控制器的DispatcherServlet
類。數據庫
DispatcherServlet
使用請求處理程序映射來發現將分析請求的控制器(controller設計模式
)。接口org.springframework.web.servlet.HandlerMapping的實現返回一個包含org.springframework.web.servlet.HandlerExecutionChain類的實例。此實例包含可在控制器調用以前或以後調用的處理程序攔截器數組。你能夠在Spring中有關於攔截器的文章中瞭解更多的信息。若是在全部定義的處理程序映射中找不到HandlerExecutionChain
,這意味着Spring沒法將URL與對應的控制器進行匹配。這樣的話會拋出一個錯誤。
如今系統進行攔截器預處理並調用由映射處理器找到的相應的controller(其實就是在找到的controller以前進行一波攔截處理)。在controller處理請求後,DispatcherServlet
開始攔截器的後置處理。在此步驟結束時,它從controller接收ModelAndView實例(整個過程其實就是 request請求
->進入interceptors
->controller
->從interceptors出來
->ModelAndView接收
)。
DispatcherServlet如今將使用的該視圖的名稱發送到視圖解析器。這個解析器將決定前臺的展示內容。接着,它將此視圖返回給DispatcherServlet,其實也就是一個「視圖生成後可調用」的攔截器。
最後一個操做是視圖的渲染並做爲對客戶端request請求的響應。
經過上面講到的前端控制器模式,咱們能夠很輕易的知道DispatcherServlet
是基於Spring
的Web
應用程序的中心點。它須要傳入請求,並在處理程序映射,攔截器,控制器和視圖解析器的幫助下,生成對客戶端的響應。因此,咱們能夠分析這個類的細節,並總結出一些核心要點。
下面是處理一個請求時DispatcherServlet
執行的步驟:
DispatcherServlet
是一個位於org.springframework.web.servlet包中的類,並擴展了同一個包中的抽象類FrameworkServlet
。它包含一些解析器的私有靜態字段(用於本地化,視圖,異常或上傳文件),映射處理器:handlerMapping
和處理適配器:handlerAdapter
(進入這個類的第一眼就能看到的)。DispatcherServlet
很是重要的一個核心點就是是初始化策略的方法(protected void initStrategies(ApplicationContext context))。在調用onRefresh
方法時調用此方法。最後一次調用是在FrameworkServlet
中經過initServletBean
和initWebApplicationContext
方法進行的(initServletBean
方法中調用initWebApplicationContext
,後者調用onRefresh(wac)
)。initServletBean
經過所提供的這些策略生成咱們所須要的應用程序上下文。其中每一個策略都會產生一類在DispatcherServlet
中用來處理傳入請求的對象。
基於篇幅,有些代碼就不給貼示了,請在相應版本的源碼中自行對照查找,此處只給一部分源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* This implementation calls {
@link #initStrategies}.
*/
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
|
須要注意的是,若是找的結果不存在,則捕獲異常NoSuchBeanDefinitionException
(下面兩段代碼的第一段),並採用默認策略。若是在DispatcherServlet.properties文件中初始定義的默認策略不存在,則拋出BeanInitializationException異常(下面兩段代碼的第二段)。默認策略以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* Initialize the LocaleResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to AcceptHeaderLocaleResolver.
*/
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug(
"Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug(
"Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +"': using default [" + this.localeResolver + "]");
}
}
}
|
拋出異常後調用getDefaultStrategy
(由於容器裏都是單例的存在,因此只須要判斷基於這個接口的默認實現實例size爲1便可,兩個以上還能叫默認麼,都有選擇了):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
/**
* Return the default strategy object for the given strategy interface.
* The default implementation delegates to {
@link #getDefaultStrategies},
* expecting a single object in the list.
*
@param context the current WebApplicationContext
*
@param strategyInterface the strategy interface
*
@return the corresponding strategy object
*
@see #getDefaultStrategies
*/
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
List<T> strategies = getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException(
"DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
}
return strategies.get(0);
}
/**
* Create a List of default strategy objects for the given strategy interface.
* The default implementation uses the "DispatcherServlet.properties" file (in the same
* package as the DispatcherServlet class) to determine the class names. It instantiates
* the strategy objects through the context's BeanFactory.
*
@param context the current WebApplicationContext
*
@param strategyInterface the strategy interface
*
@return the List of corresponding strategy objects
*/
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies =
new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className +"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className +"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
|
FrameworkServlet
抽象類擴展了同一個包下的HttpServletBean
,HttpServletBean
擴展了javax.servlet.http.HttpServlet。點開這個類源碼能夠看到,HttpServlet
是一個抽象類,其方法定義主要用來處理每種類型的HTTP
請求:doGet(GET請求)
,doPost(POST)
,doPut(PUT)
,doDelete(DELETE)
,doTrace(TRACE)
,doHead(HEAD)
,doOptions(OPTIONS)
。FrameworkServlet
經過將每一個傳入的請求調度到processRequest(HttpServletRequest request,HttpServletResponse response)來覆蓋它們。processRequest
是一個protected
和final
的方法,它構造出LocaleContext
和ServletRequestAttributes
對象,二者均可以在initContextHolders(request, localeContext, requestAttributes)
以後訪問。全部這些操做的關鍵代碼 請看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
...
/**
* Process this request, publishing an event regardless of the outcome.
* The actual event handling is performed by the abstract
* {
@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause =
null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(),
new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug(
"Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, startTime, failureCause);
}
}
...
private void initContextHolders(HttpServletRequest request,
@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext,
this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes,
this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace(
"Bound request context to thread: " + request);
}
}
|
由上面所看到的,在processRequest
的代碼中,調用initContextHolders方法後,調用protected void doService(HttpServletRequest request,HttpServletResponse response)。doService將一些附加參數放入request(如Flash映射:request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap())
,上下文信息:request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext())
等)中,並調用protected void doDispatch(HttpServletRequest request,HttpServletResponse response)。
doDispatch
方法最重要的部分是處理(handler
)的檢索。doDispatch
調用getHandler()
方法來分析處理後的請求並返回HandlerExecutionChain
實例。此實例包含handler mapping
和`interceptors(攔截器)
。DispatcherServlet
作的另外一件事是應用預處理程序攔截器(applyPreHandle())。若是至少有一個返回false
,則請求處理中止。不然,servlet
使用與 handler adapter
適配(其實理解成這也是個handler
就對了)相應的handler mapping
來生成視圖對象。
doDispatch
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
/**
* Process the actual dispatching to the handler.
* The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
*
@param request current HTTP request
*
@param response current HTTP response
*
@throws Exception in case of any kind of processing failure
*/
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);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.獲取可處理request的Handler,適配器其實還 //是調用的相應的Handler,同樣的功能,具體請參考本人的Spring設計模式中的適配器模式
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.此處就會調用咱們寫的controller來執行咯
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//視圖解析
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException =
new NestedServletException("Handler dispatch failed", err);
}
//此處進行最後一步的視圖渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", 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);
}
}
}
}
|
獲取ModelAndView
實例以查看呈現後,doDispatch
方法調用private void applyDefaultViewName(HttpServletRequest request,ModelAndView mv)。默認視圖名稱根據定義的bean名稱,即viewNameTranslator
。默認狀況下,它的實現是org.springframework.web.servlet.RequestToViewNameTranslator。這個默認實現只是簡單的將URL轉換爲視圖名稱,例如(直接從RequestToViewNameTranslator
獲取):http:// localhost:8080/admin/index.html將生成視圖admin / index。
代碼以下:
下一步是調用後置攔截器(其實就是出攔截器)作的一些處理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
/** RequestToViewNameTranslator used by this servlet */
private RequestToViewNameTranslator viewNameTranslator;
...
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
//看下面註釋
initViewResolvers(context);
initFlashMapManager(context);
}
...
/**
* Initialize the RequestToViewNameTranslator used by this servlet instance.
* <p>If no implementation is configured then we default to DefaultRequestToViewNameTranslator.
*/
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
this.viewNameTranslator =
context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
if (logger.isDebugEnabled()) {
logger.debug(
"Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
if (logger.isDebugEnabled()) {
logger.debug(
"Unable to locate RequestToViewNameTranslator with name '" +
REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
"': using default [" + this.viewNameTranslator +"]");
}
}
}
....
/**
* Translate the supplied request into a default view name.
*
@param request current HTTP servlet request
*
@return the view name (or {@code null} if no default found)
*
@throws Exception if view name translation failed
*/
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}
|
org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
實現的org.springframework.web.servlet.RequestToViewNameTranslator
接口,其內對上段代碼中getDefaultViewName
的實現爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
private static final String SLASH = "/";
private String prefix = "";
private String suffix = "";
private String separator = SLASH;
private boolean stripLeadingSlash = true;
private boolean stripTrailingSlash = true;
private boolean stripExtension = true;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
/**
* Set the prefix to prepend to generated view names.
*
@param prefix the prefix to prepend to generated view names
*/
public void setPrefix(String prefix) {
this.prefix = (prefix != null ? prefix : "");
}
/**
* Set the suffix to append to generated view names.
*
@param suffix the suffix to append to generated view names
*/
public void setSuffix(String suffix) {
this.suffix = (suffix != null ? suffix : "");
}
/**
* Set the value that will replace '{
@code /}' as the separator
* in the view name. The default behavior simply leaves '{
@code /}'
* as the separator.
*/
public void setSeparator(String separator) {
this.separator = separator;
}
...
/**
* Translates the request URI of the incoming {
@link HttpServletRequest}
* into the view name based on the configured parameters.
*
@see org.springframework.web.util.UrlPathHelper#getLookupPathForRequest
*
@see #transformPath
*/
public String getViewName(HttpServletRequest request) {
String lookupPath =
this.urlPathHelper.getLookupPathForRequest(request);
return (this.prefix + transformPath(lookupPath) + this.suffix);
}
/**
* Transform the request URI (in the context of the webapp) stripping
* slashes and extensions, and replacing the separator as required.
*
@param lookupPath the lookup path for the current request,
* as determined by the UrlPathHelper
*
@return the transformed path, with slashes and extensions stripped
* if desired
*/
protected String transformPath(String lookupPath) {
String path = lookupPath;
if (this.stripLeadingSlash && path.startsWith(SLASH)) {
path = path.substring(
1);
}
if (this.stripTrailingSlash && path.endsWith(SLASH)) {
path = path.substring(
0, path.length() - 1);
}
if (this.stripExtension) {
path = StringUtils.stripFilenameExtension(path);
}
if (!SLASH.equals(this.separator)) {
path = StringUtils.replace(path, SLASH,
this.separator);
}
return path;
}
}
|
如今,servlet
知道應該是哪一個視圖被渲染。它經過private void processDispatchResult(HttpServletRequest request,HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception)方法來進行最後一步操做 - 視圖渲染。
首先,processDispatchResult
檢查它們是否有參數傳遞異常。有一些異常的話,它定義了一個新的視圖,專門用來定位錯誤頁面。若是沒有任何異常,該方法將檢查ModelAndView實例
,若是它不爲null
,則調用render
方法。
渲染方法protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception
。跳進此方法內部,根據定義的視圖策略,它會查找獲得一個View類
實例。它將負責顯示響應。若是沒有找到View
,則會拋出一個ServletException異常
。有的話,DispatcherServlet
會調用其render
方法來顯示結果。
其實能夠說成是後置攔截器(進入攔截器前置攔截處理->controller處理->出攔截器以前的此攔截器的後置處理),也就是在請求處理的最後一個步驟中被調用。
下面是processDispatchResult
和render(渲染)
的相關代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception)
throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug(
"ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler !=
null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv !=
null);
}
}
// 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.isDebugEnabled()) {
logger.debug(
"Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response,
null);
}
}
...
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
*
@param mv the ModelAndView to render
*
@param request current HTTP servlet request
*
@param response current HTTP servlet response
*
@throws ServletException if view is missing or cannot be resolved
*
@throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(
this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug(
"Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(
"Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() +
"'", ex);
}
throw ex;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug(
"ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler !=
null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv !=
null);
}
}
// 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.isDebugEnabled()) {
logger.debug(
"Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response,
null);
}
}
/**
* Render the given ModelAndView.
* This is the last stage in handling a request. It may involve resolving the view by name.
*
@param mv the ModelAndView to render
*
@param request current HTTP servlet request
*
@param response current HTTP servlet response
*
@throws ServletException if view is missing or cannot be resolved
*
@throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException(
"Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug(
"Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(
"Error rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'", ex);
}
throw ex;
}
}
|
在這部分中,你須要記住的是咱們定義了兩個上下文:一個用於應用程序,另外一個用於Web應用程序。他們有什麼區別?應用程序上下文包含全部通用配置,好比service定義,數據庫配置。Web應用程序上下文定義全部與Web相關的組件,好比controllers
或視圖解析器。
咱們已經瞭解了DispatcherServlet
的理論知識。經過文中的這些實用要點,咱們能夠編寫本身的servlet來分派處理請求。一樣的,咱們也將按步進行,從捕獲請求開始,以視圖渲染結束。
經過上面的描述,爲了捕獲請求,咱們須要覆蓋doService
方法:
1
2
3
4
5
6
7
8
|
public class CustomDispatcherServlet extends FrameworkServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomDispatcherServlet.class);
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
LOGGER.debug(
"[CustomDispatcherServlet] I got the request !");
}
}
|
這樣,在咱們的日誌文件中,咱們應該能夠找到一條「[CustomDispatcherServlet]I got the request!」。接着,咱們繼續添加在DispatcherServlet
中doDispatch方法
所應該作的一些工做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
LOGGER.debug(
"[CustomDispatcherServlet] I got the request !");
try {
LOGGER.debug(
"[CustomDispatcherServlet] doService");
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
initContextHolders(request, localeContext, requestAttributes);
}
catch (Exception e) {
e.printStackTrace();
}
}
|
這個方法是作什麼的?首先,它爲構建一個Locale實例
用來接收請求。第二步是初始化org.springframework.web.context.request.ServletRequestAttributes實例。它是RequestAttributes
接口的實現,和本地化在同一級別。經過這個,咱們能夠訪問servlet
請求的對象和會話對象,而沒必要區分會話和全局會話。最後,咱們調用初始化context holders的initContextHolders()
方法,即從應用程序經過LocaleContextHolder
和RequestContextHolder
靜態方法(分別爲:getLocaleContext和getRequestAttributes
)訪問請求屬性和區域設置的對象。
當請求被攔截,一些基本的設置就緒的時候。咱們發現咱們尚未執行鏈和處理器適配器。咱們能夠經過如下代碼進行:
1
2
3
4
5
6
7
8
9
|
private HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest request) throws Exception {
for (HandlerMapping mapper : this.handlerMappings) {
HandlerExecutionChain executionChain = mapper.getHandler(request);
if (executionChain != null) {
return executionChain;
}
}
throw new Exception("Execution chain wasn't be found in provided handler mappings: "+this.handlerMappings);
}
|
經過執行鏈,咱們能夠經過 handler adapter將處理當前請求。看如下代碼:
1
2
3
4
5
6
7
8
9
10
|
protected HandlerAdapter getHandlerAdapter(Object executionChain) throws ServletException {
for (HandlerAdapter adapter : this.handlerAdapters) {
LOGGER.debug(
"[CustomDispatcherServlet] "+adapter + " is instanceof HandlerMethod ? "+(adapter instanceof HandlerMethod));
if (adapter.supports(executionChain)) {
return adapter;
}
}
throw new ServletException("Handler adapter was not found from adapters list :"+this.handlerAdapters);
}
|
只有應用程序上下文中定義的適配器(this.handlerAdapter
)支持適配所生成的執行鏈(adapter.supports
)才能夠返回咱們想要的適配器。最後,咱們能夠返回到咱們的doService
方法並操做它們來渲染視圖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
ModelAndView modelView = adapter.handle(request, response, executionChain.getHandler());
Locale locale =
this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view =
null;
if (!modelView.isReference()) {
throw new UnsupportedOperationException("Only view models defined as references can be used in this servlet");
}
for (ViewResolver viewResolver : this.viewResolvers) {
view = viewResolver.resolveViewName(modelView.getViewName(), locale);
if (view != null) {
break;
}
}
if (view == null) {
throw new ServletException("Could not resolve view with name '" + modelView.getViewName() + "' in servlet with name '" + getServletName() + "'");
}
view.render(modelView.getModelMap(), request, response);
|
咱們的servlet中簡化了渲染。實際上,咱們僅處理ModelAndView
的引用對象。這意味着ModelAndView
是一個String
的實例,用來表示要解析的視圖模型,例如:咱們定義好幾個模板解析器(好比freemaker
,Thymeleaf
),而後查看其配置。在這個檢查以後,咱們迭代當前視圖解析器。可以生成View實例的第一個解析器被視爲處理過的請求中使用的解析器。最後,咱們檢查視圖是否正確生成。拿到view實例後,咱們調用其render()方法來在屏幕中顯示請求處理結果。
在這部分中,咱們將描述和代碼部分限制在最低限度。只是爲了把Spring的整個過程給集中呈現如下,達到更好的理解,其實就是在Servlet中的service方法內作些對request和response的文章而已了。
本文介紹了Spring Web應用程序的中心點,一個調度器servlet。請記住,它是一個處理全部傳入請求並將視圖呈現給用戶的類。在重寫以前,你應該熟悉執行鏈,handler mapping 或handler adapter等概念。請記住,第一步要作的是定義在調度過程當中咱們要調用的全部元素。handler mapping 是將傳入請求(也就是它的URL)映射到適當的controller。最後提到的元素,一個handler適配器,就是一個對象,它將經過其內包裝的handler mapping將請求發送到controller。此調度產生的結果是ModelAndView類的一個實例,後面被用於生成和渲染視圖。
原文: Spring5源碼解析-論Spring DispatcherServlet的生命週期