前言
IoC Container是Spring的基础,我们日常使用的Bean都是有容器来创建和维护的。包org.springframework.beans和org.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代码配置小结
- AnnotatedBeanDefinitionReader 注册几个PostProcessor 的BeanDefinition,包括ConfigurationClassPostProcessor
- ConfigurationClassPostProcessor会先实例化,并先通过postProcessBeanDefinitionRegistry方法,处理用户配置类
- ConfigurationClassParser 处理各种注解,包括对@Bean注解的方法,将对应的信息封装到MethodMetadata,并存储至ConfigurationClass上
- 构造ConfigurationClassBeanDefinitionReader,处理ConfigurationClass
- 根据MethodMetadata对应的配置信息,构造ConfigurationClassBeanDefinition
- 后续一些BeanPostProcessor会优先实例化
- 最后实例化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配置,开始的流程是一样的:
- AnnotatedBeanDefinitionReader 注册几个PostProcessor 的BeanDefinition,包括ConfigurationClassPostProcessor
- ConfigurationClassPostProcessor会先实例化,并先通过postProcessBeanDefinitionRegistry方法,处理用户配置类;ConfigurationClassParser 处理各种注解,这里就变成了主要是对@ComponentScans的处理
- 后续一些BeanPostProcessor会优先实例化
- 最后实例化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的