RequestMapping原理分析和RequestMappingHandlerMapping


轉載https://juejin.im/post/5cbeadb96fb9a031ff0d18b5

源碼版本spring-webmvc-4.3.7.RELEASE

使用Spring MVC的同學一般都會以下方式定義請求地址:

@Controller
@RequestMapping("/test")
public class TestController {

    @RequestMapping(value = {"/show"})
    public String testTest() {
        return "/jsp/index";
    }
}

@Controller注解用來把一個類定義為Controller。

@RequestMapping注解用來把web請求映射到相應的處理函數。

@Controller和@RequestMapping結合起來完成了Spring MVC請求的派發流程。

為什么兩個簡單的注解就能完成這么復雜的功能呢?又和<context:component-scan base-package="xx.xx.xx"/>的位置有什么關系呢?

@RequestMapping流程分析

@RequestMapping流程可以分為下面6步:

  • 1.注冊RequestMappingHandlerMapping bean 。
  • 2.實例化RequestMappingHandlerMapping bean。
  • 3.獲取RequestMappingHandlerMapping bean實例。
  • 4.接收requst請求。
  • 5.在RequestMappingHandlerMapping實例中查找對應的handler。
  • 6.handler處理請求。

為什么是這6步,我們展開分析。

1 注冊RequestMappingHandlerMapping bean

第一步還是先找程序入口。

使用Spring MVC的同學都知道,要想使@RequestMapping注解生效,必須得在xml配置文件中配置< mvc:annotation-driven/>。因此我們以此為突破口開始分析。

在bean 解析、注冊、實例化流程源碼剖析 文中我們知道xml配置文件解析完的下一步就是解析bean。在這里我們繼續對那個方法展開分析。如下:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //如果該元素屬於默認命名空間走此邏輯。Spring的默認namespace為:http://www.springframework.org/schema/beans
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                     //對document中的每個元素都判斷其所屬命名空間,然后走相應的解析邏輯
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            //如果該元素屬於自定義namespace走此邏輯 ,比如AOP,MVC等。
            delegate.parseCustomElement(root);
        }
    }

方法中根據元素的命名空間來進行不同的邏輯處理,如bean、beans等屬於默認命名空間執行parseDefaultElement()方法,其它命名空間執行parseCustomElement()方法。

< mvc:annotation-driven/>元素屬於mvc命名空間,因此進入到 parseCustomElement()方法。

    public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }

進入parseCustomElement(ele, null)方法。

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        //獲取該元素namespace url
        String namespaceUri = getNamespaceURI(ele);
        //得到NamespaceHandlerSupport實現類解析元素
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

進入NamespaceHandlerSupport類的parse()方法。

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //此處得到AnnotationDrivenBeanDefinitionParser類來解析該元素
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }

上面方法分為兩步,(1)獲取元素的解析類。(2)解析元素。

(1)獲取解析類。

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }

Spring MVC中含有多種命名空間,此方法會根據元素所屬命名空間得到相應解析類,參考spring xml 配置文件中標簽的解析,其中< mvc:annotation-driven/>對應的是AnnotationDrivenBeanDefinitionParser解析類。

(2)解析< mvc:annotation-driven/>元素

進入AnnotationDrivenBeanDefinitionParser類的parse()方法。

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);
        XmlReaderContext readerContext = parserContext.getReaderContext();

        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
        parserContext.pushContainingComponent(compDefinition);

        RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);

        //生成RequestMappingHandlerMapping bean信息
        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerMappingDef.getPropertyValues().add("order", 0);
        handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

        ......
        
        //此處HANDLER_MAPPING_BEAN_NAME值為:RequestMappingHandlerMapping類名
        //容器中注冊name為RequestMappingHandlerMapping類名
        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
        
        ......
    }

可以看到上面方法在Spring MVC容器中注冊了一個名為“HANDLER_MAPPING_BEAN_NAME”,類型為RequestMappingHandlerMapping的bean(看此函數的其它代碼,得到同時也注冊了RequestMappingHandlerAdapter等)。

至於這個bean能干嗎,繼續往下分析。

2. RequestMappingHandlerMapping bean實例化

bean注冊完后的下一步就是實例化。

