Spring源码学习篇1 - 加载配置文件

前言

IoC Container是Spring的基础,我们日常使用的Bean都是有容器来创建和维护的。包org.springframework.beansorg.springframework.context是容器的根本实现所在。

BeanFactory是容器的基础,它提供了配置框架和基础的功能。

ApplicationContext的是BeanFactory的子接口,可以说是容器的完整超集。它在BeanFactory的基础上增加了一些功能:

  • Easier integration with Spring’s AOP features.(更方便AOP集成)
  • Message resource handling (for use in internationalization). 消息资源处理(用于国际化)
  • Event publication. 事件发布
  • Application-layer specific contexts such as the WebApplicationContext for use in web applications. 特定的应用上下文,如用于网络应用的WebApplicationContext

容器通过读取配置元数据来获得关于要实例化、配置和组装哪些对象的指示。

配置元数据有三种存在方式:

  • XML-based configuration:XML配置文件是最早的配置方式,可能也是大家学习Spring最早接触的配置
  • Annotation-based configuration:基于注解配置,Spring2.5之后提供的支持,可以简化XML配置。
  • Java-based configuration:基于Java代码的配置,Spring3.0开始,可以不再使用XML配置。

Spring的配置包括至少一个以上的Bean定义,容器必须管理这些定义。基于XML的配置元数据将这些Bean配置为顶层<beans/>元素中的<bean/>元素。Java配置通常使用@Configuration类中的@Bean-annotated方法。

这里先讨论Spring是如何定位配置文件和Bean配置的,后续再看具体加载过程。

一、XML配置文件加载

XML配置文件的最基本配置结构

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

XmlBeanFactory 加载配置

XmlBeanFactory 已经被标记为Deprecated,但是不影响我们了解配置文件加载。

@Deprecated
public class XmlBeanFactory extends DefaultListableBeanFactory {

	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}

Resource

Resource的完整类名是org.springframework.core.io.Resource,它有一些不同的实现,ClassPathResource、FileSystemResource、 FileUrlResource、InputStreamResource、PathResource, ServletContextResource等等。

通过这些实现,可以使用不同的访问路径来获取配置资源。

1. ClassPathResource

从类名也可以看出,这是从class path中加载配置

//从class path的绝对路径加载配置文件
ClassPathResource resource = new ClassPathResource("config/services.xml");
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
TestA testa = xmlBeanFactory.getBean("testa", TestA.class);
Assertions.assertNotNull(testa);

ClassPathResource有个从相对路径加载的构造方法,这个比较实用,Spring源码有很多Unit Test都使用了这种方式,来组织各种不同的配置。

举个例子:
TestA的完整类名是beans.dependency.TestA。如果我们希望针对这个Bean做测试,那么配置文件路径可以是test\resources\beans\dependency\testa_bean_config.xml(Maven或者Gradle)。
使用绝对路径的话,需要使用"beans/dependency/testa_bean_config.xml", 但是相对类路径的话就比较简单了。

ClassPathResource resource = new ClassPathResource("testa_bean_config.xml", TestA.class);
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
TestA testa = xmlBeanFactory.getBean("testa", TestA.class);
Assertions.assertNotNull(testa);

这有助于组织关联不同模块的配置,不至于配置过多的时候混乱不堪。

2. PathResource

FileSystemResource从文件系统加载,这个也比较好理解,但是估计实际用的不多。这里换一个PathResource,自从Java提供了NIO的API之后,文件相关操作方便了很多。

PathResource resource = new PathResource(Paths.get("e:", "test", "testa_bean_config.xml"));
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
TestA testa = xmlBeanFactory.getBean("testa", TestA.class);
Assertions.assertNotNull(testa);

其他Resource加载都是大同小异,不一一列举了。总之,Spring是通过不同的Resource来指向不同配置文件。

XmlBeanDefinitionReader

