Spring MVC 源码-初始化阶段

我们首先找到DispatcherServlet 这个类,必然是寻找init()方法。然后,我们发现其init方法其实在父类HttpServletBean 中,其源码如下:

@Override
public final void init() throws ServletException {
	if (logger.isDebugEnabled()) {
		logger.debug("Initializing servlet '" + getServletName() + "'");
	}

	// Set bean properties from init parameters.
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			//定位资源
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			//加载配置信息
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}

	// Let subclasses do whatever initialization they like.
	initServletBean();

	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}

我们看到在这段代码中, 又调用了一个重要的initServletBean() 方法。进入initServletBean()方法看到以下源码:

protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {

		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	}
	catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}
	catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (this.logger.isInfoEnabled()) {
		long elapsedTime = System.currentTimeMillis() - startTime;
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
				elapsedTime + " ms");
	}
}

这段代码中最主要的逻辑就是初始化IOC 容器,最终会调用refresh()方法,前面的章节中对IOC 容器的初始化细节我们已经详细掌握,在此我们不再赘述。我们看到上面的代码中,IOC 容器初始化之后,最后有调用了onRefresh()方法。这个方法最终是在DisptcherServlet 中实现,来看源码:

@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}
/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
//初始化策略
protected void initStrategies(ApplicationContext context) {
	//多文件上传的组件
	initMultipartResolver(context);
	//初始化本地语言环境
	initLocaleResolver(context);
	//初始化模板处理器
	initThemeResolver(context);
	//handlerMapping
	initHandlerMappings(context);
	//初始化参数适配器
	initHandlerAdapters(context);
	//初始化异常拦截器
	initHandlerExceptionResolvers(context);
	//初始化视图预处理器
	initRequestToViewNameTranslator(context);
	//初始化视图转换器
	initViewResolvers(context);
	//
	initFlashMapManager(context);
}

到这一步就完成了Spring MVC 的九大组件的初始化。接下来,我们来看url 和Controller的关系是如何建立的呢? HandlerMapping 的子类AbstractDetectingUrlHandlerMapping 实现了initApplicationContext()方法,所以我们直接看子类中的初始化容器方法。

@Override
public void initApplicationContext() throws ApplicationContextException {
	super.initApplicationContext();
	detectHandlers();
}
/**
 * 建立当前ApplicationContext中的所有controller和url的对应关系
 */
protected void detectHandlers() throws BeansException {
	ApplicationContext applicationContext = obtainApplicationContext();
	if (logger.isDebugEnabled()) {
		logger.debug("Looking for URL mappings in application context: " + applicationContext);
	}
	// 获取ApplicationContext容器中所有bean的Name
	String[] beanNames = (this.detectHandlersInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
			applicationContext.getBeanNamesForType(Object.class));

	// Take any bean name that we can determine URLs for.
	// 遍历beanNames,并找到这些bean对应的url
	for (String beanName : beanNames) {
		// 找bean上的所有url(controller上的url+方法上的url),该方法由对应的子类实现
		String[] urls = determineUrlsForHandler(beanName);
		if (!ObjectUtils.isEmpty(urls)) {
			// URL paths found: Let's consider it a handler.
			// 保存urls和beanName的对应关系,put it to Map<urls,beanName>,该方法在父类AbstractUrlHandlerMapping中实现
			registerHandler(urls, beanName);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
			}
		}
	}
}

determineUrlsForHandler(String beanName)方法的作用是获取每个Controller 中的url,不同的子类有不同的实现,这是一个典型的模板设计模式。因为开发中我们用的最多的就是用注解来配置Controller 中的url , BeanNameUrlHandlerMapping 是
AbstractDetectingUrlHandlerMapping 的子类,处理注解形式的url 映射.所以我们这里以BeanNameUrlHandlerMapping 来进行分析。我们看BeanNameUrlHandlerMapping 是如何查beanName 上所有映射的url。

@Override
protected String[] determineUrlsForHandler(String beanName) {
	List<String> urls = new ArrayList<>();
	if (beanName.startsWith("/")) {
		urls.add(beanName);
	}
	String[] aliases = obtainApplicationContext().getAliases(beanName);
	for (String alias : aliases) {
		if (alias.startsWith("/")) {
			urls.add(alias);
		}
	}
	return StringUtils.toStringArray(urls);
}

到这里HandlerMapping 组件就已经建立所有url 和Controller 的对应关系。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值