前言
上篇给大家介绍了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