XmlBeanFactory已经被废弃了,可以直接使用DefaultListableBeanFactory和XmlBeanDefinitionReader。
事实上,XmlBeanFactory是DefaultListableBeanFactory的子类,XmlBeanFactory本身就使用了XmlBeanDefinitionReader。可以像下面这样使用XmlBeanDefinitionReader加载配置到指定的beanFactory。

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("testa_bean_config.xml", TestA.class));

TestA testa = beanFactory.getBean("testa", TestA.class);
assertNotNull(testa);

XmlBeanDefinitionReader 构造方法接收的是BeanDefinitionRegistry,它规定了BeanDefinition的注册和获取行为,而BeanDefinition就是Spring对Bean配置的抽象。

接下来看下XmlBeanDefinitionReader 具体如何加载配置的:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	Assert.notNull(encodedResource, "EncodedResource must not be null");
	if (logger.isTraceEnabled()) {
		logger.trace("Loading XML bean definitions from " + encodedResource);
	}

	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}

	try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
		InputSource inputSource = new InputSource(inputStream);
		if (encodedResource.getEncoding() != null) {
			inputSource.setEncoding(encodedResource.getEncoding());
		}
		return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
	}

可以看到,从Resource里面获取到InputStream之后构造了 InputSource,这是Java里面rt.jar提供用来解析xml的。之后,再取得Document ,之后遍历所有<bean/>元素得到BeanDefinition。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		Document doc = doLoadDocument(inputSource, resource);
		int count = registerBeanDefinitions(doc, resource);
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + count + " bean definitions from " + resource);
		}
		return count;
	}

ClassPathXmlApplicationContext

ApplicationContext 功能更加强大,也更加方便。它内部也是通过XmlBeanDefinitionReader来加载XML配置文件。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("testa_bean_config.xml", TestA.class);
TestA testa = applicationContext.getBean("testa", TestA.class);
assertNotNull(testa);

小结XML配置文件加载

现在暂时值考虑单个文件的加载,基本流程就是

  • 加载XML配置文件为Resource
  • XmlBeanDefinitionReader 解析XML document;
  • 将<bean/>元素注册为BeanDefinition

二、基于注解配置的加载

Annotation-based configuration可以通过 @Autowired、@Qualifier、@Resource、@Inject等等各种注解来配置Bean。而且注解的Bean可以跟XML进行混合使用, 首先需要启动注解配置:

@Inject是JSR-330 standard annotations,但是它需要依赖javax.inject

@Autowired适用于字段、构造函数和多参数方法,允许在参数级别上通过限定符注释来缩小范围。相比之下,@Resource只支持字段和只有一个参数的bean属性设置器方法。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

先来看下context:annotation-config是如何工作的

<context:annotation-config/>

由于还是在xml里面配置的,读取整个配置文件以得到document的过程是一样的,后边会遍历document的子元素,对每个子元素进行处理:

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);
	}
}

DefaultNamespace指的是http://www.springframework.org/schema/beans,根元素<beans/>的namespace是默认的,但是context:annotation-config元素的namespace是http://www.springframework.org/schema/context,所以会执行delegate.parseCustomElement处理context:annotation-config这个配置元素。

public BeanDefinition parseCustomElement(Element ele) {
	return parseCustomElement(ele, null);
}

@Nullable
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));
}

可以看到非默认namespace的处理,需要根据它自己的namespace找到对应的NamespaceHandler ,然后使用它来处理对应的子元素<context:annotation-config/>。注意,这个功能可以被用来扩展Spring。

那么http://www.springframework.org/schema/context可以找到什么样的NamespaceHandler 呢?

获取<context:annotation-config/>NamespaceHandler

从上面的代码可以看出,NamespaceHandler 来自readerContext.getNamespaceHandlerResolver().resolve(namespaceUri), 从XmlReaderContext 的构造过程可以看出,Spring默认使用DefaultNamespaceHandlerResolver来处理namespaceUri。

public XmlReaderContext createReaderContext(Resource resource) {
	return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
			this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
	if (this.namespaceHandlerResolver == null) {
		this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
	}
	return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
	ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
	return new DefaultNamespaceHandlerResolver(cl);
}

