前言
该篇是基于前面知识点进行解析,但涉及到前面的内容不多,不了解前面知识的也不用慌。
知识回顾
前面介绍spring的启动过程,它的启动就是一个准备的过程。
spring启动它做了一些操作:
- 实例化beanFactory
- 实例化class读取器reader,bean扫描器scanner
- 加载我们的主配置类为BeanDefinition
- 初始化beanFactory;对beanFactory进行设置,包括后置处理器
- 执行自定义后置处理器
- 执行postProcessBeanDefinitionRegistry方法,按优先级,顺序执行,最后执行剩下没有执行的(这篇需要深入的)
- 执行postProcessBeanFactory方法
- beanFactory的后置处理器
- 初始化国际化资源对象messagesource
- 初始化事件发布器
- 注册监听器
- 实例化非懒加载bean,这里和之前的bean的生命周期接上了。
- 生命周期回调接口执行.onfresh
- 启动完成发布事件;
配置类的加载
先来看一个例子
代码语言:javascript复制@Component
public class CService {
@Bean
public FBean fBean() {
return new FBean();
}
}
一般我们都是用@Configuration
来进行配置的,但是如上代码中的FBean,它也会被注册。这一个过程是怎么操作的我比较好奇,下面来慢慢探究。
spring启动时,在refresh方法里的一个方法:AbstractApplicationContext#invokeBeanFactoryPostProcessors
跟进之后在:PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)
方法里,第一个后置处理器执行的是自定义后置处理器,所以这个忽略,直接看第二段;
之前的文章中,我们了解了bean的生命周期过程,那么启动spring容器,最重要的步骤是什么? 准备容器,生成单例bean。怎样才能生成bean,首先得有beanDefinition,为什么要有beanDefinition,可以在看看前面的《bean的生命周期》。而生成bean要在所有的class都扫描完,才会进行实例化,所以配置类扫描,是在扫描beanDefinition的最开始,所以,需要一个入口开启beanDefinition的扫描,在spring中有一个类
BeanDefinitionRegistryPostProcessor
,这个类看名字就是beanDefinition注册类后置处理器,那么要注册beanDefinition,就必须用它。
下面这一段是找实现了ProiorityOrdered接口的的后置处理器,在一开始实例化容器时默认添加ConfigurationClassPostProcessor
,这个类就是实现了BeanDefinitionRegistryPostProcessor
,配置类的查找和注册都是由它主导的。
// 首先,先执行实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// 判断这个类是否还实现了PriorityOrdered接口
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
// 这里调用了getBean,所以生成一个BeanDefinitionRegistryPostProcessor的bean对象
// 调用getBean的时候,会添加到单例池中
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// 执行postProcessBeanDefinitionRegistry方法
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
跟进invokeBeanDefinitionRegistryPostProcessors
最里面是这个:ConfigurationClassPostProcessor#processConfigBeanDefinitions
步骤1:找配置类
代码语言:javascript复制// 遍历BeanDefinitionRegistry中当前存在的beanDefinition,从中找出那些beanDefinition是配置类
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
// 省略。。。
}
}
// 检查BeanDefinition是不是配置类候选者,那么什么样的BeanDefinition符合呢?
// 1. 存在@Configuration的就是配置类,或者
// 2. 存在@Component,@ComponentScan,@Import,@ImportResource,或者
// 3. 存在@Bean标注的方法
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 找不到候选就退出
if (configCandidates.isEmpty()) {
return;
}
如何确认是不是配置类
他这里有一个配置类候选的判断checkConfigurationClassCandidate
方法,如下
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
// 如果是AnnotatedBeanDefinition,那么就直接获取Metadata
// 如果是其他的,那么则根据类解析出来Metadata
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
// 省略。。。
return false;
}
}
// 获取Configuration注解的属性信息,配置类分两种,被@Configuration标记的配置类为full,其他的配置类为lite,full的配置类会生成代理对象
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 注意,并不是没有Configuration注解当前的BeanDefinition就不是一个配置类
// 注意isConfigurationCandidate方法,会检查是否存在@Component, @ComponentScan,@Import,@ImportResource,@Bean注解
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
// 如果没有Configuration注解信息,则返回false,表示不是一个配置类
return false;
}
// It's a full or lite configuration candidate... Let's determine the order value, if any.
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
下面这个方法是判断一个类是不是配置类的。
candidateIndicators
是静态代码块里定义的:
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
代码语言:javascript复制public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// 配置类不能是接口
if (metadata.isInterface()) {
return false;
}
// 查看是否有复合的注解
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// 最后在方法上是否有@Bean注解
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" metadata.getClassName() "]: " ex);
}
return false;
}
}
所以,判断一个类是不是配置类,并不是有@Configuration
注解的才是,还有Component、componentScan、Import、ImportResource
.
步骤2:配置类排序和名称生成器
代码语言:javascript复制// 将配置类进行排序,根据@Order注解进行排序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
SingletonBeanRegistry sbr = null;
// 当前BeanFactory是不是支持单例bean,如果支持则设置一个BeanNameGenerator,用来在扫描@Component和@Import某个Bean时取名字
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
// bean名称生成规则,可以通过 applicationContext.setBeanNameGenerator()设置
// 这里默认指定了
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
// 扫描时名称生成器
this.componentScanBeanNameGenerator = generator;
// import导入时的名称生成器
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
配置类,作为项目的配置基础,必然存在一个顺序,因为有些配置是需要前提配置的,所有出现此种情况时,会去实现order接口,如果没有实现这个接口,那么会以最低优先级设置(Integer.MAX_VALUE),但一般情况下都是不存在这种情况的,所有我们使用的基本上都没做这样的实现,但不代表不需要。
步骤3:解析配置类
代码语言:javascript复制// 配置类解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// configCandidates是已经找到的配置类
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 对配置BeanDefinition进行解析,解析完后会生成ConfigurationClass
parser.parse(candidates); //AppConfig.class
parser.validate();
// 添加解析到的class,还有我们的配置类
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());
}
// 利用reader解析ConfigurationClass,同时注册BeanDefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
怎样解析的配置类
在上面的do while中,有一个parser.parse(candidates);
解析我们扫描到的配置类,我们进入它最里面看看
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// 先看看有没有解析过
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
return;
}
else {
// 这里的意思是,可能存在旧的,但是以新的为准
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
// 把当前配置类存在configurationClasses里面
this.configurationClasses.put(configClass, configClass);
}
进入sourceClass = doProcessConfigurationClass(configClass, sourceClass);
步骤1:找所有component
递归查找component,这个比较简单,找到后还会查找内部类。
代码语言:javascript复制// 1. 如果配置bean上有@Component注解,递归去解析内部类上的注解
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}
步骤2:解析@PropertySource
代码语言:javascript复制// 2. 解析@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
// 省略。。。
}
}
@PropertySource
注解很少用到,我也是没用过,它的用法和ConditionalOnProperty
这个注解有相似之处。
如下例子:
代码语言:javascript复制@Component
@ConfigurationProperties(prefix = "test")
@PropertySource(value = {"classpath:pro.properties"})
//@PropertySource(value = {"file:E:/pro.properties"})
public class TestClass {
@Value("${test.name}")
private String name;
}
看下里面的实现:
代码语言:javascript复制private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
// @PropertySource注解中所指定的properties文件路径
for (String location : locations) {
try {
// 解析占位符
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
// 得到资源文件
Resource resource = this.resourceLoader.getResource(resolvedLocation);
// 把资源文件解析成PropertySource对象,并且添加到environment中去
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" location "] not resolvable: " ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
这里的实现就是获取到注解propertySource
的属性值(路径),然后通过ResourceLoader加载,然后解析到environment中去。
步骤3:解析@ComponentScan注解,并进行扫描
代码语言:javascript复制 // 3. 解析@ComponentScan注解,并进行扫描
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) {
// 扫描得到BeanDefinition
// 这里面有做doScan的操作
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 检查扫描所得到BeanDefinition是不是配置Bean,基本上都有@Component注解,所以都是配置类
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
这段里有两个parse方法,上面的一个parse,是将componentScan注解解析后,再根据basePackages去扫描路径下的bean,然后下面一个parse,是解析配置类,两个作用不一样,而且本身的实现都不一样,上面那个是ComponentScanAnnotationParser
的实现,下面是ConfigurationClassParser
的实现。
相当于是,上面的对ComponentScan里的标注的路径下找到配置类,然后下面的对找到的这些配置类进行解析,虽然这个路径已经很清晰了,我们还是深入看看它的一个逻辑吧。
componentScanParser.parse
首先呢,是componentScanParser.parse
,解析componentScan注解信息:
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 创建扫描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
// 设置BeanName生成器
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
// 设置IncludeFilter(包含的规则)
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
// 设置ExcludeFilter(排除的规则)
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
// 设置懒加载
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
// 获取componentScan里我们设置的基础扫描路径
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));
}
// 这里增加了一个抽象类的过滤器,因为尽管抽象类上写了component注解,可以认为是一个bean,但它并不能实例化
// 所以这个过滤器是会过滤掉抽象类和接口。
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 开始扫描包路径,得到BeanDefinitionHolder
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
在看这段的时候,我突然发现,它对我们设置的路径还有解析,所以我尝试了一下,结果:
竟然是正确的,所以,尽管我们在数组元素里写多个路径也是可以的。
代码语言:javascript复制// 这里对每一个路径又解析了一次
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
那么到这里基本上是可以理解了,但是为了和前面章节能够串联,这里我再贴一段,就是它扫描的方法soScan
,这个方法,可以对照《bean的生命周期》来看,里面findCandidateComponents
这个方法就是bean生命周期一开始的那一段。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 这是Spring中的Assert,大家开发时也可以用
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 扫描包路径得到BeanDefinition,得到的BeanDefinition是空的,还没有解析类上所定义的注解信息
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 得到Scope的信息,并设置
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 得到beanName
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并注册到registry中
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
parse
代码语言:javascript复制protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
然后这个方法,继续往里走,就又会回到下面这段,就是递归的解析配置类。
代码语言:javascript复制sourceClass = doProcessConfigurationClass(configClass, sourceClass);
那么这个componentScan的步骤它所做的,就是:
- 扫描ComponentScan得到配置类列表
- 创建扫描器
- 创建beanName生成器
- includeFilter、excludeFilter设置
- 懒加载
- 路径解析(这有一个特殊点)
- 设置默认的过滤器
- 扫描路径(doScan),这里可转到bean的生命周期篇章
- 解析扫描的配置类
- 递归整个方法
步骤4: 解析@Import
代码语言:javascript复制// 4. 解析@Import,getImports方法返回AppConfig.class上定义的Import注解中所导入的类的信息
processImports(configClass, sourceClass, getImports(sourceClass), true);
这一步就是解析得到import里导入的类的信息,然后根据导入的类实现什么接口走哪一种解析方法,代码不贴了,都是走一样的逻辑,还有递归。
还有一点是,SpringBoot中的自动配置类就是用到了这个Import,这个后面再详细说说。
最后还要说一下,这个import的使用是有3种方式,提前说一下,再看代码会比较好理解些。
- 使用注解Import
- 实现ImportSelector接口
- 实现ImportBeanDefinitionRegistrar接口
那么再来看代码;
- 首先先看一下
getImports
这个方法,这个方法是执行processImports
前调用的,如果说processImports方式法是执行import(导入),那么getImports就是从配置类里找出Import信息。
getImport方法深入之后是下面的方法:
它找的就是找有Import注解里的值,这里会看到递归调用了,这个其实就是存在这样的一种情况就是:
一个配置类有多个注解,但那些注解可能也是一个存在Import注解的,所以需要遍历注解,然后在递归调用。
就比如,SpringBoot中的自动配置的核心注解@EnableSpringbootApplication
。
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
- 那么再来看processImports,上一步是找出了所以的import,然后这里就是进行导入,上面也说了有3中方式实现效果,那么这里就是对这3种分类处理。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// 它属于是ImportSelector,需要添加到集合中等待处理
if (candidate.isAssignable(ImportSelector.class)) {
// 加载类对象
Class<?> candidateClass = candidate.loadClass();
// 实例化Selector对象,它需要生成handle对象
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 递归
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// 它属于一个bean定义注册器
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 如果@Import注解中的类实现了ImportBeanDefinitionRegistrar接口,就把该类的实例放入importBeanDefinitionRegistrars中,
// 后面再执行该实例的registerBeanDefinitions方法
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 解析配置类
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class ["
configClass.getMetadata().getClassName() "]", ex);
}
finally {
this.importStack.pop();
}
}
步骤5: 解析@ImportResource
这里是对spring的配置进行加载
代码语言:javascript复制// 5. 解析@ImportResource,得到导入进来的spring的xml配置文件,然后解析
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
// 资源位置
String[] resources = importResource.getStringArray("locations");
// 这里默认是BeanDefinitionReader的读取器
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
这一步比较简单。
步骤6: 解析配置类中的加了@Bean注解的方法
代码语言:javascript复制// 6. 解析配置类中的加了@Bean注解的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 7. 如果配置类实现了某个接口,那么则解析该接口中的加了@Bean注解的默认方法
processInterfaces(configClass, sourceClass);
SourceClass保存class对象和AnnotationMetadata
对象,我们不妨看一下retrieveBeanMethodMetadata
这个方法:
private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
// 获取到注解的元数据对象
AnnotationMetadata original = sourceClass.getMetadata();
// 找到含有Bean注解的方法
Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
// Try reading the class file via ASM for deterministic declaration order...
// Unfortunately, the JVM's standard reflection returns methods in arbitrary
// order, even between different runs of the same application on the same JVM.
try {
AnnotationMetadata asm =
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) {
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
break;
}
}
}
if (selectedMethods.size() == beanMethods.size()) {
// All reflection-detected methods found in ASM method set -> proceed
beanMethods = selectedMethods;
}
}
}
catch (IOException ex) {
//
}
}
return beanMethods;
}
这里根据注释说是要通过ASM得到@Bean注解方法的顺序,因为JVM里是不保证顺序的,但是sourceClass之前看到的是new SourceClass,里面的Metadata
对象就是和this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata()
一样的,那么这里为什么又要再来一次呢?
private SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException {
AnnotationMetadata metadata = configurationClass.getMetadata();
if (metadata instanceof StandardAnnotationMetadata) {
// 它走的是这个分支
return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
}
return asSourceClass(metadata.getClassName());
}
在调试后发现,它创建SourceClass是用的class对象,并非是用全类名,不是说spring里的class扫描是用asm不加载class吗,怎么现在又有了?
这里我忽略了一点,就是我们启动入口传入的配置类会被实例化,因为我们传入的是class对象,它从一开始就被加载了。
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); public AnnotatedGenericBeanDefinition(Class<?> beanClass) { setBeanClass(beanClass); // 当前类上有哪些注解 this.metadata = AnnotationMetadata.introspect(beanClass); }
所以,有两种情况,一直就是我们启动spring时传入的配置类,它被JVM加载,不保证@Bean注解的方法的顺序,另一种是ASM解析的配置类,它是有顺序的,所以这里,它再次通过ASM解析,保证@Bean注解的方法的顺序。
步骤7:父类递归
代码语言:javascript复制// 8. 如果有父类,则返回父类给上层遍历进行处理
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
// 避免加载系统类
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
总结
配置类的解析发生在spring启动时,首先是解析总的一个配置类,然再从这个配置类深入解析,然后重复这个步骤,直到没有配置类了,才会进行bean的实例化步骤。
配置类的一个流程:
- 后置处理器
ConfigurationClassPostProcessor
- 找配置类的beanDefinition
- @Configuration、@Component、@ComponentScan、@Import、@ImportResource还有@Bean
- 对配置类排序(@Order)
- 配置beanName生成器
- 解析配置类
- 先创建一个
ConfigurationClassParser
- 解析生成
ConfigurationClass
- 找component注解的类,并且检查内部类,然后解析
- 解析PropertySource注解
- 解析ComponentScan注解,先扫描到配置类,然后在对配置类解析
- Import的解析
- 解析ImportResource
- 解析Bean注解
- ConfigurationClass,同时注册为BeanDefinition
- 先创建一个
配置类就到这一步,它主要是将所有的bean注册为beanDefinition,之后还会有一步实例化步骤。
文中一直提到配置类,其实并不准确
上面流程中也有写,在spring中,配置类可以是5种:@Configuration、@Component、@ComponentScan、@Import、@ImportResource,所有配置类并不是单指某一类。
严格来说的话,在spring中,它以bean作为一个可管理的对象,那么进一步对bean对象的加载进行配置的是配置类,所有配置类也是一个bean,只是这些所谓的配置类有了配置的权限,就像我们创建一个配置类,我们可以用@Configuratoin,也可以用@Component,而这些配置类在spring中会被单独处理,因为他们比一般普通的bean有了其他的意义。
@ComponentScan注解是怎么实现扫描的
它内部由ClassPathBeanDefinitionScanner
实现路径下的扫描,它会先根据我们在componentScan注解中配置属性来配置这个扫描器,然后执行扫描器的doScan方法,底层是通过路径得到资源路径resource,然后一层层查找的,在本篇中没有深入去了解这个过程。