Spring MVC 解读——@RequestMapping (1)(转)
阅读原文时间:2024年12月11日阅读:1

转自:http://my.oschina.net/HeliosFly/blog/212329

为了降低文章篇幅,使得文章更目标化,简洁化,我们就不例举各种@RequestMapping的用法等内容了.

文章主要说明以下问题:

  1. Spring怎样处理@RequestMapping(怎样将请求路径映射到控制器类或方法)

  2. Spring怎样将请求分派给正确的控制器类或方法

  3. Spring如何实现灵活的控制器方法的

在Spring MVC 3.1 之前的版本中,Spring默认使用 DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter来处理 @RequestMapping注解和请求方法调用,而从3.1开始提供了一组新的API完成这些工作。相比之下,新的API更加的合理完善,开放,易拓 展,面向对象。这篇文章便是基于3.1的新API进行剖析的。

一、概念解析

在开始之前我们先了解下新的API中引入的新接口或者类,这会有助于后面的处理过程的理解。不得不说新的API提供了更多漂亮的抽象,你能感受到面向对象的魅力。

  1. RequestMappingInfo 这个类是对请求映射的一个抽象,它包含了请求路径,请求方法,请求头等信息。其实可以看做是@RequestMapping的一个对应类。

  2. HandlerMethod 这个类封装了处理器实例(Controller Bean)和 处理方法实例(Method)以及方法参数数组(MethodParameter[])

  3. MethodParameter这个类从2.0就有了,它封装了方法某个参数的相关信息及行为,如该参数的索引,该参数所属方法实例或构造器实例,该参数的类型等。

  4. HandlerMapping 该接口的实现类用来定义请求和处理器之前的映射关系,其中只定义了一个方法getHandler。

  5. AbstractHandlerMethodMapping 这是HandlerMapping的一个基本实现类,该类定义了请求与HandlerMethod实例的映射关系。

  6. RequestMappingInfoHandlerMapping这个是AbstractHandlerMethodMapping的实现类,他维护了一个RequestMappingInfo和HandlerMethod的Map属性。

  7. RequestMappingHandlerMapping 这个是RequestMappingInfoHandlerMapping的子类,它将@RequestMapping注解转化为RequestMappingInfo实例,并为父类使用。也就是我们处理@RequestMapping的终点。

  8. InitializingBean 这个接口定义了其实现Bean在容器完成属性设置后可以执行自定义初始化操作,我们的AbstractHandlerMethodMapping便实现了这个接口,并且定义了一组自定义操作,就是用来检测处理我们的@RequestMapping注解。

概念讲的太多总不是什么好事。但明白了上述概念基本上就成功一半了,其中的实现相对@Autowired那篇简单多了。

二、InitialiZingBean.afterPropertySet()

我们从头开始,看看到底Spring是怎样检测并处理我们@RequestMapping注解的。不知大家还记不记的这段代码:

?

1

2

3

4

5

6

7

Object exposedObject = bean;

try {

populateBean(beanName, mbd, instanceWrapper);

if (exposedObject != null``) {

exposedObject = initializeBean(beanName, exposedObject, mbd);

}

}