DefaultNamespaceHandlerResolver 默认从META-INF/spring.handlers配置文件读取namespaceUri跟NamespaceHandler的对应关系。而且META-INF/spring.handlers有很多个:

  • sping-bean模块下的META-INF/spring.handlers
    http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
    http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
    http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
    
    
  • spring-context模块下的META-INF/spring.handlers
    http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
    http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
    http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
    http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
    http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
    
    
  • spring-aop模块下的META-INF/spring.handlers
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

后续学习SpringBoot的时候,也会看到不同的spring.handlers文件,每个spring.handlers都是作为Properties文件读取的。

从spring-context的spring.handlers中可以知道ContextNamespaceHandler用来处理 <context:annotation-config/>元素。

ContextNamespaceHandler

找到NamespaceHandler对应的类后,会通过反射来创建对象,并调用init方法。

NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);

ContextNamespaceHandler的init方法也很简单,我们可以看到annotation-config的身影:

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());

现在回到<context:annotation-config/>的解析处,即NamespaceHandler.parse方法, 首先是根据元素的localName获取对应的BeanDefinitionParser。

<context:annotation-config/>的localName是annotation-config,而通过上面ContextNamespaceHandler.init方法,可以找到对应的BeanDefinitionParser ,即AnnotationConfigBeanDefinitionParser。

public BeanDefinition parse(Element element, ParserContext parserContext) {
	BeanDefinitionParser parser = findParserForElement(element, parserContext);
	return (parser != null ? parser.parse(element, parserContext) : null);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
	String localName = parserContext.getDelegate().getLocalName(element);
	BeanDefinitionParser parser = this.parsers.get(localName);
	if (parser == null) {
		parserContext.getReaderContext().fatal(
				"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
	}
	return parser;
}

现在可以了解<context:annotation-config/>主要解析处理逻辑都在AnnotationConfigBeanDefinitionParser中

<context:annotation-config/>的AnnotationConfigBeanDefinitionParser

AnnotationConfigBeanDefinitionParser本身并不会返回一个BeanDefinition,它的返回值是null。但是,它会注册以下几个BeanDefinition:

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • EventListenerMethodProcessor
  • DefaultEventListenerFactory

这些注册的BeanDefinition都会在后面优先初始化为单例Bean,那么后续在创建带有注解的Bean时,BeanPostProcessor会处理注解配置的依赖。这里暂时不讨论具体的加载过程,到目前为止,各个注解都是可以定位并处理的。

注解配置小结

  • 读取配置文件XML 为Resource
  • 解析XML document,依次处理子元素
  • 遇到<context:annotation-config/>,找到context命名空间处理器ContextNamespaceHandler
  • 找到annotation-config对应的解析器AnnotationConfigBeanDefinitionParser
  • AnnotationConfigBeanDefinitionParser 注册Spring基础设施几个PostProcessor对应BeanDefinition
  • 解析<bean/>元素,注册为BeanDefinition
  • 创建几个对应的BeanPostProcessor的实例Bean,后续这些实例会处理用户自定义的Bean的注解

XML配置自动扫描

只是使用<context:annotation-config/>的话,虽然可以使用@Autowired、@Resource等注解减少XML的配置内容,但是<bean/> 元素还是不能缺少的。

Spring还提供了自动扫描功能来注册Bean,它有两种配置方式:

  • Java-based配置,在@Configuration配置类中,添加@ComponentScan(“org.example”)
  • XML配置,<context:component-scan base-package=“org.example”/>

配置了自动扫描之后,就可以使用@Component、@Service等注解来注册Bean。

这里先说下XML配置方式,后面在基于Java代码的配置中再来了解@ComponentScan。

<context:component-scan/>

base-package 是必须的属性,允许配置多个包

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

同 <context:annotation-config/>,可以找到component-scan对应的解析器是ComponentScanBeanDefinitionParser

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;
}

ComponentScanBeanDefinitionParser中构造了ClassPathBeanDefinitionScanner ,获取@Component等注解的BeanDefinition的主要工作是由ClassPathBeanDefinitionScanner 来完成的。

