《SpringBoot》自动装配原理(简单易懂)

news/2025/1/20 8:19:25/文章来源:https://www.cnblogs.com/seven97-top/p/18679465

引入

先看SpringBoot的主配置类

@SpringBootApplication
public class DemoApplication{public static void main(String[] args){SpringApplication.run(StartEurekaApplication.class, args);}
}

@SpringBootApplication

点进@SpringBootApplication来看,发现@SpringBootApplication是一个组合注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

@SpringBootApplication 由 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合组成:

  • @Configuration:允许注册额外的 bean 或导入其他配置类
  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @ComponentScan:扫描被@Component (@Repository,@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。

@SpringBootConfiguration

@SpringBootConfiguration 注解源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

可以看到这个注解除了元注解以外,就只有一个@Configuration,那也就是说这个注解相当于@Configuration,所以这两个注解作用是一样的,也就是能够去注册一些额外的Bean,并且导入一些额外的配置。

@Configuration还有一个作用就是把该类变成一个配置类,不需要额外的XML进行配置。所以@SpringBootConfiguration就相当于@Configuration。

进入@Configuration,发现@Configuration核心是@Component,说明Spring的配置类也是Spring的一个组件。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {@AliasFor(annotation = Component.class)String value() default "";
}

@EnableAutoConfiguration

继续看@EnableAutoConfiguration,这个注解是开启自动配置的功能,源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}

可以看到它是由 @AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)这两个而组成的,

@AutoConfigurationPackage

先看@AutoConfigurationPackage,这是为了让包中的类以及子包中的类能够被自动扫描到spring容器中。

源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

可以看到,这里使用@Import 来给Spring容器中导入一个组件 ,这里导入的是Registrar.class。来看下这个Registrar:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());}public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));}}

就是通过以上这个方法获取扫描的包路径,可以debug查看具体的值:

那metadata是什么呢,可以看到是标注在@SpringBootApplication注解上的DemoApplication,也就是主配置类Application:

其实就是将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器。因此要把DemoApplication放在项目的最高级中(最外层目录)。

@Import(AutoConfigurationImportSelector.class)

看看注解@Import(AutoConfigurationImportSelector.class),@Import注解就是给Spring容器中导入一些组件,这里传入了一个组件的选择器:AutoConfigurationImportSelector。

可以从图中看出AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector,ImportSelector有一个方法为:selectImports。将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}
}

这里会给容器中导入 自动配置类(xxxAutoConfiguration),也就是给容器中导入这个场景需要的所有组件,并配置好这些组件。

有了自动配置类,就免去了手动编写配置注入功能组件等的工作。

那是如何获取到这些配置类的呢,看看下面这个方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);configurations = this.removeDuplicates(configurations);Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.filter(configurations, autoConfigurationMetadata);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}
}

可以看到getCandidateConfigurations()这个方法,他的作用就是引入系统已经加载好的一些类,那么到底是那些类呢:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

会从META-INF/spring.factories中获取资源,然后通过Properties加载资源:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);if (result != null) {return result;} else {try {Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");LinkedMultiValueMap result = new LinkedMultiValueMap();while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Map.Entry<?, ?> entry = (Map.Entry)var6.next();String factoryClassName = ((String)entry.getKey()).trim();String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryName = var9[var11];result.add(factoryClassName, factoryName.trim());}}}cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);}}
}

可以知道SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前需要自己配置的东西,自动配置类都帮我们完成了。

如下图可以发现Spring常见的一些类已经自动导入。

@ComponentScan

接下来看@ComponentScan注解,@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }),这个注解就是扫描包,然后放入spring容器。

@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})
public @interface SpringBootApplication {}

总结下@SpringbootApplication:就是说,他已经把很多东西准备好,具体是否使用取决于我们的程序或者说配置。

小结

总的来说,SpringBoot的自动装配原理就是 通过@EnableAutoConfiguration注解在类路径的META-INF/spring.factories文件中找到所有的对应配置类,然后将这些自动配置类加载到spring容器中

run方法

public static void main(String[] args) {SpringApplication.run(Application.class, args);}

来看下在执行run方法到底有没有用到哪些自动配置的东西,点进run:

public ConfigurableApplicationContext run(String... args) {//计时器StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();this.configureHeadlessProperty();//监听器SpringApplicationRunListeners listeners = this.getRunListeners(args);listeners.starting();Collection exceptionReporters;try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);Banner printedBanner = this.printBanner(environment);//准备上下文context = this.createApplicationContext();exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,                       new Class[]{ConfigurableApplicationContext.class}, context);//预刷新contextthis.prepareContext(context, environment, listeners, applicationArguments, printedBanner);//刷新contextthis.refreshContext(context);//刷新之后的contextthis.afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}listeners.started(context);this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}try {listeners.running(context);return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}
}

那我们关注的就是 refreshContext(context); 刷新context,我们点进来看。

private void refreshContext(ConfigurableApplicationContext context) {refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}
}

继续点进refresh(context);

protected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);((AbstractApplicationContext) applicationContext).refresh();
}

会调用 ((AbstractApplicationContext) applicationContext).refresh();方法,点进来看:

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}
}

由此可知,就是一个spring的bean的加载过程。继续来看一个方法叫做 onRefresh():

protected void onRefresh() throws BeansException {// For subclasses: do nothing by default.
}

在这里并没有直接实现,找他的具体实现:

比如Tomcat跟web有关,可以看到有个ServletWebServerApplicationContext:

@Override
protected void onRefresh() {super.onRefresh();try {createWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}
}

可以看到有一个createWebServer()方法,用于创建web容器,而Tomcat不就是web容器。

那是如何创建的呢:

private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {ServletWebServerFactory factory = getWebServerFactory();this.webServer = factory.getWebServer(getSelfInitializer());}else if (servletContext != null) {try {getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context",ex);}}initPropertySources();
}

factory.getWebServer(getSelfInitializer()),显然是通过工厂的方式创建的。

public interface ServletWebServerFactory {WebServer getWebServer(ServletContextInitializer... initializers);
}

可以看到 它是一个接口,为什么会是接口。因为不止是Tomcat一种web容器,可以看到还有Jetty

接下来看TomcatServletWebServerFactory:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {Tomcat tomcat = new Tomcat();File baseDir = (this.baseDirectory != null) ? this.baseDirectory: createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());Connector connector = new Connector(this.protocol);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);tomcat.getHost().setAutoDeploy(false);configureEngine(tomcat.getEngine());for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}prepareContext(tomcat.getHost(), initializers);return getTomcatWebServer(tomcat);
}

这块代码,就是要寻找的内置Tomcat,在这个过程当中,可以看到创建Tomcat的一个流程。

也就是:

  1. 首先从main找到run()方法,在执行run()方法之前new一个SpringApplication对象
  2. 进入run()方法,创建应用监听器SpringApplicationRunListeners开始监听
  3. 然后加载SpringBoot配置环境(ConfigurableEnvironment),然后把配置环境(Environment)加入监听对象中
  4. 然后加载应用上下文(ConfigurableApplicationContext),当做run方法的返回对象
  5. 最后创建Spring容器,refreshContext(context),实现starter自动化配置和bean的实例化等工作。

面试题专栏

Java面试题专栏已上线,欢迎访问。

  • 如果你不知道简历怎么写,简历项目不知道怎么包装;
  • 如果简历中有些内容你不知道该不该写上去;
  • 如果有些综合性问题你不知道怎么答;

那么可以私信我,我会尽我所能帮助你。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/871973.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VMware Data Services Manager 2.2 发布 - 数据库管理和数据服务管理

VMware Data Services Manager 2.2 发布 - 数据库管理和数据服务管理VMware Data Services Manager 2.2 发布 - 数据库管理和数据服务管理 在 vSphere 环境中按需配置和自动管理 PostgreSQL 和 MySQL 数据库 请访问原文链接:https://sysin.org/blog/vmware-data-services-mana…

PyMuPDF4LLM : 提取 PDF 数据的终结者

你知道处理 PDF 文件时会遇到什么,尤其是那些包含复杂研究内容或者有表格、图像和额外信息的文档。对于 AI 领域的人,特别是那些在调整大型语言模型(LLM)或构建知识检索系统(比如 RAG)的人来说,提取正确的数据非常重要。然而,老实说,并不是所有的 PDF 提取工具都能做到…

【Java开发】LLM实战:使用LangChain4j构建本地RAG系统

一、引言二、基本概念2.1 什么是RAG2.2 LangChain4j简介2.3 大模型开发 vs. 传统JAVA开发三、实战经验3.1 环境搭建3.1.1 向量库(Chroma)  3.1.2 集成LangChain4j3.2 程序编写3.2.1 项目结构3.2.2 知识采集3.2.3 文档切分  3.2.4 文本向量化3.2.5 向量…

3D NAND闪存技术的架构和工艺集成概述(下)

另一种具有FG的3D NAND结构是HC-FG结构,如图8a所示。与DC-SF结构不同,水平信道堆叠在HC-FG架构中;然而,单元格没有被CG包围。因此,FG单元可以通过沟道优先工艺堆叠,类似于传统的2D平面灰阵列,并且可以以低成本实现3D结构。此外,如图8b所示,HC-FG结构与层选择晶体管(L…

3D NAND闪存技术的架构和工艺集成概述(上)

Architecture and Process Integration Overview of 3D NAND Flash Technologies 3D NAND闪存技术的架构和工艺集成概述 摘要:在过去的几十年里,NAND闪存一直是最成功的非易失性存储技术之一,由于其高可扩展性和可靠的开关特性,它在电子设备中得到了广泛的应用。为了克服平…

推荐4本书《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》,谢谢

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

【软件】解决奥林巴斯生物显微镜软件OlyVIA提示“不支持您使用的操作系统”安装中止的问题

【软件】解决奥林巴斯生物显微镜软件OlyVIA提示“不支持您使用的操作系统”安装中止的问题 零、问题 资源在文末 问题如下,从奥林巴斯生物显微镜软件官网下载地址:https://lifescience.evidentscientific.com.cn/zh/downloads/detail-iframe/?0[downloads][id]=847253920下载…

数据库如何实现事务的隔离

1.什么是事务? 简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL中,事务支持是在引擎层实现的。你现在知道,MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是MyISAM 被 Inno…

【CodeForces训练记录】Codeforces Round 998 (Div. 3)

训练情况赛后反思 div3 给罚时拉满了,C题 \(k-a_i=a_i\) 的情况错掉怒罚五发,D题成结论猜猜乐了,E题看错题了,以为是边一致,实则联通就行,又被并查集背刺了 A题 斐波那契第三位可以从第一二位算出来,也可以从第四五位算出来,两个答案取大值即可 #include <bits/stdc…

【clang-format】C++代码风格自用配置文件,大括号换行风格

使用方法: 新建一个文本文件,把下面代码复制进去,再把名字改成.clang-format,然后粘贴到项目文件夹的根目录下即可,效果如下图。当然,IDE需要设置启用clang-format。clang-format文件内容 # LLVM Google BasedOnStyle: Google Language: Cpp# 强制花括号遵循 Allman 风格…

【clang-format】vscodeC++调整代码格式时让public顶格

习惯了leetcode里面public顶格写,vscode格式化代码public不顶格看着难受。 如图:只要在.clang-format文件中添加下面这行就行 # 访问修饰符偏移 AccessModifierOffset: -4 # -4就是public顶格如果是用vscode插件配置的: "C_Cpp.clang_format_style": "{Base…

基于反射实现的零GC高效率Unity组件绑定

前言 我是狗猥,上一世,我使用传统方式绑定UI上的组件,却因xLua扩展代码太多撑爆丹田沦为废人,失去了争夺主程岗位的资格,最后在测试同学的讥笑声中饮恨西北。 再次睁开眼,我穿越回到了拼UI的那一天。重生归来,这一世我要设计一个船新的绑定方式,夺回本就属于我的一切!…