这是BeanFactory创建Bean过程中需要执行的一段代码,其中populateBean方法便是@Autowired注解的处理过程,执行的属性的自动注入等操作。因为initializeBean方法当时与主题无关没有讲,不过这时它便是我们关注的焦点了。(上一篇@Autowired 详解

上面概念中我们讲到InitiaizingBean接口,它的实现Bean会在容器完成属性注入后执行一个自定义操作,这不就满足initializeBean方法的执行唤醒嘛,我们来看它的实现:

?

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

protected Object initializeBean(``final String beanName, final Object bean, RootBeanDefinition mbd) {

if (System.getSecurityManager() != null``) {

AccessController.doPrivileged(``new PrivilegedAction<Object>()&nbsp;{

public Object&nbsp;run()&nbsp;{

invokeAwareMethods(beanName,&nbsp;bean);

return null``;

}

},&nbsp;getAccessControlContext());

}

else {``//这里检测当前Bean是否实现一些列Aware接口,并调用相关方法,我们不关心。

invokeAwareMethods(beanName,&nbsp;bean);

}

Object&nbsp;wrappedBean&nbsp;=&nbsp;bean;

if (mbd&nbsp;== null ||&nbsp;!mbd.isSynthetic())&nbsp;{``//BeanPostProcessor&nbsp;的回调,不关心

wrappedBean&nbsp;=&nbsp;applyBeanPostProcessorsBeforeInitialization(wrappedBean,&nbsp;beanName);

}

try {

invokeInitMethods(beanName,&nbsp;wrappedBean,&nbsp;mbd);``//这是我们需要关心的,下面看下它的实现

}

if (mbd&nbsp;== null ||&nbsp;!mbd.isSynthetic())&nbsp;{``//BeanPostProcessor&nbsp;的回调,不关心

wrappedBean&nbsp;=&nbsp;applyBeanPostProcessorsAfterInitialization(wrappedBean,&nbsp;beanName);

}

return wrappedBean;

}

我们接着来看下invokeInitMethods方法的实现:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

protected void invokeInitMethods(String&nbsp;beanName, final Object&nbsp;bean,&nbsp;RootBeanDefinition&nbsp;mbd)

throws Throwable&nbsp;{

//是否是InitializingBean的实例

boolean isInitializingBean&nbsp;=&nbsp;(bean instanceof InitializingBean);

if (isInitializingBean&nbsp;&&

(mbd&nbsp;== null ||&nbsp;!mbd.isExternallyManagedInitMethod(``"afterPropertiesSet"``)))&nbsp;{

if (System.getSecurityManager()&nbsp;!= null``)&nbsp;{

try {

AccessController.doPrivileged(``new PrivilegedExceptionAction<Object>()&nbsp;{

public Object&nbsp;run() throws Exception&nbsp;{``//利用系统安全管理器调用

((InitializingBean)&nbsp;bean).afterPropertiesSet();

return null``;

}

},&nbsp;getAccessControlContext());

}

}

else {``//调用InitializingBean的afterPropertiesSet方法。

((InitializingBean)&nbsp;bean).afterPropertiesSet();

}

}

//调用自定义初始化方法。。。省略,不关心

}

上一篇关于的文章,我们说过了,当在配置文件中加上该标记后,Spring(3.1后)会默认为我们注册RequestMappingHandlerMapping等Bean定义。而RequestMappingHandlerMapping```实现了InitializingBean接口,因此,在初始化并装配该Bean实例时,执行到上述代码是,便会执行他的afterPropertySet方法。我们接下来看看他的afterPropertySet方法:` ``

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public void afterPropertiesSet()&nbsp;{

initHandlerMethods();

}

//Scan&nbsp;beans&nbsp;in&nbsp;the&nbsp;ApplicationContext,&nbsp;detect&nbsp;and&nbsp;register&nbsp;handler&nbsp;methods.

protected void initHandlerMethods()&nbsp;{

//扫描所有注册的Bean

String[]&nbsp;beanNames&nbsp;=&nbsp;(``this``.detectHandlerMethodsInAncestorContexts&nbsp;?

BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(),

Object.``class``)&nbsp;:&nbsp;getApplicationContext().getBeanNamesForType(Object.``class``));

//遍历这些Bean,依次判断是否是处理器,并检测其HandlerMethod

for (String&nbsp;beanName&nbsp;:&nbsp;beanNames)&nbsp;{

if (isHandler(getApplicationContext().getType(beanName))){

detectHandlerMethods(beanName);

}

}

//这个方法是个空实现,不管他

handlerMethodsInitialized(getHandlerMethods());

}

它直接调用了initHandlerMethods()方法,并且该方法被描述为:扫描ApplicationContext中的beans,检测并注册处理器方法。we are close。

三、检测@RequestMapping

我们再看它是怎样判断是否是处理器的,以及怎么detect Handler Methods 的:

?

1

2

3

4

5

@Override

protected boolean isHandler(Class<?>&nbsp;beanType)&nbsp;{

return ((AnnotationUtils.findAnnotation(beanType,&nbsp;Controller.``class``)&nbsp;!= null``)&nbsp;||

(AnnotationUtils.findAnnotation(beanType,&nbsp;RequestMapping.``class``)&nbsp;!= null``));

}

啊哈,很简单,就是看看有没有被@Controller或者@RequestMapping注解标记

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

protected void detectHandlerMethods(``final Object&nbsp;handler)&nbsp;{

Class<?>&nbsp;handlerType&nbsp;=&nbsp;(handler instanceof String)&nbsp;?

getApplicationContext().getType((String)&nbsp;handler)&nbsp;:&nbsp;handler.getClass();

final Class<?>&nbsp;userType&nbsp;=&nbsp;ClassUtils.getUserClass(handlerType);

Set<Method>&nbsp;methods&nbsp;=&nbsp;HandlerMethodSelector.selectMethods(userType, new MethodFilter(){

public boolean matches(Method&nbsp;method)&nbsp;{``//只选择被@RequestMapping标记的方法

return getMappingForMethod(method,&nbsp;userType)&nbsp;!= null``;

}

});

for (Method&nbsp;method&nbsp;:&nbsp;methods)&nbsp;{

//根据方法上的@RequestMapping来创建RequestMappingInfo实例。

T&nbsp;mapping&nbsp;=&nbsp;getMappingForMethod(method,&nbsp;userType);

//注册请求映射

registerHandlerMethod(handler,&nbsp;method,&nbsp;mapping);

}

}