从ClassPathBeanDefinitionScanner 的构造过程可以看到,<context:component-scan/>还有一些额外的属性,比如use-default-filters、resource-pattern、name-generator、scope-resolver、scoped-proxy等;此外还有几个子元素include-filter、exclude-filter。当前先只关注如何扫描到Bean并注册的。

ClassPathBeanDefinitionScanner .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,可以看到扫描文件或获取class path下对应的URI等等好多个方式,最终构建不同的Resource。比如,我在跑unit test的时候,最终构造出来的是FileSystemResource,每个类都是一个Resource。

Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
for (File file : matchingFiles) {
	result.add(new FileSystemResource(file));
}
return result;

其他还有VfsResource、UrlResource等。这个跟将XML配置文件读取为Resource有些类似。

读取到Resource之后,经过处理,构造MetadataReader(XML配置文件对应的是XmlBeanDefinitionReader)。MetadataReader里面通过ClassVisitor封装了通过Resource获取类的各种信息,比如类名、超类、接口、属性等等信息。

然后构建ScannedGenericBeanDefinition,它是BeanDefinition的一种具体实现。

MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
		if (isCandidateComponent(metadataReader)) {
			ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
			sbd.setSource(resource);
			if (isCandidateComponent(sbd)) {
				if (debugEnabled) {
					logger.debug("Identified candidate component class: " + resource);
				}
				candidates.add(sbd);
			}

到此,可以大致了解XML配置自动扫描是如何找到Bean配置的。

XML配置自动扫描小结

  • 读取配置文件XML 为Resource
  • 解析XML document,依次处理子元素
  • 遇到<context:component-scan/>,找到context命名空间处理器ContextNamespaceHandler
  • 找到component-scan对应的解析器ComponentScanBeanDefinitionParser
  • ComponentScanBeanDefinitionParser构造ClassPathBeanDefinitionScanner,扫描指定的包名下的资源为Resource
  • 然后读取类相关的信息,满足条件的注册为ScannedGenericBeanDefinition
  • 同annotation-config,也会注册部分PostProcessor 的BeanDefinition
  • 创建几个对应的PostProcessor的实例Bean,后续这些实例会处理用户自定的Bean的注解

三、Java-based configuration Java代码配置

通过Java代码配置,可以完全脱离XML配置Bean。Spring Java代码配置的核心是@Configuration-annotated类和@Bean-annotated方法。@Configuration注解的类本身会被注册为一个 bean definition,其下所有的@Bean注解的方法也都会被注册为bean definition。

@Configuration
public class AppConfig {
    @Bean
    public TestA testA() {
        return new TestA();
    }
}

AnnotationConfigApplicationContext可以用来维护配置的Bean,用法如下:

 ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
 TestA testa = ctx.getBean(TestA.class);
 assertNotNull(testa);

它不仅可接受@Configuration注解的类,也接收@Component、@Service等注解类:

ApplicationContext ctx = new AnnotationConfigApplicationContext(TestA.class, TestB.class);
TestA testa = ctx.getBean(TestA.class);
assertNotNull(testa);

那接下来,来了解Java代码配置是如何获取到的BeanDefinition的。

AnnotationConfigApplicationContext的默认构造方法如下:

public AnnotationConfigApplicationContext() {
	StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
	this.reader = new AnnotatedBeanDefinitionReader(this);
	createAnnotatedBeanDefReader.end();
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	this();
	register(componentClasses);
	refresh();
}

可以看出有两个属性AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner。

先来看下AnnotatedBeanDefinitionReader。

AnnotatedBeanDefinitionReader

AnnotationConfigApplicationContext构造方法里面的创建了AnnotatedBeanDefinitionReader

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

它内部注册了AnnotationConfigProcessors的BeanDefinition,包括:

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • EventListenerMethodProcessor
  • DefaultEventListenerFactory

这些Processors注册好后,AnnotationConfigApplicationContext调用register来注册用户的配置类,将注册为AnnotatedGenericBeanDefinition,后面ConfigurationClassPostProcessor会在正式初始化用户Bean之前,处理@Configuration配置类下面的@Bean注解的方法。

ConfigurationClassPostProcessor

ConfigurationClassPostProcessor 实现了BeanDefinitionRegistryPostProcessor接口,会优先创建对应的单例Bean。

BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,会在用户配置的BeanDefinition正式创建Bean之前,对现有的BeanDefinition进行一次处理。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	int registryId = System.identityHashCode(registry);
	if (this.registriesPostProcessed.contains(registryId)) {
		throw new IllegalStateException(
				"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
	}
	if (this.factoriesPostProcessed.contains(registryId)) {
		throw new IllegalStateException(
				"postProcessBeanFactory already called on this post-processor against " + registry);
	}
	this.registriesPostProcessed.add(registryId);

	processConfigBeanDefinitions(registry);
}

processConfigBeanDefinitions对用户配置Class做了一定的处理之后,就开始解析@Configuration的配置信息。解析使用ConfigurationClassParser 来得到ConfigurationClass。然后再

// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
		this.metadataReaderFactory, this.problemReporter, this.environment,
		this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
	StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
	parser.parse(candidates);
	parser.validate();

	Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
	configClasses.removeAll(alreadyParsed);

	// Read the model and create bean definitions based on its content
	if (this.reader == null) {
		this.reader = new ConfigurationClassBeanDefinitionReader(
				registry, this.sourceExtractor, this.resourceLoader, this.environment,
				this.importBeanNameGenerator, parser.getImportRegistry());
	}
	this.reader.loadBeanDefinitions(configClasses);
	alreadyParsed.addAll(configClasses);
	...
		candidateNames = newCandidateNames;
	}
}
while (!candidates.isEmpty());

