Spring MVC — @RequestMapping原理講解-1

轉載地址 :http://blog.csdn.net/j080624/article/details/56278461

爲了下降文章篇幅,使得文章更目標化,簡潔化,咱們就不例舉各類@RequestMapping的用法等內容了.

    文章主要說明如下問題:html

    1. Spring怎樣處理@RequestMapping(怎樣將請求路徑映射到控制器類或方法)java

    2. Spring怎樣將請求分派給正確的控制器類或方法web

    3. Spring如何實現靈活的控制器方法的spring

    在Spring MVC 3.1 以前的版本中,Spring默認使用 DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter來處理 @RequestMapping註解和請求方法調用,而從3.1開始提供了一組新的API完成這些工做。相比之下,新的API更加的合理完善,開放,易拓 展,面向對象。這篇文章即是基於3.1的新API進行剖析的。api

1、概念解析

    在開始以前咱們先了解下新的API中引入的新接口或者類,這會有助於後面的處理過程的理解。不得不說新的API提供了更多漂亮的抽象,你能感覺到面向對象的魅力。數組

  1. RequestMappingInfo 這個類是對請求映射的一個抽象,它包含了請求路徑,請求方法,請求頭等信息。其實能夠看作是@RequestMapping的一個對應類。緩存

  2. HandlerMethod這個類封裝了處理器實例(Controller Bean)和 處理方法實例(Method)以及方法參數數組(MethodParameter[])安全

  3. MethodParameter  這個類從2.0就有了,它封裝了方法某個參數的相關信息及行爲,如該參數的索引,該參數所屬方法實例或構造器實例,該參數的類型等。mvc

  4. HandlerMapping 該接口的實現類用來定義請求和處理器以前的映射關係,其中只定義了一個方法getHandler。app

  5. AbstractHandlerMethodMapping 這是HandlerMapping的一個基本實現類,該類定義了請求與HandlerMethod實例的映射關係。

  6. RequestMappingInfoHandlerMapping這個是AbstractHandlerMethodMapping的實現類,他維護了一個RequestMappingInfo和HandlerMethod的Map屬性。

  7. RequestMappingHandlerMapping 這個是RequestMappingInfoHandlerMapping的子類,它將@RequestMapping註解轉化爲RequestMappingInfo實例,併爲父類使用。也就是咱們處理@RequestMapping的終點。

  8. InitializingBean 這個接口定義了其實現Bean在容器完成屬性設置後能夠執行自定義初始化操做,咱們的AbstractHandlerMethodMapping便實現了這個接口,而且定義了一組自定義操做,就是用來檢測處理咱們的@RequestMapping註解。

    概念講的太多總不是什麼好事。但明白了上述概念基本上就成功一半了,其中的實現相對@Autowired那篇簡單多了。

2、InitialiZingBean.afterPropertySet()

    咱們從頭開始,看看到底Spring是怎樣檢測並處理咱們@RequestMapping註解的。不知你們還記不記的這段代碼:

 

[java]  view plain  copy
  1. Object exposedObject = bean;  
  2.         try {  
  3.             populateBean(beanName, mbd, instanceWrapper);  
  4.             if (exposedObject != null) {  
  5.                 exposedObject = initializeBean(beanName, exposedObject, mbd);  
  6.             }  
  7.         }  

   這是BeanFactory建立Bean過程當中須要執行的一段代碼,其中populateBean方法即是@Autowired註解的處理過程,執行的屬性的自動注入等操做。由於initializeBean方法當時與主題無關沒有講,不過這時它即是咱們關注的焦點了。

    上面概念中咱們講到InitiaizingBean接口,它的實現Bean會在容器完成屬性注入後執行一個自定義操做,這不就知足initializeBean方法的執行喚醒嘛,咱們來看它的實現:

