HanlerMapping是沟通请求和后端controller映射,是所有请求的入口。
1.类结构介绍
该图只对属性和类层级进行了描述,屏蔽了方法,主要是为了根据内部属性,重点理解Spring HandlerMapping提供功能。
1.1 AbstractHandlerMapping
HandlerMapping 抽象类,提供了排序,默认Handler,和handler 拦截器。
1
2
3
4
5
6
7
8
9
10
11
|
public
abstract
class
AbstractHandlerMapping
extends
WebApplicationObjectSupport
implements
HandlerMapping, Ordered {
private
int
order = Integer.MAX_VALUE;
// default: same as non-Ordered
private
Object defaultHandler;
private
final
List<Object> interceptors =
new
ArrayList<Object>();
private
HandlerInterceptor[] adaptedInterceptors;
...
}
|
属性不做过多解释。
重点说明下interceptors 和adaptedInterceptors两个属性的区别
interceptors :作用于所有的mapping对应的所有Handler;
adaptedInterceptors:转换interceptors的镜像;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
protected
void
initInterceptors() {
if
(!
this
.interceptors.isEmpty()) {
this
.adaptedInterceptors =
new
HandlerInterceptor[
this
.interceptors.size()];
for
(
int
i =
0
; i <
this
.interceptors.size(); i++) {
Object interceptor =
this
.interceptors.get(i);
if
(interceptor ==
null
) {
throw
new
IllegalArgumentException(
"Entry number "
+ i +
" in interceptors array is null"
);
}
this
.adaptedInterceptors[i] = adaptInterceptor(interceptor);
}
}
}
protected
HandlerInterceptor adaptInterceptor(Object interceptor) {
if
(interceptor
instanceof
HandlerInterceptor) {
return
(HandlerInterceptor) interceptor;
}
else
if
(interceptor
instanceof
WebRequestInterceptor) {
return
new
WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
}
else
{
throw
new
IllegalArgumentException(
"Interceptor type not supported: "
+ interceptor.getClass().getName());
}
}
|
1.2 AbstractUrlHandlerMapping
提供 URL-mapped功能,提供了根据url检索handler的能力。
url匹配规则:最长匹配。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
abstract
class
AbstractUrlHandlerMapping
extends
AbstractHandlerMapping {
//从请求request找出url
private
UrlPathHelper urlPathHelper =
new
UrlPathHelper();
//进行url匹配,选择合适的拦截器
private
PathMatcher pathMatcher =
new
AntPathMatcher();
//响应handlerMapping root请求("/")
private
Object rootHandler;
// whether to lazily initialize handlers
private
boolean
lazyInitHandlers =
false
;
//registered handlers
private
final
Map<String, Object> handlerMap =
new
LinkedHashMap<String, Object>();
//MappedInterceptors
private
MappedInterceptors mappedInterceptors;
...
}
|
1.3 AbstractDetectingUrlHandlerMapping
提供发现HandlerMapping能力.,通过内省(introspection)方式,从spring容器中获取。
具体见detectHandlers。
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
|
public
abstract
class
AbstractDetectingUrlHandlerMapping
extends
AbstractUrlHandlerMapping {
//whether to detect handler beans in ancestor ApplicationContexts
private
boolean
detectHandlersInAncestorContexts =
false
;
protected
void
detectHandlers()
throws
BeansException {
if
(logger.isDebugEnabled()) {
logger.debug(
"Looking for URL mappings in application context: "
+ getApplicationContext());
}
String[] beanNames = (
this
.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.
class
) :
getApplicationContext().getBeanNamesForType(Object.
class
));
// Take any bean name that we can determine URLs for.
for
(String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if
(!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
else
{
if
(logger.isDebugEnabled()) {
logger.debug(
"Rejected bean name '"
+ beanName +
"': no URL paths identified"
);
}
}
}
}
...
}
|
1.4 AbstractControllerUrlHandlerMapping
提供获得controller到url的转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
abstract
class
AbstractControllerUrlHandlerMapping
extends
AbstractDetectingUrlHandlerMapping {
private
ControllerTypePredicate predicate =
new
AnnotationControllerTypePredicate();
//Java packages that should be excluded from this mapping
private
Set<String> excludedPackages = Collections.singleton(
"org.springframework.web.servlet.mvc"
);
// controller classes that should be excluded
private
Set<Class> excludedClasses = Collections.emptySet();
@Override
protected
String[] determineUrlsForHandler(String beanName) {
Class beanClass = getApplicationContext().getType(beanName);
if
(isEligibleForMapping(beanName, beanClass)) {
return
buildUrlsForHandler(beanName, beanClass);
}
else
{
return
null
;
}
}
...
}
|
2. 实例
2.1 SimpleUrlHandlerMapping
简单映射关系。
2.2 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<
beans
>
<
bean
id
=
"urlMapping"
class
=
"org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
>
<
property
name
=
"defaultHandler"
><
ref
local
=
"starController"
/></
property
>
<
property
name
=
"rootHandler"
><
ref
local
=
"mainController"
/></
property
>
<
property
name
=
"urlMap"
>
<
map
>
<
entry
key
=
"/welcome*"
><
ref
local
=
"otherController"
/></
entry
>
<
entry
key
=
"/welcome.html"
><
ref
local
=
"mainController"
/></
entry
>
<
entry
key
=
"/show.html"
><
ref
local
=
"mainController"
/></
entry
>
<
entry
key
=
"/bookseats.html"
><
ref
local
=
"mainController"
/></
entry
>
<
entry
key
=
"/reservation.html"
><
ref
local
=
"mainController"
/></
entry
>
<
entry
key
=
"/payment.html"
><
ref
local
=
"mainController"
/></
entry
>
<
entry
key
=
"/confirmation.html"
><
ref
local
=
"mainController"
/></
entry
>
</
map
>
</
property
>
</
bean
>
<
bean
id
=
"mainController"
class
=
"java.lang.Object"
/>
<
bean
id
=
"otherController"
class
=
"java.lang.Object"
/>
<
bean
id
=
"starController"
class
=
"java.lang.Object"
/>
</
beans
>
|
测试用例
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
|
@Test
public
void
urlMappingWithUrlMap()
throws
Exception {
checkMappings(
"urlMapping"
);
}
private
void
checkMappings(String beanName)
throws
Exception {
MockServletContext sc =
new
MockServletContext(
""
);
XmlWebApplicationContext wac =
new
XmlWebApplicationContext();
wac.setServletContext(sc);
wac.setConfigLocations(
new
String[] {
"/org/springframework/web/servlet/handler/map2.xml"
});
wac.refresh();
Object bean = wac.getBean(
"mainController"
);
Object otherBean = wac.getBean(
"otherController"
);
Object defaultBean = wac.getBean(
"starController"
);
HandlerMapping hm = (HandlerMapping) wac.getBean(beanName);
MockHttpServletRequest req =
new
MockHttpServletRequest(
"GET"
,
"/welcome.html"
);
HandlerExecutionChain hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
assertEquals(
"/welcome.html"
, req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
req =
new
MockHttpServletRequest(
"GET"
,
"/welcome.x"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == otherBean);
assertEquals(
"welcome.x"
, req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
req =
new
MockHttpServletRequest(
"GET"
,
"/"
);
req.setServletPath(
"/welcome.html"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/welcome.html"
);
req.setContextPath(
"/app"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/show.html"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/bookseats.html"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/original-welcome.html"
);
req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE,
"/welcome.html"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/original-show.html"
);
req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE,
"/show.html"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/original-bookseats.html"
);
req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE,
"/bookseats.html"
);
hec = getHandler(hm, req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/"
);
hec = getHandler(hm, req);
//返回rootHandler
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
assertEquals(
"/"
, req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
req =
new
MockHttpServletRequest(
"GET"
,
"/somePath"
);
hec = getHandler(hm, req);
//返回defaultHandler
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == defaultBean);
assertEquals(
"/somePath"
, req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
}
|
2.2 BeanNameUrlHandlerMapping
内省方式,将注册的handler的名称作为相关的url
配置文件
1
2
3
4
5
6
7
8
9
10
11
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<
beans
>
<
bean
id
=
"handlerMapping"
class
=
"org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"
/>
<
bean
id
=
"godCtrl"
name
=
"/ /mypath/welcome.html /mypath/show.html /mypath/bookseats.html /mypath/reservation.html /mypath/payment.html /mypath/confirmation.html /mypath/test*"
class
=
"java.lang.Object"
/>
</
beans
>
|
快照
测试用例
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
|
private
void
doTestRequestsWithSubPaths(HandlerMapping hm)
throws
Exception {
Object bean = wac.getBean(
"godCtrl"
);
MockHttpServletRequest req =
new
MockHttpServletRequest(
"GET"
,
"/mypath/welcome.html"
);
HandlerExecutionChain hec = hm.getHandler(req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/myapp/mypath/welcome.html"
);
req.setContextPath(
"/myapp"
);
hec = hm.getHandler(req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/myapp/mypath/welcome.html"
);
req.setContextPath(
"/myapp"
);
req.setServletPath(
"/mypath/welcome.html"
);
hec = hm.getHandler(req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/myapp/myservlet/mypath/welcome.html"
);
req.setContextPath(
"/myapp"
);
req.setServletPath(
"/myservlet"
);
hec = hm.getHandler(req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/myapp/myapp/mypath/welcome.html"
);
req.setContextPath(
"/myapp"
);
req.setServletPath(
"/myapp"
);
hec = hm.getHandler(req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/mypath/show.html"
);
hec = hm.getHandler(req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
req =
new
MockHttpServletRequest(
"GET"
,
"/mypath/bookseats.html"
);
hec = hm.getHandler(req);
assertTrue(
"Handler is correct bean"
, hec !=
null
&& hec.getHandler() == bean);
}
|
值得一提的是
1
2
|
//返回的lookupPath:会根据request.servletPath信息,对requestURI截取
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
|
2.3 ControllerClassNameHandlerMapping
注册@Controller annotated beans,按照class names 简单转换为url
配置XML
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
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<
beans
>
<
bean
id
=
"index"
class
=
"org.springframework.web.servlet.mvc.mapping.Controller"
/>
<
bean
id
=
"welcome"
class
=
"org.springframework.web.servlet.mvc.mapping.WelcomeController"
/>
<
bean
id
=
"admin"
class
=
"org.springframework.web.servlet.mvc.mapping.AdminController"
/>
<
bean
id
=
"buy"
class
=
"org.springframework.web.servlet.mvc.mapping.BuyForm"
/>
<
bean
name
=
"/myFile"
class
=
"org.springframework.web.servlet.mvc.UrlFilenameViewController"
/>
<
bean
name
=
"/myFile2"
class
=
"org.springframework.web.servlet.mvc.UrlFilenameViewController"
/>
<
bean
id
=
"mapping"
class
=
"org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"
>
<!--
We have to revert the default exclude for "org.springframework.web.servlet.mvc",
since our test controllers sit in this package.
-->
<
property
name
=
"excludedPackages"
><
list
></
list
></
property
>
<!-- 配置bean 的名称 -->
<
property
name
=
"excludedClasses"
value
=
"org.springframework.web.servlet.mvc.UrlFilenameViewController"
/>
</
bean
>
<
bean
id
=
"mapping2"
class
=
"org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"
>
<!--
We have to revert the default exclude for "org.springframework.web.servlet.mvc",
since our test controllers sit in this package.
-->
<
property
name
=
"excludedPackages"
><
list
></
list
></
property
>
<
property
name
=
"excludedClasses"
value
=
"org.springframework.web.servlet.mvc.UrlFilenameViewController"
/>
<
property
name
=
"caseSensitive"
value
=
"true"
/>
<
property
name
=
"pathPrefix"
value
=
"/myapp"
/>
<!-- 可匹配/myapp/mvc/mapping/buyForm-->
<
property
name
=
"basePackage"
value
=
"org.springframework.web.servlet"
/>
</
bean
>
<
bean
id
=
"mapping3"
class
=
"org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"
>
<!--
We have to revert the default exclude for "org.springframework.web.servlet.mvc",
since our test controllers sit in this package.
-->
<
property
name
=
"excludedPackages"
><
list
></
list
></
property
>
<
property
name
=
"excludedClasses"
value
=
"org.springframework.web.servlet.mvc.UrlFilenameViewController"
/>
<
property
name
=
"caseSensitive"
value
=
"true"
/>
<
property
name
=
"pathPrefix"
value
=
"/myapp"
/>
<!--/myapp/welcome -->
<
property
name
=
"basePackage"
value
=
"org.springframework.web.servlet.mvc.mapping"
/>
</
bean
>
<
bean
id
=
"mapping4"
class
=
"org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"
>
<!--
We have to revert the default exclude for "org.springframework.web.servlet.mvc",
since our test controllers sit in this package.
-->
<
property
name
=
"excludedPackages"
><
list
></
list
></
property
>
<
property
name
=
"excludedClasses"
value
=
"org.springframework.web.servlet.mvc.UrlFilenameViewController"
/>
<
property
name
=
"pathPrefix"
value
=
"myapp/"
/>
<
property
name
=
"basePackage"
value
=
""
/>
<!--匹配/myapp/org/springframework/web/servlet/mvc/mapping/welcome -->
</
bean
>
</
beans
>
|
basePackage:Set the base package to be used for generating path mappings, including all subpackages underneath this packages as path elements.
pathPrefix:Specify a prefix to prepend to the path generated from the controller name.
生成url规则具体见generatePathMappings方法。
2.4 ControllerBeanNameHandlerMapping
和ControllerClassNameHandlerMapping类似,可以理解为url的生成规则有差异而已。
总结:
介绍了相关的类结构,不难发现HandlerMapping的主要职责
注册Handler.可以是注解,可以是XML声明。
生成url,有很多策略。beanName,前缀,包名称都可以作为参考
维护mapping关系
url的匹配能力
接下来,会继续探讨HandlerMapping的另一个重要部分
拦截器和DefaultAnnotationHandlerMapping
本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1879040,如需转载请自行联系原作者