看下ConfigurationClassParser怎么处理ConfigurationClassParser,跟踪ConfigurationClassParser的parse方法,可以找到对配置类上各种注解的处理,比如我们希望看到的@Bean注解的处理。

// ConfigurationClassParser#doProcessConfigurationClass
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
	// Recursively process any member (nested) classes first
	processMemberClasses(configClass, sourceClass, filter);
}

... 这里还有很多注解的处理,比如@ComponentScans@ComponentScan等等

// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
	configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

通过调用retrieveBeanMethodMetadata方法,抽取到所有配置@Bean的方法信息添加到ConfigurationClass属性上,后续BeanDefinitionRegistryPostProcessor的实例ConfigurationClassPostProcessor会构造ConfigurationClassBeanDefinitionReader处理得到的ConfigurationClass,并注册BeanDefinition:

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	for (ConfigurationClass configClass : configurationModel) {
		loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
	}
}

/**
 * Read a particular {@link ConfigurationClass}, registering bean definitions
 * for the class itself and all of its {@link Bean} methods.
 */
private void loadBeanDefinitionsForConfigurationClass(
		ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

	if (trackedConditionEvaluator.shouldSkip(configClass)) {
		String beanName = configClass.getBeanName();
		if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
			this.registry.removeBeanDefinition(beanName);
		}
		this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
		return;
	}

	if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}
	// 这里会处理@Bean注册方法
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}

	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

上面可以看到,loadBeanDefinitionsForBeanMethod会依次处理之前得到@Bean注解的方法信息,并注册为ConfigurationClassBeanDefinition。

Java代码配置小结

  1. AnnotatedBeanDefinitionReader 注册几个PostProcessor 的BeanDefinition,包括ConfigurationClassPostProcessor
  2. ConfigurationClassPostProcessor会先实例化,并先通过postProcessBeanDefinitionRegistry方法,处理用户配置类
    • ConfigurationClassParser 处理各种注解,包括对@Bean注解的方法,将对应的信息封装到MethodMetadata,并存储至ConfigurationClass上
    • 构造ConfigurationClassBeanDefinitionReader,处理ConfigurationClass
    • 根据MethodMetadata对应的配置信息,构造ConfigurationClassBeanDefinition
  3. 后续一些BeanPostProcessor会优先实例化
  4. 最后实例化ConfigurationClassBeanDefinition为Bean时,BeanPostProcessor会继续处理部分像@Autoware、@Resource等等注解

