当前位置 : 主页 > 编程语言 > java >

SpringBoot Starter依赖原理与实例详解

来源:互联网 收集:自由互联 发布时间:2023-01-30
目录 1 Starter 2 了解 spring.factories机制 2.1 不同包路径下的依赖注入 2.2 spring.factories 机制 3 spring.factories 机制的实现源码分析 4 程序运行入口run() 1 Starter 在开发 SpringBoot 项目的时候,我
目录
  • 1 Starter
  • 2 了解 spring.factories机制
    • 2.1 不同包路径下的依赖注入
    • 2.2 spring.factories 机制
  • 3 spring.factories 机制的实现源码分析
    • 4 程序运行入口run()

      1 Starter

      在开发 SpringBoot 项目的时候,我们常常通过 Maven 导入自动各种依赖,其中很多依赖都是以 xxx-starter 命名的。

      像这种 starter 依赖是怎么工作的呢?

      2 了解 spring.factories机制

      导入一个依赖,我们就可以调用包内的公共类,这是因为公共类可以被异包调用。很多时候我们添加依赖它会自动往我们的主程序注入一些对象或者监听器,这个是怎么做到的?

      2.1 不同包路径下的依赖注入

      SpringBoot 默认只扫描启动类所在目录里面的对象

      而我们导入的依赖是在另外一个包里,SpringBoot 是扫描不到的!

      如何让主项目注入(加载)异包对象呢?通常有两种方法:

      • 在启动类上加上@SpringBootApplication注解,配置scanBasePackages属性,指定扫描路径。
      • resources/META-INF目录下创建spring.factories配置文件,在里面配置需要加载的类

      2.2 spring.factories 机制

      spring.factories机制是springboot的核心基础之一,这可以描述为一种 可插拔结构,模仿自java中的SPI扩展机制。

      spring.factories 实现例子

      1.在任意一个项目中新建一个starter模块(springboot项目)

      导入 springboot 的自动配置依赖,这里我们主要用到它的@Configuration@Bean注解和ApplicationListener监听器接口

      <!-- SpringBoot 自动配置 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-autoconfigure</artifactId>
      </dependency>
      

      2.随便创建一个bean

      public class User {
          private String name;
          public void setName(String name) { this.name = name; }
          public String getName() { return name; }
      }
      

      3.创建一个初始化监听器

      一般的starter会在容器启动时做一些初始化的操作,这里作为演示只打印一句话。

      public class ApplicationInitialize implements ApplicationListener {
          @Override
          public void onApplicationEvent(ApplicationEvent applicationEvent) {
              System.out.println("应用初始化");
          }
      }
      

      4.创建配置类

      这个配置类就是主项目注入starter模块依赖的入口,当它扫描到这个配置类的时候就会加载里面的Bean对象

      @Configuration
      public class StarterGenericConfig {
          @Bean
          public User getUser() {
              User user = new User();
              user.setName("我来自starter模块");
              return user;
          }
          @Bean
          public ApplicationInitialize getAppli() {
              return new ApplicationInitialize();
          }
      }
      

      5.创建spring.factories配置文件

      配置类有了,但是因为和主项目不同包启动类它扫描不到,这时我们就要通过spring.factories机制让它能扫描到这个配置类,完成依赖注入。

      先在resource资源目录下创建META-INF文件夹,然后创建一个名为spring.factories的文件

      内容如下:

      org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

      这里需要配置StarterGenericConfig配置类的全限定名。

      把这个项目导入主项目(添加到主项目的pom依赖中),运行主项目,看看是否注入成功

      这就是starter依赖注入的基本思路,实际可能复杂得多,需要继续摸索。

      3 spring.factories 机制的实现源码分析

      springframework 框架中有这样一个类

      package org.springframework.core.io.support;
      public final class SpringFactoriesLoader {
      	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
          private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
          private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
      	...
      	/** SpringFactories 
      	  * 静态方法, 加载spring.factories文件
      	  * */
      	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
             		...
                  try {
                  	// 通过类加载器加载资源目录下的"META-INF/spring.factories"文件
                      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()) {
                              Entry<?, ?> entry = (Entry)var6.next();
                              String factoryTypeName = ((String)entry.getKey()).trim();
                              String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                              int var10 = var9.length;
                              for(int var11 = 0; var11 < var10; ++var11) {
                                  String factoryImplementationName = var9[var11];
                                  result.add(factoryTypeName, factoryImplementationName.trim());
                              }
                          }
                      }
                      cache.put(classLoader, result);
                      return result;
                  } catch (IOException var13) {
                      throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
                  }
              }
          }
       	...
      }
      

      SpringFactoriesLoaderSpring容器初始化时会加载的一个类,而它的静态方法loadSpringFactories()里会用类加载器去加载资源文件resourece/META-INF/spring.factories,然后读取里面的配置参数(应该都是待加载Bean类的映射数据),用集合封装返回Spring容器,后面应该就是Spring容器加载对应的Bean类。

      4 程序运行入口run()

      前面知道了Spring容器会加载加载资源文件resourece/META-INF/spring.factories然后加载里面对应的类,那为什么对应的keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration?

      先说结论:Spring容器初始化会加载org.springframework.boot.autoconfigure.EnableAutoConfiguration这个类,完了还会去扫描resourece/META-INF/spring.factories加载里面的Bean类,配置文件是键值对形式的,那keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration是因为这个是一个注解,本身就是为了注入拓展类用的,它会在容器初始化或刷新的适当时机注入对应的类。因为扫描不到异包配置类上的@Configuration注解,所以创建了一个@EnableAutoConfiguration注解配合spring.factories配置文件的形式来注入配置类

      public ConfigurableApplicationContext run(String... args) {
              StopWatch stopWatch = new StopWatch(); // 创建stopWatch对象
              stopWatch.start(); // 开始计算时间
              ConfigurableApplicationContext context = null;
              Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
              this.configureHeadlessProperty(); // 是否使用模拟输入输出设备(默认是,因为服务器不一定有鼠标键盘显示器)
              SpringApplicationRunListeners listeners = this.getRunListeners(args); // 获取并启动监听器
              listeners.starting(); // 获取的监听器为 Event PublishingRunListener,监听并发布启动事件
              Collection exceptionReporters;
              try {
                  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                  // 准备应用环境
                  ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
                  this.configureIgnoreBeanInfo(environment);
                  Banner printedBanner = this.printBanner(environment); // 打印 banner
                  context = this.createApplicationContext(); 创建容器
                  // 加载 SpringFactories 实例(返回的是实例加载的记录、报告)
                  exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
                  // 准备上下文环境
                  this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                  this.refreshContext(context); // 刷新容器
                  this.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) {
                 ...
              }
              ...
          }

      里面有个创建应用容器的方法createApplicationContext(),深入进去发现他是根据webApplicationType类型去决定创建那种容器,而webApplicationType类型在SpringApplication初始化的时候指定。

          protected ConfigurableApplicationContext createApplicationContext() {
              Class<?> contextClass = this.applicationContextClass;
              if (contextClass == null) {
                  try {
                      switch(this.webApplicationType) {
                      case SERVLET:
                          contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                          break;
                      case REACTIVE:
                          contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                          break;
                      default:
                          contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                      }
                  } catch (ClassNotFoundException var3) {
                      throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
                  }
              }
      		// 反射创建容器实例
              return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
          }
      

      这里一共有三种类型的容器

      • SERVLET 类型创建 AnnotationConfigServletWebServerApplicationContextServlet容器)
      • REACTIVE类型创建AnnotationConfigReactiveWebServerApplicationContextReactive容器)
      • 默认创建AnnotationConfigApplicationContextApplication容器)

      SpringApplication的构造器中对webApplicationType类型进行了初始化,默认返回SERVLET 类型。

          public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
              ...
              this.webApplicationType = WebApplicationType.deduceFromClasspath();
              ...
          }
      

      也就是说默认创建AnnotationConfigServletWebServerApplicationContextServlet容器)

      在上面入口方法run()方法中,有一个refreshContext()方式,这个刷新容器的方法里面

      跟踪这个refreshContext()底层是一个refresh()方法,三种容器都分别实现了这个方法

      这里着重看ServletWebServerApplicationContext.refresh()

          public final void refresh() throws BeansException, IllegalStateException {
              try { super.refresh(); } catch (RuntimeException var3) { ... }
          }
      

      发现它调用的是父类AbstractApplicationContextrefresh()函数

          public void refresh() throws BeansException, IllegalStateException {
              synchronized(this.startupShutdownMonitor) { // 上锁,防止并发
                  this.prepareRefresh();                  // 刷新准备工作,记录开始时间,校验配置文件
                  ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 获取Bean工厂
                  this.prepareBeanFactory(beanFactory);   // Bean工厂准备工作,不详谈
                  try {
                      this.postProcessBeanFactory(beanFactory);
                      this.invokeBeanFactoryPostProcessors(beanFactory); // Spring拓展点之一
                      ...
                  } catch (BeansException var9) {
                      ...
                  } finally {
                      this.resetCommonCaches();
                  }
              }
          }
      

      重点在invokeBeanFactoryPostProcessors(beanFactory)方法上,这是SpringBoot实现Spring拓展的关键节点,这个方法执行时会调用实现了BeanFactoryPostProcessors接口的实现类的postProcessBeanFactory(factory)方法

      (也会调用BeanDefinitionRegistryPostProcessor接口的各个实现类的postProcessBeanDefinitionRegistry(registry)方法)

      进入invokeBeanFactoryPostProcessors(beanFactory)方法

          public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
         			...
                  currentRegistryProcessors = new ArrayList();
                  // 获取所有 BeanDefinitionRegistryPostProcessor 接口实现类的全限定名集合
                  postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); 
                  String[] var16 = postProcessorNames;
                  var9 = postProcessorNames.length;
                  int var10;
                  String ppName;
                  for(var10 = 0; var10 < var9; ++var10) {
                      ppName = var16[var10];
                      if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                      	// 根据 Bean 名获取 Bean 对象放入 currentRegistryProcessors
                          currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                          processedBeans.add(ppName);
                      }
                  }
                  sortPostProcessors(currentRegistryProcessors, beanFactory); // 排序,暂时没看,不知道排啥
                  registryProcessors.addAll(currentRegistryProcessors);
                  // 调用 Bean 定义注册后处理器
                  invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
                  currentRegistryProcessors.clear();
      			...
      	}
      

      每一个Bean类的定义注册是在Spring容器中完成的,在上面invokeBeanFactoryPostProcessors()方法中,通过 Bean 工厂获取 了所有BeanDefinitionRegistryPostProcessor接口的实现类名,然后再通过 invokeBeanDefinitionRegistryPostProcessors()方法调用所有实现类的postProcessBeanDefinitionRegistry()方法去做 Bean 类注册后的相关处理动作。

      BeanDefinitionRegistryPostProcessor接口:

      public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
      	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
      }
      

      知道这个有什么用呢?前面我们知道了Springboot容器中的AnnotationConfigServletWebServerApplicationContextServlet容器)是通过反射获取AnnotationConfigServletWebServerApplicationContext的构造器创建实例的,所以我们看看AnnotationConfigServletWebServerApplicationContext的构造器长什么样儿。

          public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
              super(beanFactory);
              this.annotatedClasses = new LinkedHashSet();
              this.reader = new AnnotatedBeanDefinitionReader(this); // 注解 Bean 定义读取器
              this.scanner = new ClassPathBeanDefinitionScanner(this);
          }
      

      进入AnnotatedBeanDefinitionReader看它的构造器

          public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
              ...
              // 通过 AnnotationConfigUtils 工具注册 注解配置处理器
              AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
          }
      

      再进入AnnotationConfigUtils 工具的registerAnnotationConfigProcessors()看看它是如何注册的

      public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
      	...
      	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet(8); // 存储 BeanDefinitionHolder 对象的集合
          RootBeanDefinition def;
      	if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor")) {
      		// 创建一个 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象
      		def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
              def.setSource(source);
              // 把 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象装入一个 BeanDefinitionHolder (容器)
              // 并映射名字为 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
              beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"));
          }
      	...
      }
      

      在它的registerAnnotationConfigProcessors()方法中看到了它用注册后处理器registerPostProcessor去注册org.springframework.context.annotation.internalConfigurationAnnotationProcessorBeanDefinition(Bean定义描述对象)和BeanDefinitionHolder(Bean定义描述对象容器),然后返回这个BeanDefinitionHolder(Bean定义描述对象容器)存储到beanDefs(Bean定义描述对象容器集合)里面。

      到这里就是说明在初始化AnnotationConfigServletWebServerApplicationContextServlet容器)时,会用org.springframework.context.annotation.internalConfigurationAnnotationProcessor这个名字注册ConfigurationClassPostProcessor这个 Bean对象,然后就能根据它的BeanDefinitionHolder(Bean定义描述对象容器)去创建ConfigurationClassPostProcessor对象。

      现在问题就来到了ConfigurationAnnotationProcessor对象身上了,为啥要创建它?因为它就是加载spring.factories配置文件的关键。

      进入它的postProcessBeanDefinitionRegistry()方法

          public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
              int registryId = System.identityHashCode(registry);
              if (this.registriesPostProcessed.contains(registryId)) {       // 判断是否有对应的注册记录
                  throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
              } else if (this.factoriesPostProcessed.contains(registryId)) { // 是否有对应的 Bean 工厂
                  throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);
              } else { // 都没有,说明这个 BeanDefinition 没有注册加载过
                  this.registriesPostProcessed.add(registryId); // 添加注册记录
                  this.processConfigBeanDefinitions(registry);  // 处理这个 BeanDefinition 的配置
              }
          }
      

      深入processConfigBeanDefinitions()看它怎么处理这个 BeanDefinition 的配置

          public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
              List<BeanDefinitionHolder> configCandidates = new ArrayList(); // 待处理配置集合
              String[] candidateNames = registry.getBeanDefinitionNames();
              String[] var4 = candidateNames;
              int var5 = candidateNames.length;
              for(int var6 = 0; var6 < var5; ++var6) {
                  String beanName = var4[var6];
                  BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 根据名称获取 BeanDefinition
                  // 判断这个 BeanDefinition 的配置属性是不是空
                  if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                      if (this.logger.isDebugEnabled()) {
                      	// 如果不是空就说明这个 BeanDefinition 已经被当作配置类处理过了
                          this.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));
                  }
              }
      		// 指令排序相关,不深究
              if (!configCandidates.isEmpty()) {
                  configCandidates.sort((bd1, bd2) -> {
                      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                      int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                      return Integer.compare(i1, i2);
                  });
                  ...
                  // 根据环境创建了一个配置类解析器
                  ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
                  Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
                  HashSet alreadyParsed = new HashSet(configCandidates.size());
      
                  do {
                      parser.parse(candidates); // 解析配置类
                      parser.validate();
                      Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
                      configClasses.removeAll(alreadyParsed);
                      if (this.reader == null) {
                          this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
                      }
      				...
                  } while(!candidates.isEmpty())
                  ...
              }
          }
      

      进入ConfigurationClassParser.parse()方法,看看它怎么解析配置类

      	public void parse(Set<BeanDefinitionHolder> configCandidates) {
      		for (BeanDefinitionHolder holder : configCandidates) {
      			BeanDefinition bd = holder.getBeanDefinition();
      			try { // 对 BeanDefinition 做解析操作
      				if (bd instanceof AnnotatedBeanDefinition) {
      					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
      				}
      				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
      					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
      				}
      				else {
      					parse(bd.getBeanClassName(), holder.getBeanName());
      				}
      			}
      			catch (BeanDefinitionStoreException ex) {
      				throw ex;
      			}
      			catch (Throwable ex) {
      				throw new BeanDefinitionStoreException(
      						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
      			}
      		}
      		// 延迟导入选择器
      		this.deferredImportSelectorHandler.process();
      	}
      

      解析发现不了什么线索,进入this.deferredImportSelectorHandler.process()延迟导入选择处理器看看

      层层追踪:

      this.deferredImportSelectorHandler.process()

      DeferredImportSelectorGroupingHandler.processGroupImports()

      grouping.getImports()

      grouping.getImports()

      this.group.selectImports()

      追踪到public interface ImportSelector {...}接口

      在找到它的实现类AutoConfigurationImportSelector

      在实现类AutoConfigurationImportSelector里层层追踪

      selectImports()

      getAutoConfigurationEntry()

      getCandidateConfigurations()

      getSpringFactoriesLoaderFactoryClass()

      最后追踪到 getSpringFactoriesLoaderFactoryClass()方法

          protected Class<?> getSpringFactoriesLoaderFactoryClass() {
              return EnableAutoConfiguration.class;
          }
      

      里面返回的是一个EnableAutoConfiguration.class类,这个类就是我们在spring.factories配置文件里面配置的org.springframework.boot.autoconfigure.EnableAutoConfiguration

      org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

      到此这篇关于SpringBoot Starter依赖原理与实例详解的文章就介绍到这了,更多相关SpringBoot Starter依赖内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!

      上一篇:MyBatis映射器mapper快速入门教程
      下一篇:没有了
      网友评论