好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Spring MVC学习教程之RequestMappingHandlerMapping匹配

前言

对于requestmappinghandlermapping,使用spring的同学基本都不会陌生,该类的作用有两个:

通过request查找对应的handlermethod,即当前request具体是由controller中的哪个方法进行处理; 查找当前系统中的interceptor,将其与handlermethod封装为一个handlerexecutionchain。

本文主要讲解requestmappinghandlermapping是如何获取handlermethod和interceptor,并且将其封装为handlerexecutionchain的。

下面话不多说了,来一起看看详细的介绍吧

1.整体封装结构

requestmappinghandlermapping实现了handlermapping接口,该接口的主要方法如下:

?

1

2

3

4

public interface handlermapping {

  // 通过request获取handlerexecutionchain对象

  handlerexecutionchain gethandler(httpservletrequest request) throws exception;

}

这里我们直接看requestmappinghandlermapping是如何实现该接口的:

?

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

@override

@nullable

public final handlerexecutionchain gethandler(httpservletrequest request)

  throws exception {

  // 通过request获取具体的处理bean,这里handler可能有两种类型:handlermethod和string。

  // 如果是string类型,那么就在beanfactory中查找该string类型的bean,需要注意的是,返回的

  // bean如果是需要使用requestmappinghandleradapter处理,那么也必须是handlermethod类型的

  object handler = gethandlerinternal(request);

  if (handler == null ) {

   // 如果找不到处理方法,则获取自定义的默认handler

   handler = getdefaulthandler();

  }

  if (handler == null ) {

   return null ;

  }

  if (handler instanceof string) {

   // 如果获取的handler是string类型的,则在当前beanfactory中获取该名称的bean,

   // 并将其作为handler返回

   string handlername = (string) handler;

   handler = obtainapplicationcontext().getbean(handlername);

  }

 

  // 获取当前系统中配置的interceptor,将其与handler一起封装为一个handlerexecutionchain

  handlerexecutionchain executionchain = gethandlerexecutionchain(handler, request);

  // 这里corsutils.iscorsrequest()方法判断的是当前请求是否为一个跨域的请求,如果是一个跨域的请求,

  // 则将跨域相关的配置也一并封装到handlerexecutionchain中

  if (corsutils.iscorsrequest(request)) {

   corsconfiguration globalconfig =

    this .globalcorsconfigsource.getcorsconfiguration(request);

   corsconfiguration handlerconfig = getcorsconfiguration(handler, request);

   corsconfiguration config = (globalconfig != null ?

    globalconfig测试数据bine(handlerconfig) : handlerconfig);

   executionchain = getcorshandlerexecutionchain(request, executionchain, config);

  }

  return executionchain;

}

从上面的代码可以看出,对于handlerexecutionchain的获取,requestmappinghandlermapping首先会获取当前request对应的handler,然后将其与interceptor一起封装为一个handlerexecutionchain对象。这里在进行封装的时候,spring会对当前request是否为跨域请求进行判断,如果是跨域请求,则将相关的跨域配置封装到handlerexecutionchain中,关于跨域请求,读者可以阅读 跨域资源共享 cors 详解 。

2. 获取handlermethod

关于requestmappinghandlermapping是如何获取handler的,其主要在gethandlerinternal()方法中,如下是该方法的源码:

?

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

@override

protected handlermethod gethandlerinternal(httpservletrequest request) throws exception {

  // 获取当前request的uri

  string lookuppath = geturlpathhelper().getlookuppathforrequest(request);

  if (logger.isdebugenabled()) {

   logger.debug( "looking up handler method for path " + lookuppath);

  }

  // 获取注册的mapping的读锁

  this .mappingregistry.acquirereadlock();

  try {

   // 通过path和request查找具体的handlermethod

   handlermethod handlermethod = lookuphandlermethod(lookuppath, request);

   if (logger.isdebugenabled()) {

    if (handlermethod != null ) {

     logger.debug( "returning handler method [" + handlermethod + "]" );

    } else {

     logger.debug( "did not find handler method for [" + lookuppath + "]" );

    }

   }

   // 如果获取到的bean是一个string类型的,则在beanfactory中查找该bean,

   // 并将其封装为一个handlermethod对象

   return (handlermethod != null ? handlermethod.createwithresolvedbean() : null );

  } finally {

   // 释放当前注册的mapping的读锁

   this .mappingregistry.releasereadlock();

  }

}

上述方法中,其首先会获取当前request的uri,然后通过uri查找handlermethod,并且在最后,会判断获取到的handlermethod中的bean是否为string类型的,如果是,则在当前beanfactory中查找该名称的bean,并且将其封装为handlermethod对象。这里我们直接阅读lookuphandlermethod()方法:

