背景
在SpringMVC项目配置中,我使用了xml的方式来启动SptingMVC对声明式注解的支持,其中使用如下标签进行启动
<!--支持注解方式声明@Controller-->
<mvc:annotation-driven />
因为解析配置文件过程中,需要对其中的标签进行解析,而这里使用的是自定义标签,则需要有对应相关的处理器和解析器
标签解析过程 <mvc:annotation-driven />
解析自定义标签
MvcNamespaceHandler
因为标签前缀是mvc:,这里需要找出nameSpaceUri为xmlns:mvc="http://www.springframework.org/schema/mvc"的处理器
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
//添加解析<mvc:annotation-driven/>的Beandefinition的解析器
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
}
}
其实对应的命令空间的解析器对应关系是配置在每个Spring项目下面的,比如mvc项目的在如下
MvcNamespaceHandler会查找对应的BeanDefinitionParser解析mvc:annotation-driven标签,从图中可以看到找出了AnnotationDrivenBeanDefinitionParser
AnnotationDrivenBeanDefinitionParser
作用
- 将一些实现MVC功能的BeanDefinition注册到Spring容器中
- 容器在启动的时候,会加载单例的BeanDefinition到容器中
注册的Bean
具体注册了哪些BeanDefinition这里就不贴代码了,如下列出比较重要的Bean
- RequestMappingHandlerMapping
- 用于解析@controller中的@RequestMapping注解,最终解析为HandleMethod
- RequestMappingHandlerAdapter
- 用于执行HandleMethod
- ConfigurableWebBindingInitializer
- 用于初始化WebDataBinder,用于数据绑定
- DefaultHandlerExceptionResolver
- 用于处理Spting MVC中默认的异常
- HttpRequestMethodNotSupportedException 不支持某种HTTP METHOD
- 如果@RequestMapping中定义为GET,使用POST请求就会抛出该异常,并进入DefaultHandlerExceptionResolver中处理
- NoHandlerFoundException 找不到处理器
- 等等
- HttpRequestMethodNotSupportedException 不支持某种HTTP METHOD
- 用于处理Spting MVC中默认的异常
- ExceptionHandlerExceptionResolver
- 用于将异常传递到@ExceptionHandler定义的方法中处理
- 将替代默认的DefaultHandlerExceptionResolver处理异常
RequestMappingHandlerMapping
用于解析@RequestMapping,解析成一个个的HandleMethod,这点很重要,大家要记住。在DispatcherServlet中通过RequestMappingHandlerAdapter根据URL找出匹配的HandleMethod,并通过反射执行调用。
工作原理
- 继承了InitializingBean接口,在afterPropertiesSet()中执行初始化方法
@Override
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();
}
- 继续看到父类AbstractRequestMappingHandlerMapping,可以看到在该类中执行真正的初始化逻辑
@Override
public void afterPropertiesSet() {
//这里就是初始化HandleMethod
initHandlerMethods();
}
protected void initHandlerMethods() {
//获取所有候选Bean,其实就是遍历容器中所有的单例Bean
for (String beanName : getCandidateBeanNames()) {
//beanName不为scopedTarget.开头
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//注册HandleMethod
processCandidateBean(beanName);
}
}
//打印一下初始化了多少个HandlerMethod
handlerMethodsInitialized(getHandlerMethods());
}
解析并注册HandleMethod
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
//获取beanName的类型
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
//判断类上是否定义了@Controller或@RequestMapping
if (beanType != null && isHandler(beanType)) {
//查询@RequstMapping的方法并注册在一个Map中
detectHandlerMethods(beanName);
}
}
用于判断当前Bean是否为Controller
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
DispatcherServlet 初始化过程
我们都知道HTTP请求最终会被Servelet处理,而且Spring MVC使用DispatcherServlet作为中央处理器对所有的请求进行处理,并路由到对应的HandleMethod处理
继承关系
- GenericServlet实现了Servlet一些通用的方法
- HttpServlet实现了HTTP的功能 doGet() dotPost()等
- HttpServletBean提供了获取环境变量Environment的能力
- FrameworkServlet提供了Spring容器ApplicationConext的能力
- 最终DispatcherServlet实现执行HandlerMehod以及渲染视图的功能
初始化过程
在HttpServletBean的init()打个断点,可以发现初始化是在第一个HTTP请求的时候才初始化的。可以看到又执行了initServletBean(),但是是一个空的方法,留给子类继承实现。
HttpServletBean initServletBean()
没有实现逻辑,留给子流继承,
/**
* Subclasses may override this to perform custom initialization.
* All bean properties of this servlet will have been set before this
* method is invoked.
* <p>This default implementation is empty.
* @throws ServletException if subclass initialization fails
*/
protected void initServletBean() throws ServletException {
}
继承往下看 FrameworkServlet 实现了初始化逻辑
在initWebApplicationContext()中创建了子容器,并执行onRefresh()初始化
protected WebApplicationContext initWebApplicationContext() {
//获取Root Spring容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//判断当前是否已经初始化过子容器
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//从ServletContext容器的Attribute中查询是否存在子容器
wac = findWebApplicationContext();
}
//如果为空 代表没初始化过子容器
if (wac == null) {
//创建新的子容器 传入父容器
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
//空方法,在DispatcherServlet实现了
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
//将子容器放入到ServletConext中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
DispatcherServlet onRefresh() 执行相关组件初始化逻辑
从下图中可以看到,在DispatcherServlet的onRefresh() 实现了相关组件的初始化。直观的看到有我们熟悉的视图解析器initViewResolvers()
以初始化视图解析器initResolvers()为例
如果在子容器中没有找到视图解析器ViewResolver,则会创建默认的视图解析器,那么Spring如何获取默认的视图解析器呢,看下图,可以看到保存在Spring MVC项目的一个文件中
小结
我们可以看到在首先会在FrameworkServlet创建一个当前Servlet的Spring容器,并传入Root容器,然后在DispatcherServlet中初始化MVC的相关组件,如果在子容器中没有找到相关组件,则会采用默认的策略,创建默认的组件。
疑问: 为什么DispatcherServlet需要独立的子容器?
在这里引用Spring官方的一张图,就可以非常直观的理解其中的奥妙。从下图可以看到Servlet有自己独立的Spring容器,包含Controller,ViewResolver等组件,并且共享父容器的功能(比如 查询数据的Bean),这是官方推荐的配置方法,也是我在SpringMVC项目配置中使用了两个Spring配置文件的原因。这里做的好处是: 不同的Servlet有独立处理请求的能力,互不影响。
总结
本文采用了xml的方式来启动Spring MVC 主要包含两个重要的阶段:
- 解析@Controller中的@RequestMapping
- 初始化DispatcherServlet
思考
- 如果不采用xml的方式,如何启动Spring MVC功能
- 理解其他方式的启动原理