整个的检测过程大致清楚了:1)遍历Handler中的所有方法,找出其中被@RequestMapping注解标记的方法。2)然后遍历这些方法,生成RequestMappingInfo实例。3)将RequestMappingInfo实例以及处理器方法注册到缓存中。

下面我们看看细节:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

@Override

protected RequestMappingInfo&nbsp;getMappingForMethod(Method&nbsp;method,&nbsp;Class<?>&nbsp;handlerType)&nbsp;{

RequestMappingInfo&nbsp;info&nbsp;= null``;

//获取方法method上的@RequestMapping实例。

RequestMapping&nbsp;methodAnnotation&nbsp;=

AnnotationUtils.findAnnotation(method,&nbsp;RequestMapping.``class``);

if (methodAnnotation&nbsp;!= null``)&nbsp;{``//方法被注解了

RequestCondition<?>&nbsp;methodCondition&nbsp;=&nbsp;getCustomMethodCondition(method);``//始终返回null

info&nbsp;=&nbsp;createRequestMappingInfo(methodAnnotation,&nbsp;methodCondition);``//创建MappingInfo

//检查方法所属的类有没有@RequestMapping注解

RequestMapping&nbsp;typeAnnotation&nbsp;=&nbsp;AnnotationUtils.findAnnotation(handlerType,

RequestMapping.``class``);

if (typeAnnotation&nbsp;!= null``)&nbsp;{``//有类层次的@RequestMapping注解

RequestCondition<?>&nbsp;typeCondition&nbsp;=&nbsp;getCustomTypeCondition(handlerType);``//null

//将类层次的RequestMapping和方法级别的RequestMapping结合

info&nbsp;=&nbsp;createRequestMappingInfo(typeAnnotation,&nbsp;typeCondition).combine(info);

}

}

return info;

}

很清晰吧,先获取方法上的@RequestMapping信息,然后获取类级别上的@RequestMapping 信息,然后将两者结合,这里我们有必要再了解下怎样创建RequestMappingInfo对象的(包括他的内部结构),以及怎样将类级别的request mapping信息和方法级别的进行结合的?

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

private RequestMappingInfo&nbsp;createRequestMappingInfo(RequestMapping&nbsp;annotation,

RequestCondition<?>&nbsp;customCondition)&nbsp;{

return new RequestMappingInfo(

new PatternsRequestCondition(annotation.value(),&nbsp;getUrlPathHelper(),&nbsp;getPathMatcher(),

this``.useSuffixPatternMatch, this``.useTrailingSlashMatch, this``.fileExtensions),

new RequestMethodsRequestCondition(annotation.method()),

new ParamsRequestCondition(annotation.params()),

new HeadersRequestCondition(annotation.headers()),

new ConsumesRequestCondition(annotation.consumes(),&nbsp;annotation.headers()),

new ProducesRequestCondition(annotation.produces(),&nbsp;annotation.headers(),

getContentNegotiationManager()),

customCondition

);

}

其中涉及到了几个类,我们大致了解下含义:

  • PatternRequestCondition 它其实就是URL模式的封装,它包含了一个URL模式的Set集合。其实就是@RequestMapping注解中的value值得封装。

  • RequestMethodRequestCondition 它是@RequestMapping 注解中method属性的封装

  • ParamsRequestCondition 它是@RequestMapping注解中params属性的封装

等等,依次类推。因此RequestMappingInfo其实就是对@RquestMapping 的封装。

下面我们再看看怎样进行Combine的:

?

1

2

3

4

5

6

7

8

9

10

11

12

public RequestMappingInfo&nbsp;combine(RequestMappingInfo&nbsp;other)&nbsp;{

PatternsRequestCondition&nbsp;patterns&nbsp;= this``.patternsCondition.combine(other.patternsCondition);

RequestMethodsRequestCondition&nbsp;methods&nbsp;= this``.methodsCondition.combine(other.methodsCondition);

ParamsRequestCondition&nbsp;params&nbsp;= this``.paramsCondition.combine(other.paramsCondition);

HeadersRequestCondition&nbsp;headers&nbsp;= this``.headersCondition.combine(other.headersCondition);

ConsumesRequestCondition&nbsp;consumes&nbsp;= this``.consumesCondition.combine(other.consumesCondition);

ProducesRequestCondition&nbsp;produces&nbsp;= this``.producesCondition.combine(other.producesCondition);

RequestConditionHolder&nbsp;custom&nbsp;= this``.customConditionHolder.combine(other.customConditionHolder);

return new RequestMappingInfo(patterns,&nbsp;methods,&nbsp;params,&nbsp;headers,&nbsp;consumes,

produces,&nbsp;custom.getCondition());

}