[java]  view plain  copy
  1. protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {  
  2.         if (System.getSecurityManager() != null) {  
  3.             AccessController.doPrivileged(new PrivilegedAction<Object>() {  
  4.                 public Object run() {  
  5.                     invokeAwareMethods(beanName, bean);  
  6.                     return null;  
  7.                 }  
  8.             }, getAccessControlContext());  
  9.         }  
  10.         else {//這裏檢測當前Bean是否實現一些列Aware接口,並調用相關方法,咱們不關心。  
  11.             invokeAwareMethods(beanName, bean);  
  12.         }  
  13.           
  14.         Object wrappedBean = bean;  
  15.         if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回調,不關心  
  16.             wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);  
  17.         }  
  18.   
  19.         try {  
  20.             invokeInitMethods(beanName, wrappedBean, mbd);//這是咱們須要關心的,下面看下它的實現  
  21.         }  
  22.   
  23.         if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回調,不關心  
  24.             wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);  
  25.         }  
  26.         return wrappedBean;  
  27.     }  

   咱們接着來看下invokeInitMethods方法的實現:

 

[java]  view plain  copy
  1. protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)  
  2.             throws Throwable {  
  3.         //是不是InitializingBean的實例  
  4.         boolean isInitializingBean = (bean instanceof InitializingBean);  
  5.         if (isInitializingBean &&   
  6.                 (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {  
  7.             if (System.getSecurityManager() != null) {  
  8.                 try {  
  9.                     AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {  
  10.                         public Object run() throws Exception {//利用系統安全管理器調用  
  11.                             ((InitializingBean) bean).afterPropertiesSet();  
  12.                             return null;  
  13.                         }  
  14.                     }, getAccessControlContext());  
  15.                 }  
  16.             }                  
  17.             else {//調用InitializingBean的afterPropertiesSet方法。  
  18.                 ((InitializingBean) bean).afterPropertiesSet();  
  19.             }  
  20.         }  
  21.         //調用自定義初始化方法。。。省略,不關心  
  22.     }  

   上一篇關於<mvc:annotation-driven/>的文章咱們說過了,當在配置文件中加上該標記後,Spring(3.1後)會默認爲咱們註冊RequestMappingHandlerMapping等Bean定義。而RequestMappingHandlerMapping實現了InitializingBean接口,所以,在初始化並裝配該Bean實例時,執行到上述代碼是,便會執行他的afterPropertySet方法。咱們接下來看看他的afterPropertySet方法:

 

[java]  view plain  copy
  1. public void afterPropertiesSet() {  
  2.         initHandlerMethods();  
  3.     }  
  4.   
  5.     //Scan beans in the ApplicationContext, detect and register handler methods.  
  6.     protected void initHandlerMethods() {  
  7.         //掃描全部註冊的Bean  
  8.         String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?  
  9.            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(),   
  10.                 Object.class) : getApplicationContext().getBeanNamesForType(Object.class));  
  11.         //遍歷這些Bean,依次判斷是不是處理器,並檢測其HandlerMethod  
  12.         for (String beanName : beanNames) {  
  13.             if (isHandler(getApplicationContext().getType(beanName))){  
  14.                 detectHandlerMethods(beanName);  
  15.             }  
  16.         }  
  17.         //這個方法是個空實現,無論他  
  18.         handlerMethodsInitialized(getHandlerMethods());  
  19.     }  

   它直接調用了initHandlerMethods()方法,而且該方法被描述爲:掃描ApplicationContext中的beans,檢測並註冊處理器方法。we are close。

3、檢測@RequestMapping   

    咱們再看它是怎樣判斷是不是處理器的,以及怎麼detect Handler Methods 的:

 

[java]  view plain  copy
  1. @Override  
  2.     protected boolean isHandler(Class<?> beanType) {  
  3.         return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||  
  4.                 (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));  
  5.     }  

   啊哈,很簡單,就是看看有沒有被@Controller或者@RequestMapping註解標記

 

