前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >透过Spring自动配置原理看Spring的扩展点

透过Spring自动配置原理看Spring的扩展点

作者头像
tunsuy
发布2022-10-27 10:09:22
2970
发布2022-10-27 10:09:22
举报

EnableAutoConfiguration注解

spring的自动配置就是得益于这个EnableAutoConfiguration注解:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

  /**
   * Exclude specific auto-configuration classes such that they will never be applied.
   * @return the classes to exclude
   */
  Class<?>[] exclude() default {};

  /**
   * Exclude specific auto-configuration class names such that they will never be
   * applied.
   * @return the class names to exclude
   * @since 1.3.0
   */
  String[] excludeName() default {};

}

该注解导入了一个类AutoConfigurationImportSelector,从该类的名字我们可以看到,它是一个selector类,那么就具有selector类的特性。

我们都知道,spring启动的时候会自动执行selector类的方法selectImports方法,从而将配置类自动注入容器中。下面就来看看是在哪里执行的这个方法

启动加载selector

spring的启动流程在之前的文章中已经讲过了,我们知道启动的时候会经过AbstractApplicationContext类的refresh方法 在该方法中有这样一句:

代码语言:javascript
复制
// Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
  PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

  // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
  // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
  if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
    beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
    beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
  }
}

最终会进入到ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法:

代码语言:javascript
复制
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();

    

  // Parse each @Configuration class
  ConfigurationClassParser parser = new ConfigurationClassParser(
      this.metadataReaderFactory, this.problemReporter, this.environment,
      this.resourceLoader, this.componentScanBeanNameGenerator, registry);

  Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
  do {
    parser.parse(candidates);
    parser.validate();

}

这里就是在处理标有Configuration注解的类,我们进入parser.parse(candidates);方法:

代码语言:javascript
复制
ConfigurationClassParser.java

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
    boolean checkForCircularImports) {

  if (importCandidates.isEmpty()) {
    return;
  }

  if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
  }
  else {
    this.importStack.push(configClass);
    try {
      for (SourceClass candidate : importCandidates) {
        if (candidate.isAssignable(ImportSelector.class)) {
          // Candidate class is an ImportSelector -> delegate to it to determine imports
          Class<?> candidateClass = candidate.loadClass();
          ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
              this.environment, this.resourceLoader, this.registry);
          Predicate<String> selectorFilter = selector.getExclusionFilter();
          if (selectorFilter != null) {
            exclusionFilter = exclusionFilter.or(selectorFilter);
          }
          if (selector instanceof DeferredImportSelector) {
            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
          }
          else {
            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
            processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
          }
        }
        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
          // Candidate class is an ImportBeanDefinitionRegistrar ->
          // delegate to it to register additional bean definitions
          Class<?> candidateClass = candidate.loadClass();
          ImportBeanDefinitionRegistrar registrar =
              ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                  this.environment, this.resourceLoader, this.registry);
          configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        }
        else {
          // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
          // process it as an @Configuration class
          this.importStack.registerImport(
              currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
          processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
        }
      }
    }
    catch (BeanDefinitionStoreException ex) {
      throw ex;
    }
    catch (Throwable ex) {
      throw new BeanDefinitionStoreException(
          "Failed to process import candidates for configuration class [" +
          configClass.getMetadata().getClassName() + "]", ex);
    }
    finally {
      this.importStack.pop();
    }
  }
}

我们看到有这样一句selector.selectImports(currentSourceClass.getMetadata())。看到这里,是不是就很清楚了,spring就是这样在启动的时候实现了自动导入配置类的。

下面我们再来看看自动配置类的内部逻辑

AutoConfigurationImportSelector

我们进入selector类,找到selectImports方法:

代码语言:javascript
复制
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  }
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

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

这里我们注意到这行代码List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);,这句代码就是获取所有jar中meta-inf/spring.factories文件中的类,下面具体来看看:

代码语言:javascript
复制
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  List<String> 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;
}

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  String factoryTypeName = factoryType.getName();
  return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  MultiValueMap<String, String> result = cache.get(classLoader);
  if (result != null) {
    return result;
  }

  try {
    Enumeration<URL> urls = (classLoader != null ?
        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    result = new LinkedMultiValueMap<>();
    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();
        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryTypeName, factoryImplementationName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
        FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
}

上面的代码中,我们注意到这样一个常量public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";,意思就是获取该文件中定义的类名,并作为列表返回。我们看一下这个文件的示例:

代码语言:javascript
复制
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
org.springframework.cloud.configuration.CompatibilityVerifierAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.cloud.configuration.CompatibilityNotMetFailureAnalyzer

好了,我们回到之前的getAutoConfigurationEntry方法,

代码语言:javascript
复制
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
  }
  AnnotationAttributes attributes = getAttributes(annotationMetadata);
  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  configurations = removeDuplicates(configurations);
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  checkExcludedClasses(configurations, exclusions);
  configurations.removeAll(exclusions);
  configurations = getConfigurationClassFilter().filter(configurations);
  fireAutoConfigurationImportEvents(configurations, exclusions);
  return new AutoConfigurationEntry(configurations, exclusions);
}

