BeanDefinition扫描
执行流程
- 根据classpath:* + 包名获取所有.class文件
- 遍历Resouce资源文件。
- 判断当前resouce文件所指的类是否拥有component注解,也就是判断该bean是否要扫描到spring容器中。
- 构建beanDefiniation,并将资源和类全限定包名设置上去。
- 在上一步仅仅是构造了BeanDefiniation并将bean全限定名称设置上去,但是类是否为单例、是否懒加载等等还没有设置,所以接下来要设置其余BeanDefiniation。
- 遍历BeanDefinitionList,并设置scope。
- 获取并设置beanName。
- 设置beanDefiniation默认值。
- 解析 @Lazy、@DependsOn、@Role和@Description注解
- 检查bean是否注册过,如果没注册过则对bean进行注册。
AnnotationConfigApplicationContext
首先AnnotationConfigApplicationContext
新建了两个对象,分别是BeanDefinition
读取器和扫描器,Bean是由扫描器来进行扫描的,先从这个类看起,spring是怎样完成扫描操作的。
包扫描
在ClassPathBeanDefinitionScanner
中有个scan
方法用于进行包扫描的,在这个方法中有个doScan
方法用于完成扫描逻辑,进入这个方法看一下。
主要注释已经写完了,通过扫描将beanDefinitions
进行返回
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) {
// 获取BeanDefinition,这里仅仅是将类名称获取到,是否单例 是否懒加载之类的参数还未获取,以下for循环就是获取这些参数的
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 获取scope数据信息
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) {
// 解析 @Lazy、@DependsOn、@Role和@Description注解
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 检查bean是否被注册
if (checkCandidate(beanName, candidate)) {
// 构造一个BeanDefinitionHolder一,BeanDefinitionHolder中封装着beanName和BeanDefinition
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 这个先忽略
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册beanDefinition到map中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
findCandidateComponents()
该方法用于 获取BeanDefinition,这里仅仅是将类名称获取到,是否单例 是否懒加载之类的参数还未获取,以下for循环就是获取这些参数的,这个方法有两种情况,之后在讲第一种情况,一般使用的都是第二种情况。
进入scanCandidateComponents
方法。在方法中将包名与classpath进行拼接获取编译目录
之后获取包下的所有.class资源文件。
遍历这些资源文件,找出符合扫描规则的资源
之后构建BeanDefinition并将资源设置到BeanDefinition中,最后添加到BeanDefinition list中,执行完这些操作之后进行返回。
isCandidateComponent()
判断resource是否为bean是根据该类是否被排除、是否拥有includeFilters中设置的规则和是否与Condition注解规则匹配来完成的。
构造BeanDefinition
时因为元数据读取器底层采用的是ASM技术,并没有将类加载到JVM中,所以这里仅仅将className设置到MetaData中,等后续使用反射实例化类时才会修改为class对象。
isCandidateComponent()
如果类不是独立也就是类中嵌套着内部类的则不能扫描成bean,如果是接口或抽象类也不能扫描成bean,如果是抽象类且类中方法包含lookup注解则会扫描成bean
resolveScopeMetadata()
获取scope信息时,遍历类中的所有注解,如果注解存在,则将Scope信息进行返回,如果不存在则构建一个默认的scope进行返回。
初始化beanDefinition
会设置默认值, 比如默认不是烂加载 是否有初始化方法等等。
对注解进行解析
这里也没有讲 不知道作用是扫描
检查bean是否被注册
检查bean是否被注册,如果没有被注册则对bean进行注册,如果已经被注册过,那么会有两种情况,分别是如果重名的两个bean可以互相兼容,则第二被注册的bean不会被注册,如果两个bean不兼容则会抛出异常
Lookup注解
用于解决单例Bean中成员变量中非单例对象使用依赖注入时只能注入单例值的问题。
在方法中添加一个LookUp注解,注解中的值为要注入beanName,当每次调用该方法时,都会获取不同的值注入到非单例对象中。
@Component
public class UserService {
@Autowired
private OrderService orderService;
@Lookup("orderService")
protected OrderService orderService() {
return null;
}
public void test() {
this.orderService = orderService();
System.out.println("orderService" + orderService);
System.out.println("Hello world");
}
}
findCandidateComponents的第一种情况
如果resource目录下新建个spring.components文件,在文件中可以以key做要扫描的全限定名称,value做使用的bean注解
配置了这个文件之后就会走第一个if条件判断
在这个条件判断中会读取spring.components文件中的所有数据并以key list的方式解析到componentsIndex中,之后调用addCandidateComponentsFromIndex方法进行bean解析,解析bean的逻辑过程和之前的代码相同。
使用这个方法对bean进行扫描的好处是在对包进行扫描时,无需扫描所有的.class文件,只需要扫描在spring.components中的配置项即可,缺点是还得写配置文件。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)