processon
首先也还是先给出自己的processon地址:www.processon.com/view/617cfb…
多余的话
首先网上关于springboot自动装配的名词解释,springboot什么约定大于配置,什么@EnableAutoConfiguration注解等等其实都已经有了非常到位的解释,正常应对面试说实话都是绰绰有余的。 所以此篇文章还是主要以我自己读取源码,分析源码来讲述springboot自动装配的起始与终结。
个人分析
很多人讲springboot的自动装配都会从springboot的主注解讲到@EnableAutoConfiguration注解再讲到 AutoConfigurationImportSelector类,然后再讲这个类里做了什么。。。。
对于我来说这样分析确实是不完整的或者说有些断章取义的,如果确实对springboot源码已经有了相关的阅读,对整体的架子有了一些稍微的认知的话。那所有的分析的入口便绝对不是这个注解了。(如果没有阅读源码的相关理解,这个注解确实是一个切入口)
spring容器加载bean对象按照大步骤来说的话其实就2步。
1.获取beanDefinition信息。 2.根据definition信息生成bean。
这样来理解总体的话也就大致清楚了自动装配的起点在哪里。其实就是在加载beanDefinition的时候。
入口
AbstractApplicationContext类中refresh方法中的invokeBeanFactoryPostProcessors方法 也就是调用bean工厂的后置处理器。(注:BeanFactoryPostProcessor和BeanPostProcessor这两个接口对于我现在的认知来说是spring中极为重要的两个接口,我会单独开一章来讨论这两个接口)
说到自动装配不得不提到的非常重要的类ConfigurationClassPostProcessor。它实现BeanFactoryPostProcessor接口也就是说对于beanDefinition信息的加载它做了很多事情。
源码解析
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
//如果配置bean是以注解方式注入走此方法
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();
走到doProcessConfigurationClass方法正式处理配置bean的信息
//处理 @PropertySource 注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 处理我们非常熟悉的 @ComponentScan 注解
// 也就是扫包把类上有 @Component 等我们熟悉的mvc的相关注解类加入beanDefinition后续生成bean
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// 处理 @Import注解
// processImports方法非常重要下面讲
// getImports(sourceClass) 递归找出所有import注解里面的类
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 处理 @ImportResource注解
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 处理 @Bean 修饰的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
processImports方法及其重要
众所周知,将对象加入spring的方式有很多种,其中就有
@Import(A.class)
@Import(B.class) B实现了ImportSelector重写selectImports方法,返回的String[](存放类的名称的数组)会加入到容器
@Import(C.class) C实现了ImportBeanDefinitionRegistrar重写registerBeanDefinitions将beanDefinition信息注册到容器
processImports大致就处理了这三种实现 当然还后一个比较特殊就是自动装配的 DeferredImportSelector
try {
for (SourceClass candidate : importCandidates) {
//如果实现了ImportSelector接口
if (candidate.isAssignable(ImportSelector.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
//点进此方法可以发现把对应的类信息加入到了deferredImportSelectors这个集合里面
//这个集合非常眼熟其实就是上方第一张截图的集合
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
//获取到String[](存放类的名称的数组)然后递归处理里面的类
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
//如果实现了ImportBeanDefinitionRegistrar接口 就委托给它注册额外的bean定义
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
//此处英文注解也写得很清楚 如果不是ImportSelector ImportBeanDefinitionRegistrar就是@Import(A.class) 直接按照@Configuration处理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
然后我们分析这个接口DeferredImportSelector,发现@EnableAutoConfiguration注解中AutoConfigurationImportSelector这个类实现了这个延迟导入的接口,于是乎判定自动装配的实现肯定在deferredImportSelectors集合里也就是第一张截图的地方。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//看到loadFactoryNames就已经非常明了了。
//getSpringFactoriesLoaderFactoryClass() 返回的就是EnableAutoConfiguration.class类
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;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//获取EnableAutoConfiguration所需要加载全类名
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
//FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
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();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
尾
至此自动装配的大致流程差不多就结束了。当然里面还有很多细节,可能由于水平原因并不能完全讲出请谅解,但是自动装配的源码大致流程已经是比较清晰了。
如果文章中有错误存在,希望大佬指正,非常感谢!