在開始分析實例化流程之前,我們先介紹一下RequestMappingHandlerMapping是個什么樣類。

2.1 RequestMappingHandlerMapping繼承圖

上圖信息比較多,我們查找關鍵信息。可以看到這個類間接實現了HandlerMapping接口,是HandlerMapping類型的實例。

除此之外還實現了ApplicationContextAware和IntitalzingBean 這兩個接口。

2.2 ApplicationContextAware接口

下面是官方介紹

在這里簡要介紹一下這兩個接口:

public interface ApplicationContextAware
extends Aware
Interface to be implemented by any object that wishes to be notified of the  ApplicationContext that it runs in.
 
該接口只包含以下方法:
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
* Set the ApplicationContext that this object runs in.
* Normally this call will be used to initialize the object.
概括一下上面表達的信息:如果一個類實現了ApplicationContextAware接口,Spring容器在初始化該類時候會自動回調該類的setApplicationContext()方法。這個接口主要用來讓實現類得到Spring 容器上下文信息。

2.3 InitializingBean接口

 下面是 官方介紹
public interface InitializingBean
Interface to be implemented by beans that need to react once all their properties have been set by a  BeanFactory: e.g. to perform custom initialization, or merely to check that all mandatory properties have been set.
該接口只包含以下方法:
void afterPropertiesSet() throws Exception;
* Invoked by a BeanFactory after it has set all bean properties supplied
* (and satisfied BeanFactoryAware and ApplicationContextAware).

概括一下上面表達的信息:如果一個bean實現了該接口,Spring 容器初始化bean時會回調afterPropertiesSet()方法。這個接口的主要作用是讓bean在初始化時可以實現一些自定義的操作。

介紹完RequestMappingHandlerMapping類后我們開始對這個類的源碼進行分析。

2.2.4 RequestMappingHandlerMapping類源碼分析

既然RequestMappingHandlerMapping實現了ApplicationContextAware接口,那實例化時候肯定會執行setApplicationContext方法,我們查看其實現邏輯。

    public final void setApplicationContext(ApplicationContext context) throws BeansException {
        if (context == null && !isContextRequired()) {
            // Reset internal context state.
            this.applicationContext = null;
            this.messageSourceAccessor = null;
        }
        else if (this.applicationContext == null) {
            // Initialize with passed-in context.
            if (!requiredContextClass().isInstance(context)) {
                throw new ApplicationContextException(
                        "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
            }
            this.applicationContext = context;
            this.messageSourceAccessor = new MessageSourceAccessor(context);
            initApplicationContext(context);
        }
        else {
            // Ignore reinitialization if same context passed in.
            if (this.applicationContext != context) {
                throw new ApplicationContextException(
                        "Cannot reinitialize with different application context: current one is [" +
                        this.applicationContext + "], passed-in one is [" + context + "]");
            }
        }
    }

可以看到此方法把容器上下文賦值給applicationContext變量,因為現在是Spring MVC容器創建流程,因此此處設置的值就是Spring MVC容器 。

RequestMappingHandlerMapping也實現了InitializingBean接口,當設置完屬性后肯定會回調afterPropertiesSet方法,再看afterPropertiesSet方法邏輯。

    public void afterPropertiesSet() {
        this.config = new RequestMappingInfo.BuilderConfiguration();
        this.config.setUrlPathHelper(getUrlPathHelper());
        this.config.setPathMatcher(getPathMatcher());
        this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
        this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
        this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
        this.config.setContentNegotiationManager(getContentNegotiationManager());

        super.afterPropertiesSet();
    }

上面調用了父類的afterPropertiesSet()方法,沿調用棧繼續查看。

    public void afterPropertiesSet() {
        //初始化handler函數
        initHandlerMethods();
    }

進入initHandlerMethods初始化方法查看邏輯。

    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        //1.獲取容器中所有bean 的name。
        //根據detectHandlerMethodsInAncestorContexts bool變量的值判斷是否獲取父容器中的bean,默認為false。因此這里只獲取Spring MVC容器中的bean,不去查找父容器
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));
        //循環遍歷bean
        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                //2.判斷bean是否含有@Controller或者@RequestMappin注解
                if (beanType != null && isHandler(beanType)) {
                    //3.對含有注解的bean進行處理,獲取handler函數信息。
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

上面函數分為3步。

(1)獲取Spring MVC容器中的bean。

(2)找出含有含有@Controller或者@RequestMappin注解的bean。

(3)對含有注解的bean進行解析。

第1步很簡單就是獲取容器中所有的bean name,我們對第2、3步作分析。

查看isHandler()方法實現邏輯。

    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

上面邏輯很簡單,就是判斷該bean是否有@Controller或@RequestMapping注解,然后返回判斷結果。

如果含有這兩個注解之一就進入detectHandlerMethods()方法進行處理。

查看detectHandlerMethods()方法。

    protected void detectHandlerMethods(final Object handler) {
        //1.獲取bean的類信息
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType((String) handler) : handler.getClass());
        final Class<?> userType = ClassUtils.getUserClass(handlerType);

        //2.遍歷函數獲取有@RequestMapping注解的函數信息
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                new MethodIntrospector.MetadataLookup<T>() {
                    @Override
                    public T inspect(Method method) {
                        try {
                            //如果有@RequestMapping注解,則獲取函數映射信息
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    }
                });

        if (logger.isDebugEnabled()) {
            logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
        }
        //3.遍歷映射函數列表,注冊handler
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
            T mapping = entry.getValue();
            //注冊handler函數
            registerHandlerMethod(handler, invocableMethod, mapping);
        }
    }

