前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot自动装配原理

springboot自动装配原理

作者头像
叔牙
修改2022-01-20 14:17:53
1.1K0
修改2022-01-20 14:17:53
举报

使用springboot的过程中我们发现,框架层帮我们自动注册了很多能力,类似的基础配置、集成能力支撑等,我们之前有分析过springboot应用启动的时候自动扫描注册的bean要么是启动门面类路径及子路径下的信息,要么就是用户指定的路径信息,springboot所支持的部件和框架层配置肯定不在我们指定的路径下,按照这个思路应用启动时并不会把相应的配置实例化到上下文中,而我们编写的所有业务代码甚至扩展框架信息都是基于框架能力的支撑,没有这些配置和基础组件是不可能实现的,我们本篇就围绕springboot自动装配展开分析。

一、能力使用

基于springboot自动装配能力,我们能够便捷的集成和使用springboot所支持的很多能力。 1.数据库连接 在应用模块中引入数据库相关依赖,在属性文件中简单配置就能直接使用数据库连接以及衍生能力:

代码语言:javascript
复制
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://host:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=true
spring.datasource.username=aaa
spring.datasource.password=bbb

2.redis缓存 引入redis依赖,属性文件中添加连接配置:

代码语言:javascript
复制
#redis
spring.redis.database=0
spring.redis.host=host
spring.redis.port=port
spring.redis.password=password

这样框架会自动创建redis相关配置并且注入RedisTemplate供业务编码使用。 3.内嵌web容器 只需要引入spring-boot-starter-web依赖,框架就会自动集成默认的tomcat容器。

还有很多其他能力都可以简单的通过引入依赖和配置来集成,这里就不一一列举了。

二、原理分析

大部分人看到这里会思考一个问题,为什么通过简单的配置就能集成某个能力了,框架层是如何设计和实现的呢? 接下来我们就从源码维度详细分析一下其实现原理。基于springboot构建的应用,启动类上一般如下:

代码语言:javascript
复制
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

本篇要分析的自动装配能力就要从SpringBootApplication注解作为切入点,看一下代码:

代码语言:javascript
复制
@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是一个组合注解,拥有SpringBootConfiguration、ComponentScan和EnableAutoConfiguration注解的能力,其他两个不多说,自动装配就是有EnableAutoConfiguration注解来支持实现。

代码语言:javascript
复制
@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能力,并导入了AutoConfigurationImportSelector配置,有两个属性,都是用于在处理自动装配时排出指定的配置类。 先看一下AutoConfigurationPackage做了什么:

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

仅仅导入了一个配置类AutoConfigurationPackages.Registrar,该配置类是ImportBeanDefinitionRegistrar类型,用于将启动类指定的扫描包路径转化成BasePackages存储,供后续使用:

代码语言:javascript
复制
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
    register(registry, new PackageImport(metadata).getPackageName());
  }
  @Override
  public Set<Object> determineImports(AnnotationMetadata metadata) {
    return Collections.singleton(new PackageImport(metadata));
  }
}

继续看EnableAutoConfiguration导入的AutoConfigurationImportSelector:

它是一个ImportSelector,在ConfigurationClassPostProcessor执行逻辑的时候根据需要导入对应的配置类,看一下selectImports实现:

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

先从如下路径加载元数据信息:

代码语言:javascript
复制
META-INF/spring-autoconfigure-metadata.properties

然后调用getAutoConfigurationEntry方法获取自动配置实体信息:

代码语言:javascript
复制
protected AutoConfigurationEntry getAutoConfigurationEntry(
    AutoConfigurationMetadata autoConfigurationMetadata,
    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 = filter(configurations, autoConfigurationMetadata);
  fireAutoConfigurationImportEvents(configurations, exclusions);
  return new AutoConfigurationEntry(configurations, exclusions);
}

先检查自动配置是否启用,如果没有启用直接返回,默认启用:

代码语言:javascript
复制
protected boolean isEnabled(AnnotationMetadata metadata) {
  if (getClass() == AutoConfigurationImportSelector.class) {
    return getEnvironment().getProperty(
        EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
        true);
  }
  return true;
}

然后调用getCandidateConfigurations方法获取候选配置:

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

调用SpringFactoriesLoader的loadFactoryNames方法加载配置类:

代码语言:javascript
复制
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  String factoryClassName = factoryClass.getName();
  return loadSpringFactories(classLoader)
        .getOrDefault(factoryClassName, Collections.emptyList());
}

加载META-INF/spring.factories路径下的配置信息,然后根据指定的EnableAutoConfiguration名称获取并返回:

代码语言:javascript
复制
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 factoryClassName = ((String) entry.getKey()).trim();
        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryClassName, factoryName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
        FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
}

解析出来的META-INF/spring.factories路径是一个列表,也就是说可能多个模块中都有改配置,甚至我们的业务应用也可以有该配置;spring.factories的格式如下:

解析出来的结果是Map<string, list>,getCandidateConfigurations方法加载并返回EnableAutoConfiguration类型的配置列表,然后移除重复的配置,移除注解中排出的配置,然后调用filter方法过滤掉不符合条件的配置:

代码语言:javascript
复制
private List<String> filter(List<String> configurations,
    AutoConfigurationMetadata autoConfigurationMetadata) {
  long startTime = System.nanoTime();
  String[] candidates = StringUtils.toStringArray(configurations);
  boolean[] skip = new boolean[candidates.length];
  boolean skipped = false;
  for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
    invokeAwareMethods(filter);
    boolean[] match = filter.match(candidates, autoConfigurationMetadata);
    for (int i = 0; i < match.length; i++) {
      if (!match[i]) {
        skip[i] = true;
        candidates[i] = null;
        skipped = true;
      }
    }
  }
  if (!skipped) {
    return configurations;
  }
  List<String> result = new ArrayList<>(candidates.length);
  for (int i = 0; i < candidates.length; i++) {
    if (!skip[i]) {
      result.add(candidates[i]);
    }
  }
  }
  return new ArrayList<>(result);
}

从META-INFO/spring.factories中加载并实例化AutoConfigurationImportFilter类型的过滤器,然后过滤掉不符合条件的配置,比如@ConditionalOnBean,@ConditionalOnClass和@ConditionalOnWebApplication注解等。

过滤配置之后,触发自动装配通知事件。

代码语言: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);
    }
  }
}

从META-INFO/spring.factories加载并实例化AutoConfigurationImportListener类型的监听器,并触发监听逻辑。 最后返回配置信息列表待用(DeferredImportSelector的getImportGroup方法类似,调用时机不一致)。然后在执行ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法时将配置类列表进行实例化并完成初始化。 springboot自动装配时序图大致如下:

三、总结

自动装配时springboot一个非常重要的能力,框架层把很多功能强大的基础配置和能力融合进来方便我们使用,同样我们也可以模仿框架的实现把我们应用层通用配置写到META-INFO/spring.factories中让框架帮我们加载和实例化,甚至我们可以根据其实现原理自己定义路径和文件以及内容格式,然后重写加载逻辑,当然spring的设计原则是面向设计关闭和面向扩展开放,我们可以基于其能力复用和扩展就能满足绝大多数场景,尽量不要破坏其设计。

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

本文分享自 PersistentCoder 微信公众号,前往查看

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

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

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