关于SpringBoot的自动装配原理详解
作者:贤子磊
一、@SpringBootApplication
正常情况下的启动类都会加上@SpringBootApplication注解
@SpringBootApplication public class SpringbootSourceApplication { public static void main(String[] args) { SpringApplication.run(SpringbootSourceApplication.class, args); } }
我们查看下@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) }) @ConfigurationPropertiesScan public @interface SpringBootApplication { }
我们可以看到有@EnableAutoConfiguration注解,这个是自动装配的核心
二、@EnableAutoConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { }
其中有两个注解
@AutoConfigurationPackage
这个注解的作用就是将注解标记的类所在包及所有子包下的组件到扫描到Spring容器中
@Import(AutoConfigurationImportSelector.class)
- @Import根据配置内容有三种使用方式
- class数组
- 将数组内的类注入到Spring容器中,bean名称是全类名
ImportSelector
类型- 实现
ImportSelector
接口
- 实现
- class数组
- @Import根据配置内容有三种使用方式
public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */ String[] selectImports(AnnotationMetadata importingClassMetadata); }
返回值:就是我们实际需要注入到容器的组件全类名(返回值可以是空数组,但是不能为null,否则会有空指针异常)
参数:当前被@Import注解标记的所有注解信息
ImportBeanDefinitionRegistrar
类型
实现ImportBeanDefinitionRegistrar
接口,例如
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { //指定bean定义信息(包括bean的类型、作用域...) RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(自己的类.class); //注册一个bean指定bean名字(id) beanDefinitionRegistry.registerBeanDefinition("自定义名称",rootBeanDefinition); } }
可以自定义一个或多个bean
这里采用的是第二种方式,通过AutoConfigurationImportSelector
来返回需要注入的类名数组
三、AutoConfigurationImportSelector
我们看下AutoConfigurationImportSelector
类中selectImports
方法
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //1、加载spring-autoconfigure-metadata.properties信息,并保存到AutoConfigurationMetadata实例中 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); //2、获取自动装配信息 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
这个方法有两步
- 第一步:加载
spring-autoconfigure-metadata.properties
信息,并保存到AutoConfigurationMetadata
实例中 - 第二步:获取自动装配信息
1、加载spring-autoconfigure-metadata.properties信息
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-autoconfigure-metadata.properties
并封装到AutoConfigurationMetadata
中。spring-autoconfigure-metadata.properties
存储了自动装配的条件元信息,例如
#...省略... org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraTemplate org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration.ConditionalOnBean=javax.jms.ConnectionFactory #...省略...
格式:全类名.条件=条件值
条件(列举下面三个常见的)
ConditionalOnClass
:classpath
下存在某个类才会加载ConditionalOnBean
:容器内下存在某个bean才会加载ConditionalOnWebApplication
:在什么样的web环境下才会加载
2、获取自动装配的类信息
这里的消息包括两个:最终需要自动装配的类列表,和被排除的类列表
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { //1、判断是否允许自动装配,如果否则直接返回空对象 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } //2、获取@EnableAutoConfiguration注解上标注的类的元信息 AnnotationAttributes attributes = getAttributes(annotationMetadata); //3、获取自动装配的候选类名集合 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //4、可能存在重复,所以这里进行去重(因为是从配置文件类名获取的,可能会配置重,所有先去下重) configurations = removeDuplicates(configurations); //5、获取自动装配组件的排除名单 Set<String> exclusions = getExclusions(annotationMetadata, attributes); //6、检查排除名单是否合法 checkExcludedClasses(configurations, exclusions); //7、排除exclusions中的类 configurations.removeAll(exclusions); //8、执行过滤操作,依赖前面AutoConfigurationMetadataLoader.loadMetadata获取的autoConfigurationMetadata configurations = filter(configurations, autoConfigurationMetadata); //9、触发自动装配的导入事件 fireAutoConfigurationImportEvents(configurations, exclusions); //10、返回信息 return new AutoConfigurationEntry(configurations, exclusions); }
分为一下10个步骤
2.1)判断是否允许自动装配,如果否则直接返回空对象
//1、判断是否允许自动装配,如果否则直接返回空对象 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; }
protected boolean isEnabled(AnnotationMetadata metadata) { if (getClass() == AutoConfigurationImportSelector.class) { return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true); } return true; }
如果是AutoConfigurationImportSelector
类型,则需要判断配置中的spring.boot.enableautoconfiguration
是否为true,否则直接返回空对象
2.2)获取@EnableAutoConfiguration
注解上标注的类的元信息
//2、获取@EnableAutoConfiguration注解上标注的类的元信息 AnnotationAttributes attributes = getAttributes(annotationMetadata);
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) { String name = getAnnotationClass().getName(); AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true)); Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?"); return attributes; }
获取注解上的元信息
2.3)获取自动装配的候选类名集合
//3、获取自动装配的候选类名集合 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
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; }
核心方法是SpringFactoriesLoader
中的loadFactoryNames
方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories" 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); } }
搜索指定ClassLoader
下所有的META/spring.factories
资源内容,可能会返回多个
spring.factories
中存放了所有自动装配的候选类,这些类名格式都是XXXAutoConfiguration
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ ......
将这些资源内容作为Properties
文件读取,合并为一个Key
为接口的全类名,Value
是实现全类名列表的Map,作为loadSpringFactories
的返回值
再从上一步返回的Map中查找并返回方法指定类名所映射的实现类全类名列表
2.4)去重
//4、可能存在重复,所以这里进行去重(因为是从配置文件类名获取的,可能会配置重,所有先去下重) configurations = removeDuplicates(configurations);
因为配置文件可能存在重复,因此这里手动做了去重处理
protected final <T> List<T> removeDuplicates(List<T> list) { return new ArrayList<>(new LinkedHashSet<>(list)); }
使用到LinkedHashSet
来去重
2.5)获取自动装配组件的排除名单
//5、获取自动装配组件的排除名单 Set<String> exclusions = getExclusions(annotationMetadata, attributes);
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) { Set<String> excluded = new LinkedHashSet<>(); excluded.addAll(asList(attributes, "exclude")); excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName"))); excluded.addAll(getExcludeAutoConfigurationsProperty()); return excluded; } private List<String> getExcludeAutoConfigurationsProperty() { if (getEnvironment() instanceof ConfigurableEnvironment) { Binder binder = Binder.get(getEnvironment()); return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList) .orElse(Collections.emptyList()); } String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class); return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList(); }
排除名单来源于三个地方
@EnableAutoConfiguration
注解中的exclue
属性@EnableAutoConfiguration
注解中的excludeName
属性spring.autoconfigure.exclude
配置
2.6)检查排除名单是否合法
//6、检查排除名单是否合法 checkExcludedClasses(configurations, exclusions);
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) { List<String> invalidExcludes = new ArrayList<>(exclusions.size()); for (String exclusion : exclusions) { if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) { invalidExcludes.add(exclusion); } } if (!invalidExcludes.isEmpty()) { handleInvalidExcludes(invalidExcludes); } } protected void handleInvalidExcludes(List<String> invalidExcludes) { StringBuilder message = new StringBuilder(); for (String exclude : invalidExcludes) { message.append("\t- ").append(exclude).append(String.format("%n")); } throw new IllegalStateException(String.format( "The following classes could not be excluded because they are not auto-configuration classes:%n%s", message)); }
当排除类存在于当前ClassLoader
,且不在自动装配的候选类名单上,则当前排除类非法,会触发IllegalStateException
异常
2.7)排除exclusions中的类
//7、排除exclusions中的类 configurations.removeAll(exclusions);
2.8)执行过滤操作
//8、执行过滤操作,依赖前面AutoConfigurationMetadataLoader.loadMetadata获取的autoConfigurationMetadata configurations = filter(configurations, autoConfigurationMetadata);
- 第一个参数:自动装配候选名单
- 第二个参数:自动装配的条件元信息
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); //将候选名单转化为数组,目的是为了下面的参数匹配 String[] candidates = StringUtils.toStringArray(configurations); //每个位置都标记是否需要过滤 boolean[] skip = new boolean[candidates.length]; //是否发生过过滤,目的是如果没有发生过,则不需要遍历skip数组进行筛选 boolean skipped = false; //获取当前beanClassLoader下的所有AutoConfigurationImportFilter for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); //匹配计算出结果 boolean[] match = filter.match(candidates, autoConfigurationMetadata); //根据结果更新skip for (int i = 0; i < match.length; i++) { //如果不匹配,则表示当前位置的类不满足条件,需要过滤掉 if (!match[i]) { skip[i] = true; //这里是手动释放引用,方便下次GC回收 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]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<>(result); } //获取当前beanClassLoader下的所有AutoConfigurationImportFilter protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); }
该方法核心就是根据spring-autoconfigure-metadata.properties
中配置的条件进行候选名单的过滤,目的是减少不必要的类加载,提高启动速度
2.9)触发自动装配的导入事件
//9、触发自动装配的导入事件 fireAutoConfigurationImportEvents(configurations, exclusions);
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); } } } protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader); }
获取当前beanClassLoader
下的所有的AutoConfigurationImportListener
实例,执行对应的监听方法
2.10)返回自动装配信息
//10、返回信息 return new AutoConfigurationEntry(configurations, exclusions);
返回的信息内容
List<String> configurations
:最终需要自动装配的类列表Set<String> exclusions
:排除名单
四、总结(自动装配流程)
- 查询配置spring.boot.enableautoconfiguration,如果是true则继续,否则表示不启用自动装配,直接返回空对象
- 读取所有META-INF/spring-autoconfigure-metadata.properties资源,保存为自动装配的条件元信息,后续用来做最后的过滤
- 读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合
- 读取当前配置类所标注的@EnableAutoConfiguration属性exclude和excludeName,以及spring.autoconfigure.exclude配置属性合并为自动装配Class排除集合
- 检查自动装配Class排除集合是否合法
- 排除候选自动装配Class集合中的排除名单
- 使用之前加载的条件元信息,再次过滤候选自动装配Class集合中Class不存在的成员
- 自动装配Class集合过滤完成后,触发AutoConfigurationImportEvent监听器执行
- 返回装配Class集合+排除名单
到此这篇关于关于SpringBoot的自动装配原理详解的文章就介绍到这了,更多相关SpringBoot的自动装配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!