?

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

@nullable

protected handlermethod lookuphandlermethod(string lookuppath,

   httpservletrequest request) throws exception {

  list<match> matches = new arraylist<>();

  // 通过uri直接在注册的requestmapping中获取对应的requestmappinginfo列表,需要注意的是,

  // 这里进行查找的方式只是通过url进行查找,但是具体哪些requestmappinginfo是匹配的,还需要进一步过滤

  list<t> directpathmatches = this .mappingregistry.getmappingsbyurl(lookuppath);

  if (directpathmatches != null ) {

   // 对获取到的requestmappinginfo进行进一步过滤,并且将过滤结果封装为一个match列表

   addmatchingmappings(directpathmatches, matches, request);

  }

  if (matches.isempty()) {

   // 如果无法通过uri进行直接匹配,则对所有的注册的requestmapping进行匹配,这里无法通过uri

   // 匹配的情况主要有三种:

   // ①在requestmapping中定义的是pathvariable,如/user/detail/{id};

   // ②在requestmapping中定义了问号表达式,如/user/?etail;

   // ③在requestmapping中定义了*或**匹配,如/user/detail/**

   addmatchingmappings( this .mappingregistry.getmappings().keyset(),

    matches, request);

  }

 

  if (!matches.isempty()) {

   // 对匹配的结果进行排序,获取相似度最高的一个作为结果返回,这里对相似度的判断时,

   // 会判断前两个是否相似度是一样的,如果是一样的,则直接抛出异常,如果不相同,

   // 则直接返回最高的一个

   comparator<match> comparator = new matchcomparator(getmappingcomparator(request));

   matches.sort(comparator);

   if (logger.istraceenabled()) {

    logger.trace( "found " + matches.size()

        + " matching mapping(s) for [" + lookuppath + "] : " + matches);

   }

   // 获取匹配程度最高的一个匹配结果

   match bestmatch = matches.get( 0 );

   if (matches.size() > 1 ) {

    // 如果匹配结果不止一个,首先会判断是否是跨域请求,如果是,

    // 则返回preflight_ambiguous_match,如果不是,则会判断前两个匹配程度是否相同,

    // 如果相同则抛出异常

    if (corsutils.ispreflightrequest(request)) {

     return preflight_ambiguous_match;

    }

    match secondbestmatch = matches.get( 1 );

    if (comparator测试数据pare(bestmatch, secondbestmatch) == 0 ) {

     method m1 = bestmatch.handlermethod.getmethod();

     method m2 = secondbestmatch.handlermethod.getmethod();

     throw new illegalstateexception( "ambiguous handler methods mapped for"

      + " http path '" + request.getrequesturl() + "': {" + m1

      + ", " + m2 + "}" );

    }

   }

   // 这里主要是对匹配结果的一个处理,主要包含对传入参数和返回的mediatype的处理

   handlematch(bestmatch.mapping, lookuppath, request);

   return bestmatch.handlermethod;

  } else {

   // 如果匹配结果是空的,则对所有注册的mapping进行遍历,判断当前request具体是哪种情况导致

   // 的无法匹配:①requestmethod无法匹配;②consumes无法匹配;③produces无法匹配;

   // ④params无法匹配

   return handlenomatch( this .mappingregistry.getmappings().keyset(),

    lookuppath, request);

  }

}

这里对于结果的匹配,首先会通过uri进行直接匹配,如果能匹配到,则在匹配结果中尝试进行requestmethod,consumes和produces等配置的匹配;如果通过uri不能匹配到,则直接对所有定义的requestmapping进行匹配,这里主要是进行正则匹配,如果能匹配到。如果能够匹配到,则对匹配结果按照相似度进行排序,并且对前两个结果相似度进行比较,如果相似度一样,则抛出异常,如果不一样,则返回相似度最高的一个匹配结果。如果无法获取到匹配结果,则对所有的匹配结果进行遍历,判断当前request具体是哪一部分参数无法匹配到结果。对于匹配结果的获取,主要在addmatchingmappings()方法中,这里我们继续阅读该方法的源码:

?

1

2

3

4

5

6

7

8

9

10

private void addmatchingmappings(collection<t> mappings, list<match> matches,

   httpservletrequest request) {

  for (t mapping : mappings) {

   t match = getmatchingmapping(mapping, request);

   if (match != null ) {

    matches.add( new match(match,

     this .mappingregistry.getmappings().get(mapping)));

   }

  }

}