上面方法中用了幾個回調,可能看起來比較復雜,其主要功能就是獲取該bean和父接口中所有用@RequestMapping注解的函數信息,並把這些保存到methodMap變量中。

我們對上面方法進行逐步分析,看看如何對有@RequestMapping注解的函數進行解析。

先進入selectMethods()方法查看實現邏輯。

    public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
        final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
        Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
        Class<?> specificHandlerType = null;
        //把自身類添加到handlerTypes中
        if (!Proxy.isProxyClass(targetType)) {
            handlerTypes.add(targetType);
            specificHandlerType = targetType;
        }
        //獲取該bean所有的接口,並添加到handlerTypes中
        handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));

        /對自己及所有實現接口類進行遍歷
        for (Class<?> currentHandlerType : handlerTypes) {
            final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
            //獲取函數映射信息
            ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
                //循環獲取類中的每個函數,通過回調處理
                @Override
                public void doWith(Method method) {
                    //對類中的每個函數進行處理
                    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                    //回調inspect()方法給個函數生成RequestMappingInfo  
                    T result = metadataLookup.inspect(specificMethod);
                    if (result != null) {
                        Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                        if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                            //將生成的RequestMappingInfo保存到methodMap中
                            methodMap.put(specificMethod, result);
                        }
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
        //返回保存函數映射信息后的methodMap
        return methodMap;
    }

上面邏輯中doWith()回調了inspect(),inspect()又回調了getMappingForMethod()方法。

我們看看getMappingForMethod()是如何生成函數信息的。

    @Override
    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);
            }
        }
        return info;
    }

查看createRequestMappingInfo()方法。

    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        //如果該函數含有@RequestMapping注解,則根據其注解信息生成RequestMapping實例,
        //如果該函數沒有@RequestMapping注解則返回空
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = (element instanceof Class ?
                getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        //如果requestMapping不為空,則生成函數信息MAP后返回
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }

看看createRequestMappingInfo是如何實現的。

    protected RequestMappingInfo createRequestMappingInfo(
            RequestMapping requestMapping, RequestCondition<?> customCondition) {

        return RequestMappingInfo
                .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name())
                .customCondition(customCondition)
                .options(this.config)
                .build();
    可以看到上面把RequestMapping注解中的信息都放到一個RequestMappingInfo實例中后返回。當生成含有@RequestMapping注解的函數映射信息后,最后一步是調用registerHandlerMethod 注冊handler和處理函數映射關系。    protectedvoid registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
    }

看到把所有的handler方法都注冊到了mappingRegistry這個變量中。

到此就把RequestMappingHandlerMapping bean的實例化流程就分析完了。

3 獲取RequestMapping bean

