Java|Java Springboot自动装配原理详解
目录
- Debug路线图
- 让我们从run说起
- 归属
- 小结
- run
- 再说说注解
- 总结
Debug路线图 说多都是泪,大家看图。
文章图片
让我们从run说起 用了这么多年的的Springboot,这个 run() 方法到底做了些什么事呢?
@SpringBootApplicationpublic class SpringbootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootDemoApplication.class, args); }}
归属
run() 方法归属于
SpringApplication.class
对象,所以在调用run() 方法前,需要先实例化 SpringApplication.class
对象:public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args); }
SpringApplication.class
对象实例化时,都做了些什么事呢?这里主要看需要注意两个方法:①
getSpringFactoriesInstances()
和 ②deduceMainApplicationClass()
/** * 实例化时,实际调用的构造方法 */public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 这里将spring.this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories(); // 设置初始化器setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 设置监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
①
getSpringFactoriesInstances()
方法主要加载整个应用程序中的 spring.factories
文件,将文件的内容放到缓存对象中,方便后续获取使用。private static Map> loadSpringFactories(ClassLoader classLoader) {// 初次加载,cache中获取不到数据,所以为nullMap> result = cache.get(classLoader); if (result != null) {return result; }result = new HashMap<>(); try {// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories",这个配置类眼熟吧// 加载配置类Enumerationurls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) {URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); // 这里加载资源Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim()); }}}// Replace all lists with unmodifiable lists containing unique elementsresult.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); // 这里将获取到资源放入cache中cache.put(classLoader, result); }catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex); }return result; }
②
deduceMainApplicationClass()
方法返回了启动类的类信息:文章图片
小结
实例化
SpringApplication.class
对象完成了两件事:1.加载整个应用程序中的
spring.factories
文件,将文件的内容放到缓存对象中,方便后续获取使用。2.返回了启动类的类信息。
run 有了
SpringApplication.class
对象实例对象,接下来就可以调用run() 方法。在run() 方法中,我们主要关注两个方法
prepareContext()
和 refreshContext()
public ConfigurableApplicationContext run(String... args) {// 省略部分代码 try {prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); }// 省略部分代码
prepareContext()
方法准备上下文环境,通过调用load()方法加载启动类,为获取启动类上的注解做准备;private void load(Class> source) {if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {// Any GroovyLoaders added in beans{} DSL can contribute beans hereGroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans()); }// 这里会判断启动类不是一个groovy闭包也不是一个匿名类if (isEligible(source)) {// 注册读取启动类的注解信息// 注意,这里将启动类型注册为AnnotatedBeanDefinition类型,后面parse()解析时会用到。this.annotatedReader.register(source); } }
refreshContext()
方法最终调用了AbstractApplicationContext.class
类的 refresh()
,这里相信看过spring源码的小伙伴都很熟悉 refresh()
这个方法。自动装配操作的主战场主要是在 ①
invokeBeanFactoryPostProcessors()
方法,①调用了②invokeBeanDefinitionRegistryPostProcessors()
方法,②调用了ConfigurationClassPostProcessor.class
的③postProcessBeanDefinitionRegistry()
方法,③调用了 ④processConfigBeanDefinitions()
方法;④
processConfigBeanDefinitions()
方法中:public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {ListconfigCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); // 这里会循环匹配到启动类,并且添加到上面的configCandidates集合中。for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); }}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); }}// ...// 解析每一个标注了@Configuration注解的类,启动类上的@SpringBootApplication就包含了@Configuration注解ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set candidates = new LinkedHashSet<>(configCandidates); Set alreadyParsed = new HashSet<>(configCandidates.size()); do {StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); // 开始解析parser.parse(candidates); parser.validate(); }// ============ 分割线 =================/** * 为了方便阅读,这里将parse()方法接入 */public void parse(Set configCandidates) {for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition(); try {// 在前面prepareContext()方法中的load()方法中已经说过,所以这会进入这个判断解析if (bd instanceof AnnotatedBeanDefinition) {parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // ...}// 注意!!这里,parse()解析完成后,会回到这里执行process()方法;this.deferredImportSelectorHandler.process(); }
进入判断后
parse()
方法会接着调用 ①processConfigurationClass()
方法,①调用②doProcessConfigurationClass()
方法;②
doProcessConfigurationClass()
中又开始对注解进行进一步的解析,包括@PropertySource、@ComponentScan、@Import
(咱们看这个)、@ImportResource、@Bean,解析之前,会通过getImports()
方法调用collectImports()
方法,统计出被@Import标注的类型信息;protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)throws IOException {// ...// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), filter, true); // ...// ============ 分割线 =================private Set getImports(SourceClass sourceClass) throws IOException {Set imports = new LinkedHashSet<>(); Set visited = new LinkedHashSet<>(); collectImports(sourceClass, imports, visited); return imports; }private void collectImports(SourceClass sourceClass, Set imports, Set 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")); } }}
getImports()
方法查询结果展示:文章图片
当
parse()
方法解析完成 @Import 注解后(这里忘记的小伙伴,可看看上面的parse()方法,我有代码注释),接着开始调用①process()
方法,①中调用②processGroupImports()
方法,②中接着调用 ③grouping.getImports()
方法,③调用DeferredImportSelector.Group
接口的 ④process()
方法,这里我们看它的实现类 AutoConfigurationImportSelector.class
实现的 ④process()
方法(这里需要留意一下,一会还会回来用到),④调用了 ⑤getAutoConfigurationEntry(),⑤
中调用了⑥getCandidateConfigurations()
方法;重点来了:经过上面的一系列方法调用,终于来到这个方法,相信大家在许多博客里都又看到过 ⑥getCandidateConfigurations() 这个方法,但又没有说清楚具体是怎么调用到这个方法的,只是说了这个类会得到待配置的class的类名集合等等;
在⑥这个方法中,显示通过 ⑦
getSpringFactoriesLoaderFactoryClass()
这个方法返回了一个EnableAutoConfiguration.class
注解对象,然后又通过调用 ⑧loadFactoryNames(),⑧
又调用了 ⑨loadSpringFactories();
⑧⑨方法看着是不是比较眼熟? 是的,我们在初始化SpringApplication对象时,曾调用过这两个方法,在调用⑨时,将 spring.factories 文件的内容放到cache缓存对象中。
@Overrideprotected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// getSpringFactoriesLoaderFactoryClass()这个方法返回了一个EnableAutoConfiguration.class注解对象List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "+ "are using a custom packaging, make sure that file is correct."); return configurations; }protected Class> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class; }// ===========================分割线===========================private static Map> loadSpringFactories(ClassLoader classLoader) {Map> result = cache.get(classLoader); if (result != null) {return result; } // ...
此时的cache对象中存在EnableAutoConfiguration对象,size=131个:
文章图片
这131个就是
spring.factories
文件中的自动装配配置项:文章图片
当然,这里面有许多我们没有用到类信息也被装配了进来,这里不要着急接着往下看,装配完成后回到了⑤
getAutoConfigurationEntry()
方法中,且返回了一个List< String>的一个配置类信息集合,接着又做了些什么事?文章图片
从上图可以看出,131个配置信息,经过过滤移除后,最终变成13个需要使用的,拿到最终配置信息,(愣着干嘛,赶紧撒花呀!),到这里自动装配过程基本上就结束了。
这里的结束是指自动装配过程结束,也就是我们 refresh()中的invokeBeanFactoryPostProcessors() 方法执行结束,当然这个方法还做很多别的事,但是本文只关注自动装配相关,完成此方法后并不表示类就已经实例化完成,这里只是将类信息装配到了spring容器中,后续会有别的方法完成类的实例化。(实例化看它:finishBeanFactoryInitialization())
文章图片
再说说注解 @SpringBootApplication 是的没错,这个注解大家都熟悉,springboot 项目启动类上都有:
文章图片
@SpringBootApplication 中包含了两个注解:
- @EnableAutoConfiguration(重点):启用 SpringBoot 的自动配置机制;
- @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类;
- @SpringBootConfiguration:允许在上下文中注册额外的 bean 或导入其他配置类;
文章图片
@EnableAutoConfiguration
注解通过 Spring 提供的 @Import
注解导入了 AutoConfigurationImportSelector.class
类(@Import 注解可以导入配置类或者 Bean 到当前类中),这个类的作用在上也说过(获取spring.factories文件中待配置的class的类名集合)。总结 【Java|Java Springboot自动装配原理详解】本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- Activiti(一)SpringBoot2集成Activiti6
- python学习之|python学习之 实现QQ自动发送消息
- 事件代理
- SpringBoot调用公共模块的自定义注解失效的解决
- Java|Java OpenCV图像处理之SIFT角点检测详解
- 解决SpringBoot引用别的模块无法注入的问题
- java中如何实现重建二叉树
- 使用composer自动加载类文件
- 数组常用方法一