[java]  view plain  copy
  1. protected void detectHandlerMethods(final Object handler) {  
  2.         Class<?> handlerType = (handler instanceof String) ?  
  3.                 getApplicationContext().getType((String) handler) : handler.getClass();  
  4.         final Class<?> userType = ClassUtils.getUserClass(handlerType);  
  5.         Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter(){  
  6.             public boolean matches(Method method) {//只選擇被@RequestMapping標記的方法  
  7.                 return getMappingForMethod(method, userType) != null;  
  8.             }  
  9.         });  
  10.   
  11.         for (Method method : methods) {  
  12.             //根據方法上的@RequestMapping來建立RequestMappingInfo實例。  
  13.             T mapping = getMappingForMethod(method, userType);  
  14.             //註冊請求映射  
  15.             registerHandlerMethod(handler, method, mapping);  
  16.         }  
  17.     }  

   整個的檢測過程大體清楚了:1)遍歷Handler中的全部方法,找出其中被@RequestMapping註解標記的方法。2)而後遍歷這些方法,生成RequestMappingInfo實例。3)將RequestMappingInfo實例以及處理器方法註冊到緩存中。

    下面咱們看看細節:

 

[java]  view plain  copy
  1. @Override  
  2.     protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {  
  3.         RequestMappingInfo info = null;  
  4.         //獲取方法method上的@RequestMapping實例。  
  5.         RequestMapping methodAnnotation =   
  6.                                 AnnotationUtils.findAnnotation(method, RequestMapping.class);  
  7.         if (methodAnnotation != null) {//方法被註解了  
  8.             RequestCondition<?> methodCondition = getCustomMethodCondition(method);//始終返回null  
  9.             info = createRequestMappingInfo(methodAnnotation, methodCondition);//建立MappingInfo  
  10.             //檢查方法所屬的類有沒有@RequestMapping註解  
  11.             RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType,   
  12.                                                                         RequestMapping.class);  
  13.             if (typeAnnotation != null) {//有類層次的@RequestMapping註解  
  14.                 RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);//null  
  15.                 //將類層次的RequestMapping和方法級別的RequestMapping結合  
  16.                 info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);  
  17.             }  
  18.         }  
  19.         return info;  
  20.     }  

   很清晰吧,先獲取方法上的@RequestMapping信息,而後獲取類級別上的@RequestMapping 信息,而後將二者結合,這裏咱們有必要再瞭解下怎樣建立RequestMappingInfo對象的(包括他的內部結構),以及怎樣將類級別的request mapping信息和方法級別的進行結合的?

 

[java]  view plain  copy
  1. private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation,   
  2.                                                         RequestCondition<?> customCondition) {  
  3.     return new RequestMappingInfo(  
  4.          new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(),  
  5.                  this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),  
  6.          new RequestMethodsRequestCondition(annotation.method()),  
  7.          new ParamsRequestCondition(annotation.params()),  
  8.          new HeadersRequestCondition(annotation.headers()),  
  9.          new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),  
  10.          new ProducesRequestCondition(annotation.produces(), annotation.headers(),   
  11.                     getContentNegotiationManager()),   
  12.         customCondition  
  13.     );  
  14. }  

   其中涉及到了幾個類,咱們大體瞭解下含義:

  • PatternRequestCondition 它其實就是URL模式的封裝,它包含了一個URL模式的Set集合。其實就是@RequestMapping註解中的value值得封裝。

  • RequestMethodRequestCondition 它是@RequestMapping 註解中method屬性的封裝

  • ParamsRequestCondition 它是@RequestMapping註解中params屬性的封裝

    等等,依次類推。所以RequestMappingInfo其實就是對@RquestMapping 的封裝。

    下面咱們再看看怎樣進行Combine的:

 

[java]  view plain  copy
  1. public RequestMappingInfo combine(RequestMappingInfo other) {  
  2.     PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);  
  3.     RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);  
  4.     ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);  
  5.     HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);  
  6.     ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);  
  7.     ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);  
  8.     RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);  
  9.   
  10.     return new RequestMappingInfo(patterns, methods, params, headers, consumes,   
  11.                     produces, custom.getCondition());  
  12. }<span style="white-space:pre;">    </span>  

   很清晰,對每個元素都進行combine操做,咱們這裏只看PatternRequestCondition是怎麼結合的,就是看看怎樣合併url的。其餘沒太大必要。

 

