Spring MVC初始化过程-源码解析

背景

SpringMVC项目配置中,我使用了xml的方式来启动SptingMVC对声明式注解的支持,其中使用如下标签进行启动

<!--支持注解方式声明@Controller-->
 <mvc:annotation-driven />

因为解析配置文件过程中,需要对其中的标签进行解析,而这里使用的是自定义标签,则需要有对应相关的处理器和解析器

标签解析过程 <mvc:annotation-driven />

解析自定义标签

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFeS7HBV-1611543946324)(BA182415C3BC4DD089A06E2C5AD5A516)]

MvcNamespaceHandler

因为标签前缀是mvc:,这里需要找出nameSpaceUri为xmlns:mvc="http://www.springframework.org/schema/mvc"的处理器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ina97Z3P-1611543946325)(A90645BE50874FEEAA32825CE9EBF8D6)]

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项目的在如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AtUP5vSl-1611543946327)(6FB9557B6D774DF88668659B5003C33A)]

MvcNamespaceHandler会查找对应的BeanDefinitionParser解析mvc:annotation-driven标签,从图中可以看到找出了AnnotationDrivenBeanDefinitionParser
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gc7cLspe-1611543946328)(29C0DFBDFF7A498B9DC60634722A928F)]

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 找不到处理器
      • 等等
  • 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以及渲染视图的功能
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxKZBfMy-1611543946330)(16AC58A159B9459D8D062F5DED2A019A)]
初始化过程

在HttpServletBean的init()打个断点,可以发现初始化是在第一个HTTP请求的时候才初始化的。可以看到又执行了initServletBean(),但是是一个空的方法,留给子类继承实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mu7wh7Aw-1611543946331)(4FE5A2168D954D4297F722A66BEB44F2)]

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 实现了初始化逻辑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EIgbnZvm-1611543946332)(FC7E759C6FCA4431AF5B2AB6C3DE3317)]

在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() 执行相关组件初始化逻辑

从下图中可以看到,在DispatcherServletonRefresh() 实现了相关组件的初始化。直观的看到有我们熟悉的视图解析器initViewResolvers()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HAkZ8EZM-1611543946333)(1B7469ACA4754B96A3E49A60FAD77167)]

以初始化视图解析器initResolvers()为例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHu0YS1j-1611543946333)(E2BD586AB2624A83A6D17F4D617FFB22)]

如果在子容器中没有找到视图解析器ViewResolver,则会创建默认的视图解析器,那么Spring如何获取默认的视图解析器呢,看下图,可以看到保存在Spring MVC项目的一个文件中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkQHHcub-1611543946334)(18CBDE3CF9F540DABEED4ABC83F4F36E)]

小结

我们可以看到在首先会在FrameworkServlet创建一个当前Servlet的Spring容器,并传入Root容器,然后在DispatcherServlet中初始化MVC的相关组件,如果在子容器中没有找到相关组件,则会采用默认的策略,创建默认的组件。

疑问: 为什么DispatcherServlet需要独立的子容器?

在这里引用Spring官方的一张图,就可以非常直观的理解其中的奥妙。从下图可以看到Servlet有自己独立的Spring容器,包含Controller,ViewResolver等组件,并且共享父容器的功能(比如 查询数据的Bean),这是官方推荐的配置方法,也是我在SpringMVC项目配置中使用了两个Spring配置文件的原因。这里做的好处是: 不同的Servlet有独立处理请求的能力,互不影响。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnTAFkMk-1611543946334)(C60179747EF741F590980FD96BB20B59)]

总结

本文采用了xml的方式来启动Spring MVC 主要包含两个重要的阶段:

  • 解析@Controller中的@RequestMapping
  • 初始化DispatcherServlet

思考

  • 如果不采用xml的方式,如何启动Spring MVC功能
  • 理解其他方式的启动原理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值