Java 代码配置 自动扫描

AnnotationConfigApplicationContext的scan方法

AnnotationConfigApplicationContext在初始化的时候,构造两个属性,一个是AnnotatedBeanDefinitionReader,另一个是ClassPathBeanDefinitionScanner。我们上面至介绍了AnnotatedBeanDefinitionReader,现在来看下ClassPathBeanDefinitionScanner:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("beans.dependency");
ctx.refresh();

TestA testa = ctx.getBean(TestA.class);
assertNotNull(testa);

scan方法很简单,只是调用ClassPathBeanDefinitionScanner的scan。所以主要工作全部在ClassPathBeanDefinitionScanner中。

前面在XML配置自动扫描<context:component-scan/>说过,ClassPathBeanDefinitionScanner注册BeanDefinition的逻辑,不再赘述。

@ComponentScan

使用ComponentScan之后,配置变得非常简单。注册配置就可以依靠@Service、@Component等注解。

@Configuration
@ComponentScan("beans.dependency")
public class AppConfig {
}

获取Bean不变

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TestA testa = ctx.getBean(TestA.class);
assertNotNull(testa);

注册Bean的逻辑同前面的Java-based配置,开始的流程是一样的:

  1. AnnotatedBeanDefinitionReader 注册几个PostProcessor 的BeanDefinition,包括ConfigurationClassPostProcessor
  2. ConfigurationClassPostProcessor会先实例化,并先通过postProcessBeanDefinitionRegistry方法,处理用户配置类;ConfigurationClassParser 处理各种注解,这里就变成了主要是对@ComponentScans的处理
  3. 后续一些BeanPostProcessor会优先实例化
  4. 最后实例化ConfigurationClassBeanDefinition为Bean时,BeanPostProcessor会继续处理部分像@Autoware、@Resource等等注解

那么@ComponentScans是如何处理的呢?找到ConfigurationClassParser ,可以到对应的处理逻辑:

Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
		sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
		!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
	for (AnnotationAttributes componentScan : componentScans) {
		// The config class is annotated with @ComponentScan -> perform the scan immediately
		Set<BeanDefinitionHolder> scannedBeanDefinitions =
				this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
		// Check the set of scanned definitions for any further config classes and parse recursively if needed
		for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
			BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
			if (bdCand == null) {
				bdCand = holder.getBeanDefinition();
			}
			if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
				parse(bdCand.getBeanClassName(), holder.getBeanName());
			}
		}
	}
}

其中,componentScanParser是ComponentScanAnnotationParser的实例,也就是主要逻辑在ComponentScanAnnotationParser.parse里面:

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
		componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
... 省略一些scanner的处理
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
	String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
	Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
	basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
	basePackages.add(ClassUtils.getPackageName(declaringClass));
}

scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
	@Override
	protected boolean matchClassName(String className) {
		return declaringClass.equals(className);
	}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));

可以看到,依然是ClassPathBeanDefinitionScanner 进行扫描注册的,这是已经在<context:component-scan/>中介绍过了。

四、总结

  • XML配置文件一般会先将对应的配置文件加载为Resrouce
  • Spring对Bean配置的抽象是BeanDefinition
  • Spring会在对应的上下文中注册对应的BeanDefinitionRegistryPostProcessor、BeanPostProcessor
  • BeanDefinitionRegistryPostProcessor会在取得用户自定义的BeanDefinition之后,优先实例化,并对用户自定义BeanDefinition处理一遍;这里可能会由于用户自定义一些注解,比如@Bean,再构造一些对应的BeanDefinition出来
  • BeanPostProcessor 会在用户自定义BeanDefinition之前实例化,并添加到上下文中
  • 用户自定义BeanDefinition在实例话的时候,实例化的BeanPostProcessor 会进行一次额外处理
  • 不管是那种方式配置扫描,最终都是ClassPathBeanDefinitionScanner 来进行注册BeanDefinition的
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值