spring容器启动创建javaBean步骤
下面三张图是我创建feign服务的方法,以feign为例来看javaBean的创建
结论:spring容器启动时有个refresh方法,会将工程目录所有要生成 javaBean的类文件都加载存进一个BeandefinitionMap中,键值对形式,Key是beanName, value是Bean描述文件,然后根据描述文件进行javaBean实例化。
由于定位feign对象的fallbackFactory属性是如何注入进javabean对象的,调试代码顺便记录下spring容器启动
上图是获得feignJAVABEAN对象描述的方法执行顺序
到这里就是FeignClientsRegistrar类调用注册方法设置feign对象的属性(获取注解的属性值并注入生成描述文件)
spring容器启动后
第一步调用
org.springframework.boot.SpringApplication对象调用public ConfigurableApplicationContext run(String... args)方法做初始化
主要关注如下几个方法
1.prepareContext() 做工程应用的初始信息加载
2.refreshContext() (下图中的resfresh()) 扫描工程下javabean,并生成javabean实例
2.1invokeBeanFactoryPostProcessors()会扫描工程下class文件信息用作初始beanIndifinition,将javabeanName和javaBean文件描述信息建立对应关系
2.2finishBeanFactoryInitialization() 创建javaBean实例(见下图,代码暂时不看后面再描述)
3.afterRefresh(); 是spring框架留给我们自定义的接口方法
invokeBeanFactoryPostProcessors则会开始扫描工程下的组件
org.springframework.context.annotation.ConfigurationClassPostProcessor类调用processConfigBeanDefinitions()方法会扫描javaBean并将信息存进
看上图扫描出来118个组件,但是只有5个是有 beanName的,其他的beanName都是null,则就需要使用louadBeanDefinitions()方法去加载类文件信息获得javaBean名称
这里主要说两个方法 一个是1.parse方法(扫描组件) 2.loadBeanDefinitions方法(将javabean文件放进beanDefinition 如果是需要registrar注册器注册的javaBean 也会在这个方法内去扫描拿到信息加载进beanDefinition)
简单说:1.parse方法只能扫描到spring组件(@component, @Controller,@Service之类) 像feign这种三方件就需要通过注册器去扫描加载,换而言之如果你自定义一个组件希望spring容器管理,也就需要实现注册器等接口 这样spring容器
启动代码才能加载自定义的组件信息并让spring容器管理。
见下图 初始解析后扫描出来的组件是118个 但是经过loadBeanDefinitions方法使用注册器二次扫描后组件是305个 并保存在registry中(开头的feign服务接口javaBeanName和描述文件也在其中)
1.parse方法扫描组件
org.springframework.context.annotation.ComponentScanAnnotationParser.public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) 方法解析要扫描的包
该方法会通过@springbootapplication配置的basepackage basepackageclasses属性去取要扫描的包路径 如果都没有则取该@springbootapplication注解标记的类所在包为扫描路径
并且new一个ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner类来进行扫描
最后一行代码是scanner
doscan()方法见下图将扫描的javaBean及说明 存进beanDefinitions (看右边储存的信息 都是class文件路径以及说明信息 并不是javaBean对象)
2.this.reader.loadBeanDefinitions(configClasses);该方法依次遍历 parse解析出来的javabean文件说明来给javaBean命名
同时判断这些javabean.class文件是否有importBeanDefinitionRegistrars(注册器,如果有注册器还需要根据注册器二次扫描javaBean)
这也是前面图中第一次扫描只有118个组件,二次扫描有305个组件的原因。
下图是loadBeanDefinitions方法的方法体,
调用
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader类执行loadBeanDefinitionsForConfigurationClass()
FeignclientService1Application这个启动类有引入两个注册器用于加载三方件。见下图
重点关注这两个方法(见上图)
this.loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); //读取配置文件来初始化javaBean说明文件
this.loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); //调用registra(类提供的注册器)来初始化javaBean 说明文件
上面的feign对象初始化就需要注册器来注册
两个注册器 AutoConfigurationPackages FeignClientsRegistrar
(也就是说要使用spring容器通过注册器初始化 javaBean 则必须在提供的jar包中实现importBeanDefinitionRegistrar接口,这样spring容器启动时也就可以调用该接口的方法进行bean说明文件初始化,实现该接口既可以自定义初始化)
注册实现类重载了注册方法(见下图),调用了两个方法
registerDefaultConfiguration( )通过xml配置文件初始化javabean
registerFeignClients()通过@FeignClient注解来初始化javaBean说明文件 (重点看该方法)
将代码registerFeignClients()方法拷出来方便写注释
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//由于可能配置有多个@FeignClient注解的javaBean 因此创建集合来存储
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();
//加载EnableFeignClients的注解属性 共有五个value basePackages basePackageClasses defaultConfiguration clients
//@EnableFeignClients是配置在启动类上的注解
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
//clients就是你在启动类上通过注解配置的要创建的feign服务 通过注解配置获取后放入集合
if (clients != null && clients.length != 0) {
Class[] var12 = clients;
int var14 = clients.length;
for(int var16 = 0; var16 < var14; ++var16) {
Class<?> clazz = var12[var16];
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
} else {
//通过扫描器去扫描带有@FeignClient注解的类 然后放入集合 见下图
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
//通过注解@EnableFeignClients去获取属性basePackage和basePackageClassses的值,并将这些值作为要扫描的包路径集合
// 如果包路径集合为空 则取@EnableFeignClients标记的类所在包为扫描路径
//(类似@mapperScan如果没有配置,则取@enableAutoConfiguration(该注解属于@SpringBootApplication)标记的类所在包为扫描路径)
Set<String> basePackages = this.getBasePackages(metadata);
Iterator var8 = basePackages.iterator();
while(var8.hasNext()) {
String basePackage = (String)var8.next();
//扫描器根据扫描包路径添加feignClient组件
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
Iterator var13 = candidateComponents.iterator();
while(var13.hasNext()) {
BeanDefinition candidateComponent = (BeanDefinition)var13.next();
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
//通过注解拿到该javabean的注解属性 以便初始化
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
//这里有个坑 如果你配置@FeignClient注解 配置了contextId name value serviceId 那么优先级从低到高为serviceId <name <value<contextId 这个值最终是要消费的服务名
String name = this.getClientName(attributes);
this.registerClientConfiguration(registry, name, attributes.get("configuration"));
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
扫描获得FeignController成功 然后调用registerFeignClient()方法初始化
通过获取注解中配置的fallbackFactory属性并注入javaBean
上图方法没截完 接下图
执行完断点方法后将@feignClient注解标识的类作为javaBeanName和javaBean说明文件存入BeanDefinitionMap和BeanDefinitionNames.
下图是上图断点方法内部实现 将@feignClient注解标识的类作为javaBeanName和javaBean说明文件存入BeanDefinitionMap和BeanDefinitionNames.
参考如下两文(https://cloud.tencent.com/developer/article/1449288)
(https://blog.csdn.net/weixin_45481821/article/details/129703973)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人