[java]  view plain  copy
  1. public PatternsRequestCondition combine(PatternsRequestCondition other) {  
  2.         Set<String> result = new LinkedHashSet<String>();  
  3.         if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {  
  4.             for (String pattern1 : this.patterns) {  
  5.                 for (String pattern2 : other.patterns) {  
  6.                     result.add(this.pathMatcher.combine(pattern1, pattern2));  
  7.                 }  
  8.             }  
  9.         }  
  10.         else if (!this.patterns.isEmpty()) {  
  11.             result.addAll(this.patterns);  
  12.         }  
  13.         else if (!other.patterns.isEmpty()) {  
  14.             result.addAll(other.patterns);  
  15.         }  
  16.         else {  
  17.             result.add("");  
  18.         }  
  19.         return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher,   
  20.                    this.useSuffixPatternMatch,this.useTrailingSlashMatch, this.fileExtensions);  
  21.     }  

    1)兩個pattern都存在是,調用PathMatcher的combine方法合併兩個pattern。

    2)只有一個有時,使用這個。

    3)兩個都沒有時,爲空「」。

    如今真正的url拼接是由PathMatcher來完成的了。咱們就不看他的代碼了就是一串if else的組合,重點是考慮進各類狀況,咱們來看下方法的註釋吧:

    清晰,全面吧,有興趣的能夠看一下代碼,這裏不講了。

4、註冊請求映射

    上面咱們已經講了@RequestMapping的檢測和處理,而且根據@RequestMapping生成了RequestMappingInfo實例,那Spring一定須要將這些信息保存起來,以處理咱們的請求。

    第三節中咱們提到一個方法尚未分析,就是registerHandlerMethod 方法:

 

[java]  view plain  copy
  1. protected void registerHandlerMethod(Object handler, Method method, T mapping) {  
  2.         HandlerMethod handlerMethod;  
  3.         if (handler instanceof String) {  
  4.             String beanName = (String) handler;  
  5.             handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);  
  6.         }  
  7.         else {  
  8.             handlerMethod = new HandlerMethod(handler, method);  
  9.         }  
  10.         //上面幾行是根據新的處理器實例,方法實例,RequestMappingInfo來生成新的HandlerMethod實例  
  11.         //下面是從緩存中查看是否有存在的HandlerMethod實例,若是有而且不相等則拋出異常  
  12.         HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);  
  13.         if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {  
  14.             throw new IllegalStateException();  
  15.         }  
  16.         //handlerMethods 是一個Map鍵是RequestMappingInfo對象,值是HandlerMethod實例  
  17.         //所以一個HandlerMethod實例可能處理多個mapping,而一個mapping實例只能由一個method處理  
  18.         this.handlerMethods.put(mapping, handlerMethod);  
  19.         //這裏獲取mapping實例中的全部url。  
  20.         Set<String> patterns = getMappingPathPatterns(mapping);  
  21.         for (String pattern : patterns) {  
  22.             if (!getPathMatcher().isPattern(pattern)) {  
  23.                 //urlMap也是Map,鍵是url 模式,值是RequestMappingInfo實例  
  24.                 //所以一個mapping實例可能對應多個pattern,可是一個pattern只能對應一個mapping實例  
  25.                 this.urlMap.add(pattern, mapping);  
  26.             }  
  27.         }  
  28.     }  

 

   這裏可能稍微有點繞,其實道理很簡單,當請求到達時,去urlMap中需找匹配的url,以及獲取對應mapping實例,而後去handlerMethods中獲取匹配HandlerMethod實例。

5、承上啓下

    篇幅有些長了,超出字數限制了,只能分紅兩篇了..........................

    這章只分析了咱們前面三個問題中的第一個,可是已經至關接近了。下一篇咱們來說,Spring怎樣處理客戶發來的請求,以及方法調用的。

相關文章
相關標籤/搜索