从源码解析Spring IoC(一):loadBeanDefinitions

448 阅读5分钟

导语

上一篇用大白话讲述了Spring IoC的大致流程,并且用了一个比喻来协助理解。但是都比较粗略,所以这次用追踪源码的方式来体会更多的细节。

正文

本文基于SpringMVC最基础的Xml配置方式来进行源码追踪,和其他方式在流程上是一样的。

SpringMvC在初始化DispatcherServlet的时候会创建一个WebApplicationContext对象并调用refresh()方法

refresh()
@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {

		prepareRefresh();
		
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		prepareBeanFactory(beanFactory);

		try {

			postProcessBeanFactory(beanFactory);

			invokeBeanFactoryPostProcessors(beanFactory);

			registerBeanPostProcessors(beanFactory);

			initMessageSource();

			initApplicationEventMulticaster();

			onRefresh();

			registerListeners();

			finishBeanFactoryInitialization(beanFactory);

			finishRefresh();

		}
		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}
			destroyBeans();

			cancelRefresh(ex);
			
			throw ex;
		}
		finally {
			resetCommonCaches();
		}
	}
}

refresh()方法是IoC的入口,其中有很多方法,我们只看比较重要的。先从obtainFreshBeanFactory()方法开始分析。

obtainFreshBeanFactory()

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	refreshBeanFactory();
	return getBeanFactory();
}

obtainFreshBeanFactory()是获取BeanFactory的方法,返回一个ConfigurableListableBeanFactory类型的DefaultListableBeanFactory对象。

其中refreshBeanFactory()方法是对BeanFactory的刷新,在存在BeanFactory的时候销毁,没有则创建,并加载BeanDefinitions。

refreshBeanFactory()

@Override
protected final void refreshBeanFactory() throws BeansException {
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());			
		customizeBeanFactory(beanFactory);
		loadBeanDefinitions(beanFactory);
		this.beanFactory = beanFactory;		
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

我们主要看loadBeanDefinitions()方法,这主要就是加载beanDefinition,并将beanDefinition放到beanDefinitionMap中。

从loadBeanDefinitions()方法一直追踪会来到XmlBeanDefinitionReader类的registerBeanDefinitions()方法。具有int类型的返回值,逻辑很简单:表示这次注册了多少beanDefinition,注册后的数量减去注册前的数量。

registerBeanDefinitions()

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	int countBefore = getRegistry().getBeanDefinitionCount();
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

注册数量前后夹着的这个方法就是执行注册的入口,顺着摸下去,就到了BeanDefinitionDocumentReader#doRegisterBeanDefinitions()方法,这个位置就是开始分析Xml元素节点的地方了,里面会出现递归调用的情况值得注意,其实就是传入的Xml节点元素有子元素的时候层层解析。在这个方法的最后有三个方法:

preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

其中preProcessXml()和postProcessXml()都是空方法,主要关注parseBeanDefinitions()

parseBeanDefinitions()

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	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;
				if (delegate.isDefaultNamespace(ele)) {
					parseDefaultElement(ele, delegate);
				}
				else {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}

这个方法里就是对当前元素节点进行判断,没有命名空间或者是否是默认的命名空间:“www.springframework.org/schema/bean…

其中,parseDefaultElement()方法是分配当前元素节点按照期望节点名字(“import”、“alias”、“bean”、“beans”)进行对应的处理。

parseDefaultElement()

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
		importBeanDefinitionResource(ele);
	}
	else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
		processAliasRegistration(ele);
	}
	else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}

我们看一下bean的对应处理,bean的处理方式比较简单。

首先把元素节点解析成BeanDefinitionHolder对象,然后再对此对象进行装饰,最后调用BeanDefinitionReaderUtils#registerBeanDefinition()方法进行注册。

除了用beanName和beanDefinition对应的key-value进行beanDefinitionMap注册外,还注册了别名(alias)注册。aliasMap是alias和beanName对应的key-value键值对。

还有一种,在Xml中可能会存在如下配置:

<context:component-scan base-package="org.test.demo" />

我的项目中的bean不一定都是在Xml进行显示配置的,加上了上面的配置,会根据包名进行扫描,扫描该包下和子包下的类。那么这些是如何处理的呢?我们下面接着看。

parseCustomElement()

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

这个方法是delegate.parseCustomElement()中调用的。我们可以看到,先获取了元素节点的namespaceUri,再根据对应的namespaceUri获取NamespaceHandler对象,最后执行NamespaceHandler的parse方法。

我们可以看到NamespaceHandler是一个接口,有很多的实现类

其实根据名字都能看出来对应配置对应的实现类。通过命名空间获取对应实现类的方法在resolve里,简单分析下。

  • 起初handlerMappings里是namespaceUri和NamespaceHandler类名为key-value形式
  • 首先从handlerMappings里根据namespaceUri获取类名
  • 然后初始化当前类
  • 执行当前类的init()方法
  • 在把初始化的类以namespaceUri和NamespaceHandler的类放入到handlerMappings中

我们就说一下context:component-scan包扫描的分析,其他的可以自己追下源码了解。 ContextNamespaceHandler类继承自NamespaceHandlerSupport,其中ContextNamespaceHandler中只有一个init()方法,那么执行parse()方法肯定是父类NamespaceHandlerSupport的。

ContextNamespaceHandler#init()

public void init() {
	registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
	registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
	registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
	registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
	registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
	registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
	registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}

可以看出来init()方法注册了一些Parser,用来处理context命名空间下对应的localName。例如:component-scan对应的Parser是ComponentScanBeanDefinitionParser。我们再看下parse()方法是如何处理的:

parse()方法中调用findParserForElement()方法寻找对应的BeanDefinitionParser:先获取当前元素的localName,然后再根据localName从已经注册的Parser中获取对应的Parser,最后再调用该Parser的parse()方法。

ComponentScanBeanDefinitionParser#parse()

public BeanDefinition parse(Element element, ParserContext parserContext) {
	String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
	basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
	String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

	// Actually scan for bean definitions and register them.
	ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
	Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
	registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

	return null;
}
  • 首先获取元素的base-package属性拿到扫描包的路径
  • 处理包路径并将包路径分割成数组,可以配置多个包扫描路径
  • 配置扫描器,得到一个ClassPathBeanDefinitionScanner扫描器
  • 执行扫描器的doScan()方法扫描指定包路径下的类
  • 注册组件registerComponents() 其中注册beanDefinition不在最后registerComponents()方法中,而是在doScan()方法。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

findCandidateComponents()方法是扫描beanDefinition的主要方法。之后再对beanDefinition进行简单处理。

  • 设置scope,默认是singleton
  • 进行后置处理
  • 处理公共的注解
  • 应用scope的代理模式,默认是ScopedProxyMode.NO
  • 注册beanDefinition

总结

至此,loadBeanDefinitions已经完成了。这也完成了实现Spring IoC的第一步,这就像上一篇说的媒婆已经掌握了周边大小村子里面适婚的善男信女。接下来就等着问问情况,看哪个需要什么类型的。

微信公众号搜索“闲时码头”,关注阅读更多文章