Spring MVC 解讀——@RequestMapping (1)

Spring MVC 解讀——@RequestMapping

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

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

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

    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。

  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註解的。不知你們還記不記的這段代碼:

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方法的執行喚醒嘛,咱們來看它的實現:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {//這裏檢測當前Bean是否實現一些列Aware接口,並調用相關方法,咱們不關心。
            invokeAwareMethods(beanName, bean);
        }
        
        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回調,不關心
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
            invokeInitMethods(beanName, wrappedBean, mbd);//這是咱們須要關心的,下面看下它的實現
        }

        if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回調,不關心
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

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

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
            throws Throwable {
        //是不是InitializingBean的實例
        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && 
                (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        public Object run() throws Exception {//利用系統安全管理器調用
                            ((InitializingBean) bean).afterPropertiesSet();
                            return null;
                        }
                    }, getAccessControlContext());
                }
            }                
            else {//調用InitializingBean的afterPropertiesSet方法。
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }
        //調用自定義初始化方法。。。省略,不關心
    }

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

public void afterPropertiesSet() {
        initHandlerMethods();
    }

    //Scan beans in the ApplicationContext, detect and register handler methods.
    protected void initHandlerMethods() {
        //掃描全部註冊的Bean
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
           BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), 
                Object.class) : getApplicationContext().getBeanNamesForType(Object.class));
        //遍歷這些Bean,依次判斷是不是處理器,並檢測其HandlerMethod
        for (String beanName : beanNames) {
            if (isHandler(getApplicationContext().getType(beanName))){
                detectHandlerMethods(beanName);
            }
        }
        //這個方法是個空實現,無論他
        handlerMethodsInitialized(getHandlerMethods());
    }

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

3、檢測@RequestMapping   

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

@Override
    protected boolean isHandler(Class<?> beanType) {
        return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
                (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
    }

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

protected void detectHandlerMethods(final Object handler) {
        Class<?> handlerType = (handler instanceof String) ?
                getApplicationContext().getType((String) handler) : handler.getClass();
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter(){
            public boolean matches(Method method) {//只選擇被@RequestMapping標記的方法
                return getMappingForMethod(method, userType) != null;
            }
        });

        for (Method method : methods) {
            //根據方法上的@RequestMapping來建立RequestMappingInfo實例。
            T mapping = getMappingForMethod(method, userType);
            //註冊請求映射
            registerHandlerMethod(handler, method, mapping);
        }
    }

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

    下面咱們看看細節:

@Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = null;
        //獲取方法method上的@RequestMapping實例。
        RequestMapping methodAnnotation = 
                                AnnotationUtils.findAnnotation(method, RequestMapping.class);
        if (methodAnnotation != null) {//方法被註解了
            RequestCondition<?> methodCondition = getCustomMethodCondition(method);//始終返回null
            info = createRequestMappingInfo(methodAnnotation, methodCondition);//建立MappingInfo
            //檢查方法所屬的類有沒有@RequestMapping註解
            RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, 
                                                                        RequestMapping.class);
            if (typeAnnotation != null) {//有類層次的@RequestMapping註解
                RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);//null
                //將類層次的RequestMapping和方法級別的RequestMapping結合
                info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
            }
        }
        return info;
    }

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

private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, 
                                                        RequestCondition<?> customCondition) {
    return new RequestMappingInfo(
         new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(),
                 this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
         new RequestMethodsRequestCondition(annotation.method()),
         new ParamsRequestCondition(annotation.params()),
         new HeadersRequestCondition(annotation.headers()),
         new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
         new ProducesRequestCondition(annotation.produces(), annotation.headers(), 
                    getContentNegotiationManager()), 
        customCondition
    );
}

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

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

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

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

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

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

public RequestMappingInfo combine(RequestMappingInfo other) {
    PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
    RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
    ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
    HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
    ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
    ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
    RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

    return new RequestMappingInfo(patterns, methods, params, headers, consumes, 
                    produces, custom.getCondition());
}

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

public PatternsRequestCondition combine(PatternsRequestCondition other) {
        Set<String> result = new LinkedHashSet<String>();
        if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
            for (String pattern1 : this.patterns) {
                for (String pattern2 : other.patterns) {
                    result.add(this.pathMatcher.combine(pattern1, pattern2));
                }
            }
        }
        else if (!this.patterns.isEmpty()) {
            result.addAll(this.patterns);
        }
        else if (!other.patterns.isEmpty()) {
            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的組合,重點是考慮進各類狀況,咱們來看下方法的註釋吧:

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

4、註冊請求映射

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

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

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
        HandlerMethod handlerMethod;
        if (handler instanceof String) {
            String beanName = (String) handler;
            handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
        }
        else {
            handlerMethod = new HandlerMethod(handler, method);
        }
        //上面幾行是根據新的處理器實例,方法實例,RequestMappingInfo來生成新的HandlerMethod實例
        //下面是從緩存中查看是否有存在的HandlerMethod實例,若是有而且不相等則拋出異常
        HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
        if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
            throw new IllegalStateException();
        }
        //handlerMethods 是一個Map鍵是RequestMappingInfo對象,值是HandlerMethod實例
        //所以一個HandlerMethod實例可能處理多個mapping,而一個mapping實例只能由一個method處理
        this.handlerMethods.put(mapping, handlerMethod);
        //這裏獲取mapping實例中的全部url。
        Set<String> patterns = getMappingPathPatterns(mapping);
        for (String pattern : patterns) {
            if (!getPathMatcher().isPattern(pattern)) {
                //urlMap也是Map,鍵是url 模式,值是RequestMappingInfo實例
                //所以一個mapping實例可能對應多個pattern,可是一個pattern只能對應一個mapping實例
                this.urlMap.add(pattern, mapping);
            }
        }
    }

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

5、承上啓下

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

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

相關文章
相關標籤/搜索