江帅帅,微信公众号【江帅帅】作者 ,擅长系统架构设计,大数据,运维、机器学习等技术领域;对大中后台技术有丰富经验(交易平台、基础服务、智能客服、基础架构、智能运维、数据库、安全、IT 等方向);曾担任怀致科技 CTO,并还在东软集团、中国移动、多迪集团等企业中任职过相关技术负责人。
1. Spring Boot 的自动配置超详解
1.1 @SpringBootApplication 注解
Spring Boot 的启动类,也就是入口类,需要使用 @SpringBootApplication
注解来标注。在启动类中,我们的 main 方法就是 Java 应用程序的入口方法。
@SpringBootApplication 是一个组合注解,具体源码如下:
@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 { @AliasFor( annotation = EnableAutoConfiguration.class ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
其中,比较重要的三个注解是: @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。
1.2 @SpringBootConfiguration 注解
主要是负责 Spring Boot 应用配置相关的注解,它也是组合注解,具体源码如下:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
通过源码,可以看到它也使用了 @Configuration
注解,它们两个都是将当前类标注为配置类,能将类中使用 @Bean 注解标记的方法对应的实例注入到 Spring 容器中,那实例名就是方法名。
另外在 @Configuration 注解源码中,还看到有一个 @Component
注解,做了再次封装,主要是把普通 POJO 实例化到 Spring 容器中。具体源码如下:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor( annotation = Component.class ) String value() default ""; boolean proxyBeanMethods() default true; }
所以,更推荐大家在 Spring Boot 应用中使用 @SpringBootConfiguration。
1.3 @EnableAutoConfiguration 注解
主要用来启动自动配置,Spring Boot 就能够根据依赖信息自动实现应用的相关配置,总体分为两个部分:一是收集所有 spring.factories
中EnableAutoConfiguration 相关 bean 的类,二是将得到的类注册到 Spring 容器中。将符合的配置都加载到 IoC 容器中。具体源码如下:
@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 {}; }
组件调用关系图,具体如下:
这张图,怎么去理解呢?其实是这样的,涉及到了 BeanFactory 的创建。Spring 框架中会调用 ApplicationContext 的 refresh 方法来启动 Spring 容器,然后就会创建 BeanFactory,接着扫描各种包,读取使用到了 @Configuration、@Import、@SpringBootApplication 等注解标注的类,然后生成 BeanDefinition 最终注册到 BeanFactory 中。
然后就交给 BeanFactoryPostProcessor 来执行,BeanFactory 后置处理器会处理 BeanDefinition,比如在 BeanFactoryPostProcessor 接口中,提供了 postProcessBeanFactory 方法来接收 ConfigurableListableBeanFactory 对象来处理。具体源码如下:
@FunctionalInterface public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; }
其他类似 @Configuration 等配置性质的注解,就让 ConfigurationClassPostProcessor 来处理。
上面的 ConfigurationClassPostProcessor 主要是 BeanFactoryPostProcessor 接口的实现类,主要是想从 BeanFactory 中获取所有 BeanDefinition 列表,遍历出那些使用了 @Configuration、@Import 等配置性质注解标注的类所对应的 BeanDefintion,然后进行注册。具体源码如下:
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {...}
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; }
具体,我们还可以去看看它的 parse 方法是如何处理的,它会去解析注解。
public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { 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(); }
看到最后的 deferredImportSelectorHandler,这个内部类的里面有一个 deferredImportSelectors 集合,主要是用来添加 AutoConfigurationImportSelector。这个内部私有类,主要维护了一个类型为DeferredImportSelectorHolder 的 deferredImportSelectors 列表。这最后一句代码,就是处理完其他BeanDefinitions 后调用 process 方法。
再接着来看 process 方法,它负责自动配置类导入的内部实现,具体源码如下:
public void process() { List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; try { if (deferredImports != null) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); deferredImports.sort(DEFERRED_IMPORT_COMPARATOR); deferredImports.forEach(handler::register); handler.processGroupImports(); } } finally { this.deferredImportSelectors = new ArrayList<>(); } }
这个方法,需要这么来理解:
首先,DeferredImportSelector 它会去从 spring-boot-autoconfigure 包路径下的 META-INF/spring.factories 文件中找到 EnableAutoConfiguration 作为 key,然后获取对应的自动配置类列表。
第二步,在里面通过 key 即可找到对应需要自动配置的类。接着会进行遍历所有类名,加载和导入对应的配置类。
大致的思路是会先创建一个 ConfigurationClass 的对象,它会包含当前这个配置类,然后传进被调用的 doProcessConfigurationClass 方法中,然后处理该类包含的注解。如果是 @Import 注解,则会放在 processImports 方法中进行处理。
再具体讲,就是那些非 ImportSelector 接口实现类和ImportBeanDefinitionRegistrar 接口实现类的配置类,就会调用processConfigurationClass 方法来处理该自动配置类上面的其他注解,并将该自动配置类内部使用了 @Bean 注解的所有方法,条件化生成 bean 并注册到 Spring 容器,那最终就可以提供特定功能组件的默认实现,也就实现了 SpringBoot 的自动配置功能,在你使用的时候,比如直接通过 @Autowried 注解就可以注入某个功能组件,而不需要显示配置。
具体源码如下(这里不贴全部源码了,大家可以看看它给出的注释就明白了):
@Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass); } // Process any @PropertySource annotations // 省略源码... // Process any @ComponentScan annotations // 省略源码... // Process any @Import annotations // 省略源码... // Process any @ImportResource annotations // 省略源码... // Process individual @Bean methods // 省略源码... // Process default methods on interfaces // 省略源码... // Process superclass, if any // 省略源码... // No superclass -> processing is complete return null; }
1.4 获取 Bean 类信息
我们可以来研究下这个注解,了解它是如何加载配置的。在源码中,可以看到 @Import({AutoConfigurationImportSelector.class})
注解,导入的就是自动配置选择器。
AutoConfigurationImportSelector 选择器是 DeferredImportSelector 接口的实现类,会在 BeanFactory 中对所有 BeanDefinition 处理后执行来进行 SpringBoot 自动配置类的加载、导入操作等,并基于 @Conditional
条件化配置来决定是否将该配置类内部定义的 Bean 注册到 Spring 容器。具体源码如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {...}
在 AutoConfigurationImportSelector.class
中,可以看到实现了一个 selectImports
方法,用来导出 Configuration。方法中调用了 getAutoConfigurationEntry
方法,获取 bean 类信息。具体源码如下:
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // 获取自动配置的元数据 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); // 获取 bean 类信息 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); // 返回获取到的所有配置 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
继续来看 getAutoConfigurationEntry
方法,具体源码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 获取注解的元数据,主要有 exclude,excludeName 等 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 获取所有的 configurations List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 将获取到的 configurations 进行去重 configurations = removeDuplicates(configurations); // 根据 exclusion 来删掉指定的类 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
再接着来看调用的 getCandidateConfigurations
方法,它主要是想获取所有对应的配置,它里面调用了 loadFactoryNames
方法,目的是要想加载 spring.factories
文件。它们的源码具体如下:
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; }
loadFactoryNames 方法的具体源码如下:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { // 这里获得的 factoryTypeName 主要就是 org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryTypeName = factoryType.getName(); // 这里返回的是在 spring.factories 文件中那些 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的类路径 return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
接着就在 loadSpringFactories
方法中,找到所有的 spring.factories
配置信息,然后全部返回。具体源码如下:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 获取所有 spring.factories 文件 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); } }