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的生命周期