前言
要深入理解spring mvc的工作流程,就需要先了解spring mvc的架构:
从上图可以看到 前端控制器dispatcherservlet在其中起着主导作用,理解了dispatcherservlet 就完全可以说弄清楚了spring mvc。
dispatcherservlet作为spring用于处理web请求注册的唯一一个servlet,所有的请求都是经由dispatcherservlet进行分发处理的。本文主要讲解dispatcherservlet是如何对请求进行分发,处理,并且生成相应的视图的。
1. 整体结构
在httpservlet中,其对不同方式的请求进行了分发,比如对于get请求,其提供了doget()方法,对于post请求,其提供了dopost()方法等等。通过这种方式,子类可以针对于当前请求的方式实现不同的方法即可。但是在dispatcherservlet中,由于需要使用同一的方式对不同的请求进行处理,因而其对各个请求方式进行了整合,如下就是dispatcherservlet针对get和post请求所编写的同一处理逻辑:
1
2
3
4
5
6
7
8
9
10
11
|
@override
protected final void doget(httpservletrequest request, httpservletresponse response)
throws servletexception, ioexception {
processrequest(request, response);
}
@override
protected final void dopost(httpservletrequest request, httpservletresponse response)
throws servletexception, ioexception {
processrequest(request, response);
}
|
可以看到,无论是get请求还是post请求,dispatcherservlet都是委托给了processrequest()方法处理,对于其他的请求方式,其处理方式也是类似的。通过这种方式,dispatcherservlet将各个请求整合在了一起,虽然整合在了一起,但是request中也还是保存有当前请求的请求方式的,因而保存了后续对请求进行分发的能力。这里我们直接看processrequest()方法是如何处理各个请求的:
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
|
protected final void processrequest(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception {
long starttime = system.currenttimemillis();
throwable failurecause = null ;
// 获取先前请求的localecontext
localecontext previouslocalecontext = localecontextholder.getlocalecontext();
// 获取当前请求的localecontext,其中保存了当前请求的locale信息
localecontext localecontext = buildlocalecontext(request);
// 获取先前请求的attributes信息
requestattributes previousattributes = requestcontextholder.getrequestattributes();
// 获取当前请求的attributes信息,其中保存了当前请求的各个属性数据
servletrequestattributes requestattributes =
buildrequestattributes(request, response, previousattributes);
// 获取当前请求的webasyncmanager,这只有在当前请求是请求的异步任务时才会真正用到
webasyncmanager asyncmanager = webasyncutils.getasyncmanager(request);
// 注册异步任务的拦截器,如果请求的是异步任务,这个拦截器可以拦截异步任务的前置,后置和异常等情况
asyncmanager.registercallableinterceptor(frameworkservlet. class .getname(),
new requestbindinginterceptor());
// 将当前请求的locale,attributes信息初始化到对应的threadlocal对象中,用于后续使用
initcontextholders(request, localecontext, requestattributes);
try {
// 对当前请求进行分发
doservice(request, response);
} catch (servletexception | ioexception ex) {
failurecause = ex;
throw ex;
} catch (throwable ex) {
failurecause = ex;
throw new nestedservletexception( "request processing failed" , ex);
} finally {
// 在请求完成之后,判断当前请求的locale和attributes信息是否需要继承,如果需要继承,
// 则会将locale信息设置到inheritablelocalecontextholder中,而将attributes
// 信息设置到inheritablerequestattributesholder中;否则就会移除对应的信息,
// 而只为当前请求的contextholder设置相应的属性
resetcontextholders(request, previouslocalecontext, previousattributes);
if (requestattributes != null ) {
// 调用已注册的在当前请求被销毁时的回调函数,并且更新session中当前请求所更新的属性
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, response, starttime, failurecause);
}
}
|
可以看到,processrequest()方法主要是对locale和attributes信息进行了处理,然后就通过doservice()方法对请求再次进行了分发。我们这里继续阅读doservice()方法的源码:
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
|
@override
protected void doservice(httpservletrequest request, httpservletresponse response) throws exception {
if (logger.isdebugenabled()) {
string resumed = webasyncutils.getasyncmanager(request).hasconcurrentresult()
? " resumed" : "" ;
logger.debug( "dispatcherservlet with name '" + getservletname() + "'" + resumed
+ " processing " + request.getmethod() + " request for ["
+ getrequesturi(request) + "]" );
}
// 这里主要是判断当前请求是否为include请求,如果是include请求,那么就会将当前请求中的
// 数据都放入一个快照中,在当前请求完成之后,会从该块中中取出数据,然后将其重新加载到
// 当前request中,以便request进行后续的处理。这里默认情况下是会对所有的属性进行处理的,
// 因为cleanupafterinclude默认值为true,如果将其设置为false,那么就只会对spring框架
// 相关的属性进行处理
map<string, object> attributessnapshot = null ;
if (webutils.isincluderequest(request)) {
attributessnapshot = new hashmap<>();
enumeration<?> attrnames = request.getattributenames();
while (attrnames.hasmoreelements()) {
string attrname = (string) attrnames.nextelement();
if ( this .cleanupafterinclude
|| attrname.startswith(default_strategies_prefix)) {
attributessnapshot.put(attrname, request.getattribute(attrname));
}
}
}
// 这里分别将applicationcontext,loacleresolver,themeresolver和themesource等
// bean添加到当前request中
request.setattribute(web_application_context_attribute, getwebapplicationcontext());
request.setattribute(locale_resolver_attribute, this .localeresolver);
request.setattribute(theme_resolver_attribute, this .themeresolver);
request.setattribute(theme_source_attribute, getthemesource());
// 这里flashmapmanager主要的作用在于当请求如果是重定向的请求,那么可以将一些属性保存在flashmap
// 中,然后通过flashmapmanager进行管理,从而在重定向之后能够获取到重定向之前所保存的请求
if ( this .flashmapmanager != null ) {
// 在当前请求中获取flashmap数据,如果不是重定向之后的请求,那么这里获取到的就是空值
flashmap inputflashmap =
this .flashmapmanager.retrieveandupdate(request, response);
if (inputflashmap != null ) {
// 将获取到的flashmap数据保存在request中
request.setattribute(input_flash_map_attribute,
collections.unmodifiablemap(inputflashmap));
}
// 设置默认的flashmap和flashmapmanager
request.setattribute(output_flash_map_attribute, new flashmap());
request.setattribute(flash_map_manager_attribute, this .flashmapmanager);
}
try {
// 这里才是真正的对请求进行分发处理的位置
dodispatch(request, response);
} finally {
// 判断当前请求不是一个异步任务的请求,但是是一个include请求,那么就会重新加载
// 请求之前保存的快照数据
if (!webasyncutils.getasyncmanager(request).isconcurrenthandlingstarted()) {
if (attributessnapshot != null ) {
restoreattributesafterinclude(request, attributessnapshot);
}
}
}
}
|
这里的doservice()方法也还没有对请求进行真正的处理,其首先判断了当前请求是不是一个include请求,如果是include请求,那么就将请求的属性都保存在一个快照中,以便请求完成之后能够重新进行加载;然后会判断当前是否是一个重定向之后的请求,如果是重定向之后的请求,那么其flashmapmanager就不是空的,此时会将重定向之前保存的属性重新加载到当前请求中;最后doservice()方法才会调用dodispatch()方法进行请求的分发和处理。如下是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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
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 {
// 这里判断当前请求是否为一个文件请求,这里的判断方式就是要求当前请求满足两点:①请求
// 方式是post;②判断contenttype是否以multipart/开头。如果满足这两点,那么就认为当前
// 请求是一个文件请求,此时会将当前请求的request对象封装为一个
// multiparthttpservletrequest对象,这也是我们在定义文件请求的controller时
// 能够将request参数写为multiparthttpservletrequest的原因。这里如果不是文件请求,
// 那么会将request直接返回。
processedrequest = checkmultipart(request);
// 这里判断原始request与转换后的request是否为同一个request,如果不是同一个,则说明
// 其是一个文件请求
multipartrequestparsed = (processedrequest != request);
// 这里gethandler()方法就是通过遍历当前spring容器中所有定义的handlermapping对象,
// 通过调用它们的gethandler()方法,看当前的handlermapping能否将当前request映射
// 到某个handler,也就是某个controller方法上,如果能够映射到,则说明该handler能够
// 处理当前请求
mappedhandler = gethandler(processedrequest);
if (mappedhandler == null ) {
// 如果每个handlermapping都无法找到与当前request匹配的handler,那么就认为
// 无法处理当前请求,此时一般会返回给页面404状态码
nohandlerfound(processedrequest, response);
return ;
}
// 通过找到的handler,然后在当前spring容器中找到能够支持将当前request请求适配到
// 找到的handler上的handleradapter。这里需要找到这样的适配器的原因是,我们的handler
// 一般都是controller的某个方法,其是一个java方法,而当前request则是一种符合http
// 协议的请求,这里是无法直接将request直接应用到handler上的,因而需要使用一个适配器,
// 也就是这里的handleradapter。由于前面获取handler的时候,不同的handlermapping
// 所产生的handler是不一样的,比如reqeustmappinghandlermappin*生的handler是一个
// handlermethod对象,因而这里在判断某个handleradapter是否能够用于适配当前handler的
// 时候是通过其supports()方法进行的,比如requestmappinghandleradapter就是判断
// 当前的handler是否为handlermethod类型,从而判断其是否能够用于适配当前handler。
handleradapter ha = gethandleradapter(mappedhandler.gethandler());
string method = request.getmethod();
boolean isget = "get" .equals(method);
// 这里判断请求方式是否为get或head请求,如果是这两种请求的一种,那么就会判断
// 当前请求的资源是否超过了其lastmodified时间,如果没超过,则直接返回,
// 并且告知浏览器可以直接使用缓存来处理当前请求
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 ;
}
}
// 这里在真正处理请求之前会获取容器中所有的拦截器,也就是handlerinterceptor对象,
// 然后依次调用其prehandle()方法,如果某个prehandle()方法返回了false,那么就说明
// 当前请求无法通过拦截器的过滤,因而就会直接出发其aftercompletion()方法,只有在
// 所有的prehandle()方法都返回true时才会认为当前请求是能够使用目标handler进行处理的
if (!mappedhandler.applyprehandle(processedrequest, response)) {
return ;
}
// 在当前请求通过了所有拦截器的预处理之后,这里就直接调用handleradapter.handle()
// 方法来处理当前请求,并且将处理结果封装为一个modelandview对象。该对象中主要有两个
// 属性:view和model,这里的view存储了后续需要展示的逻辑视图名或视图对象,而model
// 中则保存了用于渲染视图所需要的属性
mv = ha.handle(processedrequest, response, mappedhandler.gethandler());
// 如果当前是一个异步任务,那么就会释放当前线程,等待异步任务处理完成之后才将
// 任务的处理结果返回到页面
if (asyncmanager.isconcurrenthandlingstarted()) {
return ;
}
// 如果返回的modelandview对象中没有指定视图名或视图对象,那么就会根据当前请求的url
// 来生成一个视图名
applydefaultviewname(processedrequest, mv);
// 在请求处理完成之后,依次调用拦截器的posthandle()方法,对请求进行后置处理
mappedhandler.applyposthandle(processedrequest, response, mv);
} catch (exception ex) {
dispatchexception = ex;
} catch (throwable err) {
// 将处理请求过程中产生的异常封装到dispatchexception中
dispatchexception = new nestedservletexception( "handler dispatch failed" ,
err);
}
// 这里主要是请求处理之后生成的视图进行渲染,也包括出现异常之后对异常的处理。
// 渲染完之后会依次调用拦截器的aftercompletion()方法来对请求进行最终处理
processdispatchresult(processedrequest, response, mappedhandler, mv,
dispatchexception);
} catch (exception ex) {
// 如果在上述过程中任意位置抛出异常,包括渲染视图时抛出异常,那么都会触发拦截器的
// aftercompletion()方法的调用
triggeraftercompletion(processedrequest, response, mappedhandler, ex);
} catch (throwable err) {
triggeraftercompletion(processedrequest, response, mappedhandler,
new nestedservletexception( "handler processing failed" , err));
} finally {
// 如果当前异步任务已经开始,则触发异步任务拦截器的afterconcurrenthandlingstarted()方法
if (asyncmanager.isconcurrenthandlingstarted()) {
if (mappedhandler != null ) {
mappedhandler.applyafterconcurrenthandlingstarted(processedrequest,
response);
}
} else {
// 如果当前是一个文件请求,则清理当前request中的文件数据
if (multipartrequestparsed) {
cleanupmultipart(processedrequest);
}
}
}
}
|
这里dodispatch()方法是进行请求分发和处理的主*分,其主要分为如下几个步骤:
- 判断当前是否为文件请求,如果是,则将request对象类型转换为multiparthttpservletrequest;
- 在handlermapping中查找能够处理当前request的handlermapping,并且获取能够处理当前请求的handler;
- 根据获取到的handler,查找当前容器中支持将当前request适配到该handler的handleradapter;
- 应用容器中所有拦截器的prehandle()方法,只有在所有的prehandle()方法都通过之后才会将当前请求交由具体的handler进行处理;
- 调用handleradapter.handle()方法将request适配给获取到的handler进行处理;
- 应用容器中所有拦截器的posthandle()方法,以对当前请求进行后置处理;
- 根据处理后得到的modelandview对象对视图进行渲染;
- 应用容器中所有拦截器的aftercompletion()方法,以对当前请求进行完成处理。
2. handler获取
从前面的步骤可以看出,请求的具体处理过程主要是通过handlermapping根据当前request获取到对应的handler,然后交由handleradapter将request适配给该handler进行处理,并将处理结果封装为一个modelandview对象,最后将该modelandview对象渲染出来。这里我们首先看handlermapping根据request查找具体的handler的过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@nullable
protected handlerexecutionchain gethandler(httpservletrequest request) throws exception {
if ( this .handlermappings != null ) {
// 遍历当前容器中所有的handlermapping对象,调用其gethandler()方法,如果其能够根据
// 当前request获取一个handler,那么就直接返回。
for (handlermapping hm : this .handlermappings) {
if (logger.istraceenabled()) {
logger.trace(
"testing handler map [" + hm + "] in dispatcherservlet with name '"
+ getservletname() + "'" );
}
handlerexecutionchain handler = hm.gethandler(request);
if (handler != null ) {
return handler;
}
}
}
return null ;
}
|
这里的逻辑比较简单,就是遍历当前容器中所有的handlermapping对象,然后依次判断其是否能够根据当前request获取一个handler,如果能够获取就直接使用该handler。这里关于handlermapping将request映射为handler的过程可以阅读本人之前的文章:spring mvc之requestmappinghandlermapping匹配。
3. handleradapter获取与请求处理
在获取到具体的handler之后,dispatcher就会根据获取到的handler查找能够将当前request适配到该handler的adapter,这里获取handleradapter的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
protected handleradapter gethandleradapter(object handler) throws servletexception {
if ( this .handleradapters != null ) {
// 遍历当前容器中所有的handleradapter,通过调用其supports()方法,判断当前handleradapter
// 能否用于适配当前的handler,如果可以,则直接使用该handleradapter
for (handleradapter ha : this .handleradapters) {
if (logger.istraceenabled()) {
logger.trace( "testing handler adapter [" + ha + "]" );
}
if (ha.supports(handler)) {
return ha;
}
}
}
// 如果找不到任何一个handleradapter用于适配当前请求,则抛出异常
throw new servletexception( "no adapter for handler [" + handler
+ "]: the dispatcherservlet configuration needs to include a handleradapter"
+ " that supports this handler" );
}
|
这里获取handleradapter的过程与handlermapping非常的相似,也是遍历当前容器中所有的handleradapter对象,然后调用其supports()方法,判断该适配器能否应用于当前handler的适配,如果可以则直接使用该handleradapter。关于handleradapter进行request与handler适配的过程,读者可阅读本人之前的文章:spring mvc之requestmappinghandleradapter详解。
4. 视图渲染
在handleradapter进行了请求的适配,并且调用了目标handler之后,其会返回一个modelandview对象,该对象中保存有用于渲染视图的模型数据和需要渲染的视图名。具体的视图渲染工作是在processdispatchresult()方法中进行的,这里我们直接阅读器源码:
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
|
private void processdispatchresult(httpservletrequest request,
httpservletresponse response, @nullable handlerexecutionchain mappedhandler,
@nullable modelandview mv, @nullable exception exception) throws exception {
// 用于标记当前生成view是否是异常处理之后生成的view
boolean errorview = false ;
if (exception != null ) {
// 如果当前的异常是modelandviewdefiningexception类型,则说明是modelandview的定义
// 异常,那么就会调用其getmodelandview()方法生成一个新的view
if (exception instanceof modelandviewdefiningexception) {
logger.debug( "modelandviewdefiningexception encountered" , exception);
mv = ((modelandviewdefiningexception) exception).getmodelandview();
} else {
// 如果生成的异常是其他类型的异常,就会在当前容器中查找能够处理当前异常的“拦截器”,
// 找到之后调用这些拦截器,然后生成一个新的modelandview
object handler = (mappedhandler != null ? mappedhandler.gethandler() : null );
mv = processhandlerexception(request, response, handler, exception);
errorview = (mv != null );
}
}
// 如果得到的modelandview对象(无论是否为异常处理之后生成的modelandview)不为空,并且没有被清理,
// 那么就会对其进行渲染,渲染的主要逻辑在render()方法中
if (mv != null && !mv.wascleared()) {
render(mv, request, response);
if (errorview) {
// 如果当前是异常处理之后生成的视图,那么就请求当前request中与异常相关的属性
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()) {
return ;
}
// 在视图渲染完成之后,依次调用当前容器中所有拦截器的aftercompletion()方法
if (mappedhandler != null ) {
mappedhandler.triggeraftercompletion(request, response, null );
}
}
|
从上面的逻辑可以看出,在进行视图渲染时,首先会判断请求处理过程中是否抛出了异常,如果抛出了异常,则会调用相应的异常处理器,获取异常处理之后的modelandview对象,然后通过modelandview对象渲染具体的视图,最后会依次触发当前容器中所有拦截器的aftercompletion()方法。这里对视图的具体渲染工作在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
|
protected void render(modelandview mv, httpservletrequest request, httpservletresponse response) throws exception {
// 获取当前请求的locale信息,该信息在进行视图的国际化展示时将会非常有用
locale locale = ( this .localeresolver != null
? this .localeresolver.resolvelocale(request) : request.getlocale());
response.setlocale(locale);
view view;
string viewname = mv.getviewname();
if (viewname != null ) {
// 如果视图名不为空,那么就会使用当前容器中配置的viewresolver根据视图名获取一个view对象
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 {
// 如果modelandview中没有视图名,而提供的view对象,则直接使用该view对象
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() + "'" );
}
}
if (logger.isdebugenabled()) {
logger.debug( "rendering view [" + view + "] in dispatcherservlet with name '"
+ getservletname() + "'" );
}
try {
// 设置响应的status属性
if (mv.getstatus() != null ) {
response.setstatus(mv.getstatus().value());
}
// 调用view对象的render()方法来渲染具体的视图
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;
}
}
|
这里的render()方法才是进行视图渲染的真正方法,首先该方法首先通过modelandview对象获取所要渲染的视图名,通过viewresolver生成一个用于视图渲染的view对象;如果modelandview中不是保存的视图名,而是保存的view对象,则直接使用该对象。在生成view对象之后,通过调用该对象的render()方法渲染得到具体的视图。这里关于viewresolver如何获取到view对象,并且如何进行视图渲染的过程,读者可以阅读本人的文章:spring mvc之视图解析。
5. 小结
本文首先从整体上讲解了dispatcherservlet是如何对请求进行聚合并且处理的,然后分别从handler获取,handleradapter进行请求适配,以及视图的渲染三个方面对请求处理的整体流程进行了讲解。这里主要是对dispatcherservlet处理请求的整体流程进行讲解,其各个部分的细节读者可以阅读本人前面的文章以进行详细的了解。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://my.oschina.net/zhangxufeng/blog/2222590