很清晰,对每一个元素都进行combine操作,我们这里只看PatternRequestCondition是怎么结合的,就是看看怎样合并url的。其他没太大必要。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public PatternsRequestCondition&nbsp;combine(PatternsRequestCondition&nbsp;other)&nbsp;{

Set<String>&nbsp;result&nbsp;= new LinkedHashSet<String>();

if (!``this``.patterns.isEmpty()&nbsp;&&&nbsp;!other.patterns.isEmpty())&nbsp;{

for (String&nbsp;pattern1&nbsp;: this``.patterns)&nbsp;{

for (String&nbsp;pattern2&nbsp;:&nbsp;other.patterns)&nbsp;{

result.add(``this``.pathMatcher.combine(pattern1,&nbsp;pattern2));

}

}

}

else if (!``this``.patterns.isEmpty())&nbsp;{

result.addAll(``this``.patterns);

}

else if (!other.patterns.isEmpty())&nbsp;{

result.addAll(other.patterns);

}

else {

result.add(``""``);

}

return new PatternsRequestCondition(result, this``.urlPathHelper, this``.pathMatcher,

this``.useSuffixPatternMatch,``this``.useTrailingSlashMatch, this``.fileExtensions);

}

1)两个pattern都存在是,调用PathMatcher的combine方法合并两个pattern。

2)只有一个有时,使用这个。

3)两个都没有时,为空“”。

现在真正的url拼接是由PathMatcher来完成的了。我们就不看他的代码了就是一串if else的组合,重点是考虑进各种情况,我们来看下方法的注释吧:

清晰,全面吧,有兴趣的可以看一下代码,这里不讲了。

四、注册请求映射

上面我们已经讲了@RequestMapping的检测和处理,并且根据@RequestMapping生成了RequestMappingInfo实例,那Spring必定需要将这些信息保存起来,以处理我们的请求。

第三节中我们提到一个方法还没有分析,就是registerHandlerMethod 方法:

?

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

protected void registerHandlerMethod(Object&nbsp;handler,&nbsp;Method&nbsp;method,&nbsp;T&nbsp;mapping)&nbsp;{

HandlerMethod&nbsp;handlerMethod;

if (handler instanceof String)&nbsp;{

String&nbsp;beanName&nbsp;=&nbsp;(String)&nbsp;handler;

handlerMethod&nbsp;= new HandlerMethod(beanName,&nbsp;getApplicationContext(),&nbsp;method);

}

else {

handlerMethod&nbsp;= new HandlerMethod(handler,&nbsp;method);

}

//上面几行是根据新的处理器实例,方法实例,RequestMappingInfo来生成新的HandlerMethod实例

//下面是从缓存中查看是否有存在的HandlerMethod实例,如果有并且不相等则抛出异常

HandlerMethod&nbsp;oldHandlerMethod&nbsp;=&nbsp;handlerMethods.get(mapping);

if (oldHandlerMethod&nbsp;!= null &&&nbsp;!oldHandlerMethod.equals(handlerMethod))&nbsp;{

throw new IllegalStateException();

}

//handlerMethods&nbsp;是一个Map键是RequestMappingInfo对象,值是HandlerMethod实例

//因此一个HandlerMethod实例可能处理多个mapping,而一个mapping实例只能由一个method处理

this``.handlerMethods.put(mapping,&nbsp;handlerMethod);

//这里获取mapping实例中的所有url。

Set<String>&nbsp;patterns&nbsp;=&nbsp;getMappingPathPatterns(mapping);

for (String&nbsp;pattern&nbsp;:&nbsp;patterns)&nbsp;{

if (!getPathMatcher().isPattern(pattern))&nbsp;{

//urlMap也是Map,键是url&nbsp;模式,值是RequestMappingInfo实例

//因此一个mapping实例可能对应多个pattern,但是一个pattern只能对应一个mapping实例

this``.urlMap.add(pattern,&nbsp;mapping);

}

}

}

这里可能稍微有点绕,其实道理很简单,当请求到达时,去urlMap中需找匹配的url,以及获取对应mapping实例,然后去handlerMethods中获取匹配HandlerMethod实例。

五、承上启下

篇幅有些长了,超出字数限制了,只能分成两篇了……………………..

这章只分析了我们前面三个问题中的第一个,但是已经相当接近了。下一篇我们来讲,Spring怎样处理客户发来的请求,以及方法调用的。