Spring MVC源碼解析(三):讀取@Controller@RequestMapping信息

我們平時(shí)在使用Spring MVC進(jìn)行WEB開(kāi)發(fā)的時(shí)候都會(huì)使用@Controller跟@RequestMapping注解定義控制器實(shí)體跟處理請(qǐng)求的方法的,讓我們從@Controller跟@ReuqstMapping這兩個(gè)注解開(kāi)始就看Spring MVC工作的

從JavaDoc上獲取有用的信息

我們先來(lái)看一下@Controller的JavaDoc吧,但因?yàn)閺腀Controller的JavaDoc上看不到什么有用的信息這里也就不貼它的代碼了.從@Controller的JavaDoc上來(lái)可以知道以下信息:

  1. 表明這個(gè)類在WEB中是一個(gè)Controller
  2. Spring在掃描類的時(shí)候會(huì)把它按照@Component一樣處理
  3. 和@RequestMapping注解配合使用
/**
 * Annotation for mapping web requests onto methods in request-handling classes
 * with flexible method signatures.
 *
 * <p>Both Spring MVC and Spring WebFlux support this annotation through a
 * {@code RequestMappingHandlerMapping} and {@code RequestMappingHandlerAdapter}
 * in their respective modules and package structure. For the exact list of
 * supported handler method arguments and return types in each, please use the
 * reference documentation links below:
 * <ul>
 * <li>Spring MVC
 * <a >Method Arguments</a>
 * and
 * <a >Return Values</a>
 * </li>
* </ul>
 * @author Juergen Hoeller
 * @author Arjen Poutsma
 * @author Sam Brannen
 * @since 2.5
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping

從@RequestMapping的JavaDoc可以知道Spring MVC是通過(guò)RequestMappingHandlerMapping跟RequestMappingHandlerAdapter來(lái)實(shí)現(xiàn)對(duì)@RequestMapping的支持的,以及對(duì)支持什么參數(shù)跟什么返回值(這些跟本文無(wú)關(guān)有興趣的同學(xué)可以點(diǎn)擊JavaDoc上的鏈接地址查看文檔)

@RequestMapping跟@Controller的注解信息是如何被讀取的

順著上面的注解信息查看RequestMappingHandlerMapping類的JavaDoc發(fā)現(xiàn)一句比較重要的線索Creates RequestMappingInfo instances from type and method-level @RequestMapping annotations in @Controller classes(讀取被@Controller標(biāo)記的類下被@RequestMapping標(biāo)記的方法的信息封裝成@RequestInfo)
從RequestMappingHandlerMapping上找到以下相關(guān)方法

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method);
        if (info != null) {
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
            String prefix = getPathPrefix(handlerType);
            if (prefix != null) {
                info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
            }
        }
        return info;
    }
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = (element instanceof Class ?
                getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = (element instanceof Class ?
                getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }

從上面的代碼Spring Mvc在讀取@RequestMapping的步驟如下:

  1. 先分別從方法跟類上面的@RequestMapping注解信息封裝成一個(gè)RequestMappingInfo 信息
    1.1 RequestMappingInfo由多個(gè)RequestCondition組成,RequestCondition代表一個(gè)HttpServletRequest的匹配規(guī)則
    1.2 RequestCondition有多種子類用于判斷不同的類型,例如RequestMethodsRequestCondition就是判斷Http請(qǐng)求的
    1.3 兩個(gè)RequestMappingInfo整合成一個(gè)的時(shí)候會(huì)把Spring 定義的RequestCondition會(huì)合并在一起,正因?yàn)檫@樣在類上配置注解設(shè)配get并且方法上設(shè)置設(shè)配post的話這個(gè)方法可以處理get,post http請(qǐng)求
    1.4 我們可以通過(guò)繼承RequestMappingHandlerMapping類并從寫(xiě)getCustomTypeCondition跟getCustomMethodCondition來(lái)定制自己需要的匹配規(guī)則,在將它注入到Spring ioc時(shí)設(shè)置@Order注解配置適當(dāng)?shù)膬?yōu)先順序讓它在RequestMappingHandlerMapping前設(shè)配http
  2. 將從方法跟類上讀取到RequestMappingInfo整個(gè)一個(gè)
  3. 判斷一個(gè)類是否將映射的url添加前綴,可以通過(guò)WebMvcConfigurer類的configurePathMatch方法進(jìn)行配置,例如通過(guò)如下配置定義所有被@Wkx注解的Controller類都需要添加/wkx前綴
@Configuration
public class WebConfig implements WebMvcConfigurer
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("wkx", clazz -> clazz.isAnnotationPresent(Wkx.class));
    }
}

RequestMappingHandlerMapping是在什么時(shí)候讀取@RequestMapping信息

image.png

通過(guò)idea的Call Hierarchy的知道RequestMappingHandlerMapping會(huì)在其afterPropertiesSet生命周期讀取的

public class AbstractHandlerMethodMapping {
    protected void initHandlerMethods() {
        // 1 獲取Spring ApplicationContext中所有的裝配Bean的名稱
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                // 2 對(duì)所有Bean讀取@RequestMapping信息 
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
}

    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
            if (logger.isTraceEnabled()) {
                logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
            }
        }
        // 3 對(duì)所有被@Controller類讀取@RequestMapping信息
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }

      protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            // 4 使用工具類讀取所有方法上的@RequestMapping信息  ps:國(guó)產(chǎn)開(kāi)源任務(wù)調(diào)度框架也是使用這個(gè)工具類來(lái)讀取@JobHandler注解的
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            // 5 終于輪到重頭戲了上面說(shuō)的RequestMappingHandlerMapping具體讀取@RequestMapping信息
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            if (logger.isTraceEnabled()) {
                logger.trace(formatMappings(userType, methods));
            }
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                // 6 注冊(cè)前面讀取的RequestMappingInfo存儲(chǔ)起來(lái)
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }
}

將讀取的RequestMappingInfo保存起來(lái)

從前面我們知道Spring mvc在啟動(dòng)的時(shí)候會(huì)讀取@RequestMapping信息并存儲(chǔ)到內(nèi)存中,接下來(lái)我們看看是怎么存儲(chǔ)起來(lái)的
RequestMappingHandlerMapping 會(huì)先調(diào)用其父類的registerHandlerMethod方法,其父類會(huì)把注冊(cè)規(guī)則交給MappingRegistry實(shí)現(xiàn)

class MappingRegistry {
  
        public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
            try {
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                validateMethodMapping(handlerMethod, mapping);
                this.mappingLookup.put(mapping, handlerMethod);

                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    this.urlLookup.add(url, mapping);
                }

                String name = null;
                if (getNamingStrategy() != null) {
                    name = getNamingStrategy().getName(handlerMethod, mapping);
                    addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }

                this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }
}

從上面的源碼我們注冊(cè)流程如下

  1. 先將method跟相關(guān)類實(shí)例封裝成一個(gè)HandlerMethod方便后續(xù)調(diào)用
  2. 以RequestMappingInfo為Key,HandlerMethod為Value映射到map中
  3. 以Url為Key,RequestMapping為多值Value映射到一個(gè)MultiValueMap中
  4. 以RequestMapping的name值為key,以HandlerMethod為Value映射到map中
  5. 獲取讀取方法上的類跟方法上的@CrossOrigin注解信息封裝成CorsConfiguration,并以HandlerMethod為Key,CorsConfiguration為Value映射到一個(gè)map中

看到這里可以猜測(cè)Spring通過(guò)HttpServletRequest的url是如何找到相關(guān)HandlerMethod并來(lái)處理請(qǐng)求的.有點(diǎn)類似與有點(diǎn)類似與電商系統(tǒng)中根據(jù)訂單號(hào)獲取訂單的商品信息的來(lái)進(jìn)行發(fā)貨的.假設(shè)訂單只有一個(gè)商品.首先根據(jù)訂單號(hào)(url)找到訂單(ReuestMapping),多個(gè)訂單(RequestMappingInfo)可以與一個(gè)商品(HandlerMethod)相關(guān)聯(lián),找到商品(HandlerMethod)之后查看是否有物流發(fā)貨配置(CorsConfiguration)某些獲取像生鮮只能走冷鏈

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容