使用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的设计原则是面向设计关闭和面向扩展开放,我们可以基于其能力复用和扩展就能满足绝大多数场景,尽量不要破坏其设计。