springboot自动装配源码

976 阅读3分钟

简介

springboot是由Pivotal团队提供的全新框架。spring的出现是为了解决企业级开发应用的复杂性,spring的通过注册bean的方式来管理类,但是随着业务的增加,使用xml配置bean的方式也显得相当繁琐,所以springboot就是为了解决spring配置繁琐的问题而诞生的。

核心注解

@SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@Conditional

核心思想

约定大于配置

自动装配源码解析

启动项

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

@SpringBootApplication注解

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

@SpringBootApplication注解主要由这三个注解构成,我们先看第一个

@SpringBootConfiguration

F0vX3uuOUR.jpg 主要就是一个@Configuration,一个JavaConfig配置类

@ComponentScan

扫描包路径的注解这个不用过多阐述了

@EnableAutoConfiguration

vMZUsW9ETl.jpg
@EnableAutoConfiguration上有一个@Import(AutoConfigurationImportSelector.class) 导入注解,我们着重看一下他导入了什么内容

AutoConfigurationImportSelector

W5jfSedDUa.jpg 实现了一个spring提供的导入选择器,就会去重写selectImports方法

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   //获取自动配置文件的全包名
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

这里其实就是读取spring.factories文件的信息
先看看spring.factories里的信息格式

fOOPRi22cu.jpg spring.factories里得信息是k-v的格式,自动装配呢解析的是k为EnableAutoConfiguration注解的全类名,我们继续回头看源码

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        //@EnableAutoConfiguration注解的信息
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        //获取spring.factories中k为EnableAutoConfiguration注解的全类名下的类名
        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.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

我们着重看this.getCandidateConfigurations(annotationMetadata, attributes)方法

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;
}

这个方法里又调用了一个loadFactoryNames的方法,我们继续看

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    //类加载器
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    //这里拿到的是@EnableAutoConfiguration全类名
    String factoryTypeName = factoryType.getName();
    //核心方法去获取spring.factories文件并过滤获取k为@EnableAutoConfiguration全类名的集合
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        HashMap result = new HashMap();

        try {
        //加载META-INF/spring.factories里的资源
            Enumeration urls = classLoader.getResources("META-INF/spring.factories");

            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[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;

                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }

            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}

这个方法的返回值是一个Map<String, List>的结构,验证了我们刚刚所说的spring.factories中的kv 并且v是集合,我们debug来看一下这个返回值。

0BVKnp85OI.jpg 其中有我们要的@EnableAutoConfiguration全类名的集合,但是他的集合容量多达183个,其实我们用不着这么多,我们继续看后续springboot如何帮我们过滤的。

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
   return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
   try {
      Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
      Properties properties = new Properties();
      while (urls.hasMoreElements()) {
         properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
      }
      return loadMetadata(properties);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
   }
}

spring还会去解析spring-autoconfigure-metadata.properties的文件(这个文件在项目编译时会生成)获取自动装配类上面的条件注解,然后会帮我们把不符合条件的过滤掉。

自动装配本质

  • SpringBoot自动装配的本质就是通过Spring去读取META-INF/spring.factories中保存的配置类文件然后加载bean定义的过程。
  • 如果是标了@Configuration注解,就是批量加载了里面的bean定义
  • 如何实现”自动“:通过配置文件获取对应的批量配置类,然后通过配置类批量加载bean定义,只要有写好的配置文件spring.factories就实现了自动。