对于requestmapping的匹配,这里逻辑比较简单,就是对所有的requestmappinginfo进行遍历,然后将request分别于每个requestmappinginfo进行匹配,如果匹配上了,其返回值就不为空,最后将所有的匹配结果返回。如下是getmatchingmapping()方法的源码(其最终调用的是requestmappinginfo.getmatchingcondition()方法):

?

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

@override

@nullable

public requestmappinginfo getmatchingcondition(httpservletrequest request) {

  // 判断request请求的类型是否与当前requestmethod匹配

  requestmethodsrequestcondition methods =

   this .methodscondition.getmatchingcondition(request);

  // 判断request请求的参数是否与requestmapping中params参数配置的一致

  paramsrequestcondition params = this .paramscondition.getmatchingcondition(request);

  // 判断request请求的headers是否与requestmapping中headers参数配置的一致

  headersrequestcondition headers = this .headerscondition.getmatchingcondition(request);

  // 判断request的请求体类型是否与requestmapping中配置的consumes参数配置的一致

  consumesrequestcondition consumes =

   this .consumescondition.getmatchingcondition(request);

  // 判断当前requestmapping将要返回的请求体类型是否与request中accept的header指定的一致

  producesrequestcondition produces =

   this .producescondition.getmatchingcondition(request);

 

  // 对于上述几个判断,如果匹配上了,那么其返回值都不会为空,因而这里会对每个返回值都进行判断,

  // 如果有任意一个为空,则说明没匹配上,那么就返回null

  if (methods == null || params == null || headers == null

   || consumes == null || produces == null ) {

   return null ;

  }

 

  // 对于前面的匹配,都是一些静态属性的匹配,其中最重要的uri的匹配,主要是正则匹配,

  // 就是在下面这个方法中进行的

  patternsrequestcondition patterns =

   this .patternscondition.getmatchingcondition(request);

  // 如果uri没匹配上,则返回null

  if (patterns == null ) {

   return null ;

  }

 

  // 这里主要是对用户自定义的匹配条件进行匹配

  requestconditionholder custom =

   this .customconditionholder.getmatchingcondition(request);

  if (custom == null ) {

   return null ;

  }

 

  // 如果上述所有条件都匹配上了,那么就将匹配结果封装为一个requestmappinginfo返回

  return new requestmappinginfo( this .name, patterns, methods, params, headers,

   consumes, produces, custom.getcondition());

}

可以看到,对于一个requestmapping的匹配,主要包括:requestmethod,params,headers,consumes,produces,uri和自定义条件的匹配,如果这几个条件都匹配上了,才能表明当前requestmapping与request匹配上了。

3. interceptor的封装

关于inteceptor的封装,由前述第一点可以看出,其主要在gethandlerexecutionchain()方法中,如下是该方法的源码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

protected handlerexecutionchain gethandlerexecutionchain(object handler,

   httpservletrequest request) {

  // 将当前handler封装到handlerexecutionchain对象中

  handlerexecutionchain chain = (handler instanceof handlerexecutionchain ?

   (handlerexecutionchain) handler : new handlerexecutionchain(handler));

 

  // 获取当前request的uri,用于mappedinterceptor的匹配

  string lookuppath = this .urlpathhelper.getlookuppathforrequest(request);

  // 对当前所有注册的interceptor进行遍历,如果其是mappedinterceptor类型,则调用其matches()

  // 方法,判断当前interceptor是否能够应用于该request,如果可以,则添加到handlerexecutionchain中

  for (handlerinterceptor interceptor : this .adaptedinterceptors) {

   if (interceptor instanceof mappedinterceptor) {

    mappedinterceptor mappedinterceptor = (mappedinterceptor) interceptor;

    if (mappedinterceptor.matches(lookuppath, this .pathmatcher)) {

     chain.addinterceptor(mappedinterceptor.getinterceptor());

    }

   } else {

    // 如果当前interceptor不是mappedinterceptor类型,则直接将其添加到

    // handlerexecutionchain中

    chain.addinterceptor(interceptor);

   }

  }

  return chain;

}

对于拦截器,理论上,spring是会将所有的拦截器都进行一次调用,对于是否需要进行拦截,都是用户自定义实现的。这里如果对于uri有特殊的匹配,可以使用mappedinterceptor,然后实现其matches()方法,用于判断当前mappedinterceptor是否能够应用于当前request。

4. 小结

本文首先讲解了spring是如何通过request进行匹配,从而找到具体处理当前请求的requestmapping的,然后讲解了spring是如何封装interceptor,将handlermethod和interceptor封装为一个handlerexecutionchain的。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

原文链接:https://my.oschina.net/zhangxufeng/blog/2177464

查看更多关于Spring MVC学习教程之RequestMappingHandlerMapping匹配的详细内容...

  阅读:20次