Spring 系列(三):你真的懂@RequestMapping吗?

本文深入探讨了Spring MVC中@RequestMapping注解的工作流程,包括注册RequestMappingHandlerMapping bean、实例化、查找handler等步骤。分析表明,@RequestMapping注解的处理只针对Spring MVC容器中的bean,如果将组件扫描移到Spring父容器,会导致请求处理失败,出现404错误。建议在spring-mvc.xml中仅扫描@Controller相关bean,以避免问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

上篇给大家介绍了Spring MVC父子容器的概念,主要提到的知识点是:

Spring MVC容器是Spring容器的子容器,当在Spring MVC容器中找不到bean的时候就去父容器找。

在文章最后我也给大家也留了一个问题,既然子容器找不到就去父容器找,那干脆把bean定义都放在父容器不就行了?是这样吗,我们做个实验。

我们把<context:component-scan base-package="xx.xx.xx"/> 这条语句从spring-mvc.xml文件中挪到spring.xml中,重启应用。会发现报404,如下图:

404说明请求的资源没有找到,为什么呢?

使用Spring MVC的同学一般都会以下方式定义请求地址:

@Controller
@RequestMapping("/test")
public class Test {
   @RequestMapping(value="/handle", method=RequestMethod.POST)
   public void handle();
}

@Controller注解用来把一个类定义为Controller。

@RequestMapping注解用来把web请求映射到相应的处理函数。

@Controller和@RequestMapping结合起来完成了Spring MVC请求的派发流程。

为什么两个简单的注解就能完成这么复杂的功能呢?

这又和<context:component-scan base-package="xx.xx.xx"/>的位置有什么关系呢?

我们开始从官网和源码两方面展开分析。

2.@RequestMapping流程分析

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

(1)注册
RequestMappingHandlerMapping bean 。

(2) 实例化
RequestMappingHandlerMapping bean。

(3)获取
RequestMappingHandlerMapping bean实例。

(4)接收requst请求。

(5)在
RequestMappingHandlerMapping实例中查找对应的handler。

(6)handler处理请求。

为什么是这6步,我们展开分析。

2.1 注册
RequestMappingHandlerMapping bean

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

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

在Spring系列(一)文中我们知道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 {
              //如果该元素属于自定义namespace走此逻辑 ,比如AOP,MVC等。
               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);
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

进入NamespaceHandlerSupport类的parse()方法。

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

Spring MVC中含有多种命名空间,此方法会根据元素所属命名空间得到相应解析类,其中< mvc:annotation-driven/>对应的是
AnnotationDrivenBeanDefinitionPars

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值