上面的代码很清晰:

  • 1、在配置文件中拿到类名列表
  • 2、对该列表去重
  • 3、根据注解属性,去掉指定排除的类
  • 4、根据自定义AutoConfigurationImportFilter类过滤
  • 5、回调AutoConfigurationImportListener监听的导入事件

AutoConfigurationImportFilter

在上面我们知道的,加载配置类的时候会根据条件过滤某些类,那么这里就来看下具体的实现:

代码语言:javascript
复制
configurations = getConfigurationClassFilter().filter(configurations);

private ConfigurationClassFilter getConfigurationClassFilter() {
  if (this.configurationClassFilter == null) {
    List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
    for (AutoConfigurationImportFilter filter : filters) {
      invokeAwareMethods(filter);
    }
    this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
  }
  return this.configurationClassFilter;
}

从上面的代码中,我们看到,这里的listener是怎么来的呢?

代码语言:javascript
复制
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
  return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

看到这个代码里面的调用,又回到了刚刚讲解获取自动配置类的地方,只是这里传入的Class<T> factoryType变成了AutoConfigurationImportFilter

总结一下,也就是说:filter也是从META-INF/spring.factories配置文件里面获取出来的,示例如下:

代码语言:javascript
复制
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
扩展点一

看到这里我们也可以总结出spring其中的一个扩展点:

如果业务中需要根据条件动态的控制某个类的导入,则可以实现AutoConfigurationImportFilter接口,并且实现方法match,然后放入META-INF/spring.factories配置文件中Key为:org.springframework.context.ApplicationContextInitializer的value中。

spring在导入配置类的时候,会自动执行这个扩展方法。

注:spring另外封装了一个类ConfigurationClassFilter来统一处理

AutoConfigurationImportListener

在上面我们知道的,加载配置类的时候会回调AutoConfigurationImportListener监听的导入事件,那么这里就来看下具体的实现:

代码语言:javascript
复制
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
  List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
  if (!listeners.isEmpty()) {
    AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
    for (AutoConfigurationImportListener listener : listeners) {
      invokeAwareMethods(listener);
      listener.onAutoConfigurationImportEvent(event);
    }
  }
}

从上面的代码中,我们看到,这里的listener是怎么来的呢?

代码语言:javascript
复制
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
  return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}

看到这个代码里面的调用,又回到了刚刚讲解获取自动配置类的地方,只是这里传入的Class<T> factoryType变成了AutoConfigurationImportListener

总结一下,也就是说: listener也是从META-INF/spring.factories配置文件里面获取出来的,示例如下:

代码语言:javascript
复制
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
代码语言:javascript
复制
扩展点二

看到这里我们也可以总结出spring其中的一个扩展点:

如果业务中需要监控某个类的导入,则可以实现AutoConfigurationImportListener接口,并且实现方法onAutoConfigurationImportEvent,然后放入META-INF/spring.factories配置文件中Key为:org.springframework.context.ApplicationContextInitializer的value中。

spring在导入配置类的时候,会自动执行这个扩展方法。

spring.factories

从上面我们知道了,spring就是通过spring.factories文件让外部实现自定义扩展的,其实,该文件还可以定义其他一些classtype的key,

比如在springboot的启动中,有如下代码:

代码语言:javascript
复制
public static void main(String[] args) {
  SpringApplication.run(xxx.class, args);
}
  
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  this.resourceLoader = resourceLoader;
  Assert.notNull(primarySources, "PrimarySources must not be null");
  this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  this.mainApplicationClass = deduceMainApplicationClass();
}

可以看出,springboot在启动的时候会加载spring.factories中ApplicationContextInitializerApplicationListener为key的类

看到这里我们也可以总结出spring的一个扩展点:

扩展点三

如果业务需要在启动的时候自定义下容器的初始化动作,则可以实现ApplicationContextInitializer接口,并且实现方法initialize,然后放入META-INF/spring.factories配置文件中Key为:org.springframework.context.ApplicationContextInitializer的value中。

看到这里,我们是不是可以想象下,我们是不是可以在spring.factories中自定义key-value,然后通过库方法自行解析,达到任意扩展的效果呢?答案是可以的。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-23,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 有文化的技术人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • EnableAutoConfiguration注解
  • 启动加载selector
  • AutoConfigurationImportSelector
  • AutoConfigurationImportFilter
    • 扩展点一
    • AutoConfigurationImportListener
      • 扩展点二
      • spring.factories
        • 扩展点三
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com