這里我們回到Spring MVC容器初始化流程,查看在FrameworkServlet#initWebApplicationContext方法。
    protected WebApplicationContext initWebApplicationContext() {
        //1.獲得rootWebApplicationContext
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        ......
        
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            //2.創建 Spring 容器 
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            //3.初始化容器
            onRefresh(wac);
        }

        ......

        return wac;
    }

前兩步我們在spring與springmvc父子容器一文中分析過,主要是創建Spring MVC容器,這里我們重點看第3步。

進入onRefresh()方法。

    @Override
    protected void onRefresh(ApplicationContext context) {
        //執行初始化策略 
        initStrategies(context);
    }

進入initStrategies方法,該方法進行了很多初始化行為,為減少干擾我們只過濾出與本文相關內容。

    protected void initStrategies(ApplicationContext context) {
        ......
        //初始化HandlerMapping
        initHandlerMappings(context);
        ......
    }

進入initHandlerMappings()方法。

    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            //容器中查找HandlerMapping的實例
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                //把找到的bean放到hanlderMappings中。
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

默認有多個實例,其中就有前面注冊並實例化了的RequestMappingHandlerMapping bean

4 接收請求

DispatchServlet繼承自Servlet,那所有的請求都會在service()方法中進行處理。

查看service()方法。

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //獲取請求方法
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        //若是patch請求執行此邏輯
        if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
            processRequest(request, response);
        }
        else {
            //其它請求走此邏輯
            super.service(request, response);
        }
    }

我們跟着源碼,得到不管get、post最后都會執行到DispatcherServlet#doDispatch(request, response);

5 獲取handler

最終所有的web請求都由doDispatch()方法進行處理,查看其邏輯。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        ......
        //根據請求獲得真正處理的handler
        mappedHandler = getHandler(processedRequest);
        ......
    }

查看getHandler()。

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //獲取HandlerMapping實例
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
             //得到處理請求的handler
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }

這里遍歷handlerMappings獲得所有HandlerMapping實例,還記得handlerMappings變量吧,這就是前面initHandlerMappings()方法中設置進去的值。

可以看到接下來調了用HandlerMapping實例的getHanlder()方法查找handler,看其實現邏輯。

    @Override
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        ......
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        ......
        return executionChain;
    }

進入getHandlerInternal()方法。

    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //獲取函數url
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        ......
        try {
            //查找HandlerMethod 
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            ......
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

進入lookupHandlerMethod()。

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        ......
    }

可以看到上面方法中從mappingRegistry獲取handler,這個mappingRegistry的值還記得是從哪里來的嗎?

就是前面RequestMappingHandlerMapping 實例化過程的最后一步調用registerHandlerMethod()函數時設置進去的。

6 handler處理請求

獲取到相應的handler后剩下的事情就是進行業務邏輯。處理后返回結果,這里基本也沒什么好說的。

到此整個@RequestMapping的流程也分析完畢。

3.小結

認真讀完上面深入分析@RequestMapping注解流程的同學,相信此時肯定對Spring MVC有了更深一步的認識。

在@ReqestMapping解析過程中,initHandlerMethods()函數只是對Spring MVC 容器中的bean進行處理的,並沒有去查找父容器的bean。因此不會對父容器中含有@RequestMapping注解的函數進行處理,更不會生成相應的handler。

所以當請求過來時找不到處理的handler,導致404。

4.尾聲

從上面的分析中,我們知道要使用@RequestMapping注解,必須得把含有@RequestMapping的bean定義到spring-mvc.xml中。

這里也給大家個建議:

因為@RequestMapping一般會和@Controller搭配使。為了防止重復注冊bean,建議在spring-mvc.xml配置文件中只掃描含有Controller bean的包,其它的共用bean的注冊定義到spring.xml文件中。寫法如下:

spring-mvc.xml
<!-- 只掃描@Controller注解 -->
<context:component-scan base-package="com.xxx.controller" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

spring.xml

<!-- 配置掃描注解,不掃描@Controller注解 -->
<context:component-scan base-package="com.xxx">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

use-default-filters屬性默認為true,會掃描所有注解類型的bean 。如果配置成false,就只掃描白名單中定義的bean注解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM