Spring Boot 自动装配原理

野性酷女 2023-09-25 18:32 168阅读 0赞

本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

Github地址:https://github.com/Tyson0314/Java-learning


首先,先看SpringBoot的主配置类:

  1. @SpringBootApplication
  2. public class StartEurekaApplication
  3. {
  4. public static void main(String[] args)
  5. {
  6. SpringApplication.run(StartEurekaApplication.class, args);
  7. }
  8. }

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

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(excludeFilters = {
  8. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  9. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  10. public @interface SpringBootApplication {
  11. }

首先我们先来看 @SpringBootConfiguration:

  1. @Target({
  2. ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Configuration
  6. public @interface SpringBootConfiguration {
  7. }

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

那@Configuration还有一个作用就是把该类变成一个配置类,不需要额外的XML进行配置。所以@SpringBootConfiguration就相当于@Configuration。进入@Configuration,发现@Configuration核心是@Component,说明Spring的配置类也是Spring的一个组件。

  1. @Target({
  2. ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Component
  6. public @interface Configuration {
  7. @AliasFor(
  8. annotation = Component.class
  9. )
  10. String value() default "";
  11. }

继续来看下一个@EnableAutoConfiguration,这个注解是开启自动配置的功能。

  1. @Target({
  2. ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Inherited
  6. @AutoConfigurationPackage
  7. @Import({
  8. AutoConfigurationImportSelector.class})
  9. public @interface EnableAutoConfiguration {
  10. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  11. Class<?>[] exclude() default {
  12. };
  13. String[] excludeName() default {
  14. };
  15. }

可以看到它是由 @AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)这两个而组成的,我们先说@AutoConfigurationPackage,他是说:让包中的类以及子包中的类能够被自动扫描到spring容器中。

  1. @Target({
  2. ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Inherited
  6. @Import({
  7. Registrar.class})
  8. public @interface AutoConfigurationPackage {
  9. }

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

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2. Registrar() {
  3. }
  4. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  5. AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
  6. }
  7. public Set<Object> determineImports(AnnotationMetadata metadata) {
  8. return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
  9. }
  10. }

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VZhu6uF-1679495766815)(http://img.topjavaer.cn/img/springboot自动配置1.png)\]那metadata是什么呢,可以看到是标注在@SpringBootApplication注解上的DemosbApplication,也就是我们的主配置类Application:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXxWBahj-1679495766824)(http://img.topjavaer.cn/img/springboot自动配置2.png)\]其实就是将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器。因此我们要把DemoApplication放在项目的最高级中(最外层目录)。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RIcekJCE-1679495766826)(http://img.topjavaer.cn/img/springboot自动配置3.png)\]可以从图中看出AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector,ImportSelector有一个方法为:selectImports。将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  2. if (!this.isEnabled(annotationMetadata)) {
  3. return NO_IMPORTS;
  4. } else {
  5. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  6. AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry =
  7. this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
  8. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  9. }
  10. }

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bBFMZgsY-1679495766834)(http://img.topjavaer.cn/img/springboot自动配置4.png)\]有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。那是如何获取到这些配置类的呢,看看下面这个方法:

  1. protected AutoConfigurationImportSelector.AutoConfigurationEntry
  2. getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
  3. if (!this.isEnabled(annotationMetadata)) {
  4. return EMPTY_ENTRY;
  5. } else {
  6. AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
  7. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  8. configurations = this.removeDuplicates(configurations);
  9. Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
  10. this.checkExcludedClasses(configurations, exclusions);
  11. configurations.removeAll(exclusions);
  12. configurations = this.filter(configurations, autoConfigurationMetadata);
  13. this.fireAutoConfigurationImportEvents(configurations, exclusions);
  14. return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
  15. }
  16. }

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

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  2. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  3. Assert.notEmpty(configurations,
  4. "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
  5. return configurations;
  6. }
  7. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  8. String factoryClassName = factoryClass.getName();
  9. return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  10. }

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

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2. MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
  3. if (result != null) {
  4. return result;
  5. } else {
  6. try {
  7. Enumeration<URL> urls = classLoader !=
  8. null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  9. LinkedMultiValueMap result = new LinkedMultiValueMap();
  10. while(urls.hasMoreElements()) {
  11. URL url = (URL)urls.nextElement();
  12. UrlResource resource = new UrlResource(url);
  13. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  14. Iterator var6 = properties.entrySet().iterator();
  15. while(var6.hasNext()) {
  16. Map.Entry<?, ?> entry = (Map.Entry)var6.next();
  17. String factoryClassName = ((String)entry.getKey()).trim();
  18. String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
  19. int var10 = var9.length;
  20. for(int var11 = 0; var11 < var10; ++var11) {
  21. String factoryName = var9[var11];
  22. result.add(factoryClassName, factoryName.trim());
  23. }
  24. }
  25. }
  26. cache.put(classLoader, result);
  27. return result;
  28. } catch (IOException var13) {
  29. throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
  30. }
  31. }
  32. }

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9hzmtwFe-1679495766836)(http://img.topjavaer.cn/img/springboot自动配置5.png)\]接下来看@ComponentScan注解,@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }),这个注解就是扫描包,然后放入spring容器。

  1. @ComponentScan(excludeFilters = {
  2. @Filter(type = FilterType.CUSTOM,classes = {
  3. TypeExcludeFilter.class}),
  4. @Filter(type = FilterType.CUSTOM,classes = {
  5. AutoConfigurationExcludeFilter.class})})
  6. public @interface SpringBootApplication {
  7. }

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

接下来继续看run方法:

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

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

  1. public ConfigurableApplicationContext run(String... args) {
  2. //计时器
  3. StopWatch stopWatch = new StopWatch();
  4. stopWatch.start();
  5. ConfigurableApplicationContext context = null;
  6. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
  7. this.configureHeadlessProperty();
  8. //监听器
  9. SpringApplicationRunListeners listeners = this.getRunListeners(args);
  10. listeners.starting();
  11. Collection exceptionReporters;
  12. try {
  13. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  14. ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
  15. this.configureIgnoreBeanInfo(environment);
  16. Banner printedBanner = this.printBanner(environment);
  17. //准备上下文
  18. context = this.createApplicationContext();
  19. exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{
  20. ConfigurableApplicationContext.class}, context);
  21. //预刷新context
  22. this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  23. //刷新context
  24. this.refreshContext(context);
  25. //刷新之后的context
  26. this.afterRefresh(context, applicationArguments);
  27. stopWatch.stop();
  28. if (this.logStartupInfo) {
  29. (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
  30. }
  31. listeners.started(context);
  32. this.callRunners(context, applicationArguments);
  33. } catch (Throwable var10) {
  34. this.handleRunFailure(context, var10, exceptionReporters, listeners);
  35. throw new IllegalStateException(var10);
  36. }
  37. try {
  38. listeners.running(context);
  39. return context;
  40. } catch (Throwable var9) {
  41. this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
  42. throw new IllegalStateException(var9);
  43. }
  44. }

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

  1. private void refreshContext(ConfigurableApplicationContext context) {
  2. refresh(context);
  3. if (this.registerShutdownHook) {
  4. try {
  5. context.registerShutdownHook();
  6. }
  7. catch (AccessControlException ex) {
  8. // Not allowed in some environments.
  9. }
  10. }
  11. }

我们继续点进refresh(context);

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

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

  1. public void refresh() throws BeansException, IllegalStateException {
  2. synchronized (this.startupShutdownMonitor) {
  3. // Prepare this context for refreshing.
  4. prepareRefresh();
  5. // Tell the subclass to refresh the internal bean factory.
  6. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  7. // Prepare the bean factory for use in this context.
  8. prepareBeanFactory(beanFactory);
  9. try {
  10. // Allows post-processing of the bean factory in context subclasses.
  11. postProcessBeanFactory(beanFactory);
  12. // Invoke factory processors registered as beans in the context.
  13. invokeBeanFactoryPostProcessors(beanFactory);
  14. // Register bean processors that intercept bean creation.
  15. registerBeanPostProcessors(beanFactory);
  16. // Initialize message source for this context.
  17. initMessageSource();
  18. // Initialize event multicaster for this context.
  19. initApplicationEventMulticaster();
  20. // Initialize other special beans in specific context subclasses.
  21. onRefresh();
  22. // Check for listener beans and register them.
  23. registerListeners();
  24. // Instantiate all remaining (non-lazy-init) singletons.
  25. finishBeanFactoryInitialization(beanFactory);
  26. // Last step: publish corresponding event.
  27. finishRefresh();
  28. }catch (BeansException ex) {
  29. if (logger.isWarnEnabled()) {
  30. logger.warn("Exception encountered during context initialization - " +
  31. "cancelling refresh attempt: " + ex);
  32. }
  33. // Destroy already created singletons to avoid dangling resources.
  34. destroyBeans();
  35. // Reset 'active' flag.
  36. cancelRefresh(ex);
  37. // Propagate exception to caller.
  38. throw ex;
  39. }finally {
  40. // Reset common introspection caches in Spring's core, since we
  41. // might not ever need metadata for singleton beans anymore...
  42. resetCommonCaches();
  43. }
  44. }
  45. }

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIjdSFfV-1679495766839)(http://img.topjavaer.cn/img/springboot自动配置6.png)\]比如Tomcat跟web有关,我们可以看到有个ServletWebServerApplicationContext:

  1. @Override
  2. protected void onRefresh() {
  3. super.onRefresh();
  4. try {
  5. createWebServer();
  6. }
  7. catch (Throwable ex) {
  8. throw new ApplicationContextException("Unable to start web server", ex);
  9. }
  10. }

可以看到有一个createWebServer();方法他是创建web容器的,而Tomcat不就是web容器,那是如何创建的呢,我们继续看:

  1. private void createWebServer() {
  2. WebServer webServer = this.webServer;
  3. ServletContext servletContext = getServletContext();
  4. if (webServer == null && servletContext == null) {
  5. ServletWebServerFactory factory = getWebServerFactory();
  6. this.webServer = factory.getWebServer(getSelfInitializer());
  7. }
  8. else if (servletContext != null) {
  9. try {
  10. getSelfInitializer().onStartup(servletContext);
  11. }
  12. catch (ServletException ex) {
  13. throw new ApplicationContextException("Cannot initialize servlet context",
  14. ex);
  15. }
  16. }
  17. initPropertySources();
  18. }

factory.getWebServer(getSelfInitializer());他是通过工厂的方式创建的。

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vbapQdbP-1679495766841)(http://img.topjavaer.cn/img/springboot自动配置7.png)\]

我们看到还有Jetty,那我们来看TomcatServletWebServerFactory:

  1. @Override
  2. public WebServer getWebServer(ServletContextInitializer... initializers) {
  3. Tomcat tomcat = new Tomcat();
  4. File baseDir = (this.baseDirectory != null) ? this.baseDirectory
  5. : createTempDir("tomcat");
  6. tomcat.setBaseDir(baseDir.getAbsolutePath());
  7. Connector connector = new Connector(this.protocol);
  8. tomcat.getService().addConnector(connector);
  9. customizeConnector(connector);
  10. tomcat.setConnector(connector);
  11. tomcat.getHost().setAutoDeploy(false);
  12. configureEngine(tomcat.getEngine());
  13. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  14. tomcat.getService().addConnector(additionalConnector);
  15. }
  16. prepareContext(tomcat.getHost(), initializers);
  17. return getTomcatWebServer(tomcat);
  18. }

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

如果不明白的话, 我们在用另一种方式来理解下,大家要应该都知道stater举点例子。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-freemarker</artifactId>
  8. </dependency>

首先自定义一个stater。

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.1.4.RELEASE</version>
  5. <relativePath/>
  6. </parent>
  7. <groupId>com.zgw</groupId>
  8. <artifactId>gw-spring-boot-starter</artifactId>
  9. <version>1.0-SNAPSHOT</version>
  10. <dependencies>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-autoconfigure</artifactId>
  14. </dependency>
  15. </dependencies>

我们先来看maven配置写入版本号,如果自定义一个stater的话必须依赖spring-boot-autoconfigure这个包,我们先看下项目目录。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uSiL1SK9-1679495766844)(http://img.topjavaer.cn/img/springboot自动配置8.png)\]img

  1. public class GwServiceImpl implements GwService{
  2. @Autowired
  3. GwProperties properties;
  4. @Override
  5. public void Hello()
  6. {
  7. String name=properties.getName();
  8. System.out.println(name+"说:你们好啊");
  9. }
  10. }

我们做的就是通过配置文件来定制name这个是具体实现。

  1. @Component
  2. @ConfigurationProperties(prefix = "spring.gwname")
  3. public class GwProperties {
  4. String name="zgw";
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. }

这个类可以通过@ConfigurationProperties读取配置文件。

  1. @Configuration
  2. @ConditionalOnClass(GwService.class) //扫描类
  3. @EnableConfigurationProperties(GwProperties.class) //让配置类生效
  4. public class GwAutoConfiguration {
  5. /**
  6. * 功能描述 托管给spring
  7. * @author zgw
  8. * @return
  9. */
  10. @Bean
  11. @ConditionalOnMissingBean
  12. public GwService gwService()
  13. {
  14. return new GwServiceImpl();
  15. }
  16. }

这个为配置类,为什么这么写因为,spring-boot的stater都是这么写的,我们可以参照他仿写stater,以达到自动配置的目的,然后我们在通过spring.factories也来进行配置。

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gw.GwAutoConfiguration

然后这样一个简单的stater就完成了,然后可以进行maven的打包,在其他项目引入就可以使用。


最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~

ed32b78776159495bfa0ffe6fd42124c.png

8afe192f83d113c137cd287482e9677c.png

Github地址:https://github.com/Tyson0314/java-books

发表评论

表情:
评论列表 (有 0 条评论,168人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring Boot 自动装配原理

    > 本文已经收录到Github仓库,该仓库包含计算机基础、Java核心知识点、多线程、JVM、常见框架、分布式、微服务、设计模式、架构等核心知识点,欢迎star~ > > G