在spring应用中,通过@ComponentScan注解可以声明自动注入要扫描的包,spring会将该包下所有加了@Component注解的bean扫描到spring容器中,本文是spring根据包名进行扫描,获取要注入的业务类对应的源码
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//准备工作包括设置启动时间、是否激活标志位、初始化属性源配置
prepareRefresh();
/**
* 初始化BeanFactory,解析xml格式的配置文件
* xml格式的配置,是在这个方法中扫描到beanDefinitionMap中的
* 在org.springframework.web.context.support.XmlWebApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)中会创建一个XmlBeanDefinitionReader来解析xml文件
* 会把bean.xml解析成一个InputStream,然后再解析成document格式
* 按照document格式解析,从root节点进行解析,判断root节点是bean?还是beans?还是import等,如果是bean
* 就把解析到的信息包装成beanDefinitionHolder,然后调用DefaultListablebeanFactory的注册方法将bean放到beanDefinitionMap中
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//准备工厂,给BeanFactory设置属性、添加后置处理器等
prepareBeanFactory(beanFactory);
try {
//空方法,留给子类去自定义
postProcessBeanFactory(beanFactory);
/**
* TODO
* 完成对bean的扫描,将class变成beanDefinition,并将beanDefinition存到map中
*
* 该方法在spring的环境中执行已经被注册的factory processors;
* 执行自定义的processBeanFactory
*
* 在这个方法中,注入bean,分为了三种
* 一、普通bean:@Component注解的bean
* spring 自己的类,不借助spring扫描,会直接放到beanDefinitionMap
*
* 1.获取到所有的beanFactoryPostProcessor
* 2.执行 bean后置处理器的postProcessBeanFactory(configurationClassPostProcessor),该方法会把beanFactory作为入参传到方法里面
* 3.从beanFactory中获取到所有的beanName 打断点看一下 org.springframework.context.annotation .ConfigurationClassPostProcessor#processConfigBeanDefinitions
*
* 4.然后将所有的bean包装成beanDefinitionHolder,在后面又根据beanName和bean的metadata包装成了ConfigurationClass
* 5.把所有包含@ComponentScan的类取出来,遍历每一个componentScan,调用 ClassPathBeanDefinitionScanner.doScan(basePackages)方法
* 6.在doScan方法中,会遍历basePackages,因为一个ComponentScan中可以配置多个要扫描的包
* 7.获取每个包下面的 *.class文件,registerBeanDefinition(definitionHolder, this.registry); 这个方法底层就是调用org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition方法 把当前bean put到beanDefinitionMap中
*
* 二、是通过@Import注解注入的bean
* ImportBeanDefinitionRegistrar
* ImportSelector
*
* 三、@Bean注解
*
*
*
* spring在把bean注入到beanDefinitionMaps的同时,会将当前beanName添加到一个list中 beanDefinitionNames,这个list和beanDefinitionMap是同时进行添加的,这个list在后面实例化bean的时候有用到,spring是遍历这个list,拿到每个beanName之后,从beanDefinitionMap中取到对应的beanDefinition
*/
invokeBeanFactoryPostProcessors(beanFactory);
/**
* 注册beanPostProcessor;在方法里面
* 会先把beanPostProcessor进行分类,然后按照beanPostProcessor的name从spring容器中获取bean对象,如果spring容器中没有,就创建;所以如果一个beanDefinition是后置处理器,会这这里进行实例化,然后存放到单实例池中
* 然后再调用的是 beanFactory.addBeanPostProcessor(postProcessor);
* 把所有的beanPostProcessor放到了beanPostProcessors中,在后面初始化bean的时候,如果需要调用后置处理器,就会遍历这个list,
*/
registerBeanPostProcessors(beanFactory);
//初始化MessageSource组件(该组件在spring中用来做国际化、消息绑定、消息解析)
initMessageSource();
/**
* 注册一个多事件派发器
* 先从beanFactory获取,如果没有,就创建一个,并将创建的派发器放到beanFactory中
*/
initApplicationEventMulticaster();
/**
* 这是一个空方法,在springboot中,如果集成了Tomcat,会在这里new Tomcat(),new DispatcherServlert();
*/
onRefresh();
/**
* 注册所有的事件监听器
* 将容器中的事件监听器添加到 applicationEventMulticaster 中
*
*/
registerListeners();
/**
* TODO
* 完成对bean的实例化
*
* 主要的功能都在这里面
*/
finishBeanFactoryInitialization(beanFactory);
/**
* 当容器刷新完成之后,发送容器刷新完成事件
* publishEvent(new ContextRefreshedEvent(this));
*/
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
//当发生异常时,调用bean的销毁方法
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
在refresh方法中,完成bean的初始化、动态代理、放入容器等操作;我们着重来说spring是如何扫描bean的,invokeBeanFactoryPostProcessors(beanFactory);在该方法中,完成了对所有要注入到spring容器中的业务类的扫描,将业务类转换成beanDefinition,并存入了BeanDefinitionMap中,这就是该方法完成的操作
在自动注入之前,首先,我们要搞懂这两个接口的关系,前者是父接口,后者是子接口,实现了前者;所以,后者既有postProcessBeanFactory()方法,又有postProcessBeanDefinitionRegistry()方法,为什么要说这两个方法呢?因为在spring源码中,这两者的执行是由顺序的;
1、@ComponentScan注解是加载配置类上的,所以,我们首先要解析配置类
我们前面有说到过,自动扫描是在invokeBeanFactoryPostProcessors(beanFactory);方法中完成的,所以,我们来看这个方法
跳进这个方法之后,代码有很多,隐藏了很多小的知识点,我们不关心,后面会一一的单独拎出来学习,学习自动扫描的解析,我们只需要关心一行代码
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
2、这个是执行BeanDefinitionRegistryPostProcessor接口实现类的postProcessBeanDefinitionRegistry()方法的;从spring启动到现在,beanDefinitionMap中的所有bean,BeanDefinitionRegistryPostProcessor接口的实现类,只有一个***ConfigurationClassPostProcessor***;我在前面的文章中说到过,在spring扫描bean的时候,这个类是特别重要的;
所以,执行到这行代码的时候,会执行ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()方法;在该方法中,前面是一些判断,关键调用的方法是org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
/**
* 这个list用来保存
* 添加@Configuration的类
* 添加了@Component
* 或者@ComponentScan
* 或者@Import
* 或者@ImportResource
* 注解的类
*
* 唯一的区别是:如果类上加了@Configuration,对应的ConfigurationClass是full;否则是lite
*
* 正常情况下,第一次进入到这里的时候,只有配置类一个bean,因为如果是第一次进入到这里的话,beanDefinitionMap中,只有配置类这一个是我们程序员提供的
* 业务类,其他的都是spring自带的后置处理器
*/
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//获取在 new AnnotatedBeanDefinitionReader(this);中注入的spring自己的beanPostProcessor
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
/**
* 根据beanName,从beanDefinitionMap中获取beanDefinition
*/
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
/**
* 如果bean是配置类,configurationClass就是full,否则就是lite
* 这里,如果当前bean 的configurationClass属性已经被设置值了,说明当前bean已经被解析过来,就无需再次解析
*/
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
//校验bean是否包含@Configuration 也就是校验bean是哪种配置类?注解?还是普通的配置类
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
/**
* 对config类进行 排序 configCandidates中保存的是项目中的配置类 (AppConfig ....),或者说是加了@Configuration注解的类
*/
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Parse each @Configuration class
//实例化ConfigurationClassParser是为了解析各个配置类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
/**
* 这两个set主要是为了去重
* 正常情况下,下面的do...while循环中,只会循环处理所有的配置类,因为到目前,还没有普通的bean添加到beanDefinitionMap中
*/
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
/**
* 如果将bean存入到beanDefinitionMap第三步
*
* 这里的candidates的个数是由项目中 配置文件的数量来决定的(或者说加了@Configuration或者@ComponentScan或者@Component注解的类)
*/
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());
}
/**
* 这里的configClasses 就是parse方法中,对import注解进行处理时,存入的;
* 这里面存放的是import注入类返回数组对象中的bean(就是实现importSelector接口的类中返回的值,也就是要在import中注入的bean对应的全类名)
*
* 在这个方法里面 完成了对ImportSelector和ImportBeanDefinitionRegistrar注入的bean进行初始化
*/
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());
}
3、这个方法中的部分代码,我删除掉了,因为和本次要看的代码无关
该方法中,完成了以下操作
4、我们需要关心的是对@ComponentScan注解的处理:即
parser.parse(candidates);
这行代码,再往下继续处理,嵌套的层次比较深,我录制了一个GIF,可以看下
在这中间的方法,核心的思想是,从beanDefinition中,获取到componentScan注解的value,即:要扫描的包信息,gif的最后一个方法是
org.springframework.context.annotation.ComponentScanAnnotationParser#parse
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
//这里的scanner就是spring自己new出来的,用来扫描包的scanner
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
.......
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
/**
* 如果将bean存入到beanDefinitionMap第七步
*/
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
这个方法中,我删除了一部分代码,要说明的有两个问题
doScan()的源码,我放张截图
在doScan()方法中,完成了以下操作:
我们要关注的是第一步这里:findCandidateComponents(basePackage)
这个方法中,就是完成了真正的所谓的自动扫描
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
// 判断文件是否可读:这里百度的结果是:如果返回true,不一定可读,但是如果返回false,一定不可读
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
/**
* isCandidateComponent 也是扫描bean中一个比较核心的方法吧
* 由于这里的resource是包下所有的class文件,所以,需要在这个方法中判断是否符合注入条件
*
* 在AnnatationConfigApplication构造函数中,初始化了一个ClassPathBeanDefinitionScanner;
* 在初始化这个bean的时候,给一个list中存入了三个类,其中有一个就是Component.class,个人理解:在这个方法中,会
* 判断扫描出来的class文件是否有Component注解;需要注意的是@Controller @Service @Repository都是被@Component注解修饰的
* 所以,@Controller... 这些注解修饰的bean也会被注入到spring容器中
*
* excludeFilter是在doScan()方法中赋值的,excludeFilter中包含的是当前配置类的beanClassName;因为当前配置类已经存在于beanDefinitionMap中,无需再次添加
*/
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
/**
* 对scannedGenericBeanDefinition进行判断
*/
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
需要注意的是:这里有两个isCandidateComponent方法,但是完成的功能不同
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
/**
* spring在扫描普通bean,是用的这个方法,mybatis对该方法进行了扩展,mybatis判断一个beanDefinition是否要放到beanDefinitionMap中,是判断当前是否是接口,是否是顶级类(org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent)
*
* isIndependent:当前类是否独立(顶级类或者嵌套类)
* isConcrete: 不是接口,不是抽象类,就返回true
* 如果bean是抽象类,且添加了@LookUp注解,也可以注入
* 使用抽象类+@LookUp注解,可以解决单实例bean依赖原型bean的问题,这里在spring官方文档中应该也有说明
*/
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
截止到这里,基本上整个流程先串起来了,spring源码中的细节非常多,在完成扫描的过程中,有许多的知识点,后面会一一来介绍,本文只是讲了spring将bean添加到beanDefinitionMap中的一种方式;
spring将bean添加到beanDefinitionMap中有四种方式
org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)
org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
org.springframework.context.annotation.ConfigurationClassParser#parse(org.springframework.core.type.AnnotationMetadata, java.lang.String)
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
org.springframework.context.annotation.ComponentScanAnnotationParser#parse
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
这是整个调用链