Основное содержание этой статьи
- Принцип автоматической сборки Spring Boot [с симуляцией корпуса]
- Анализ исходного кода автоматической сборки Spring Boot
Принцип автоматической загрузки в SpringBoot
SpringBoot имеет готовые функции, он незаметно помогает нам делать много вещей [автоматически создавать и собирать множество объектов]
Внедрение автовайринга
В основном через автоматическую сборку интерфейса ImportSelector
ImportSelector
Интерфейс ImportSelector является основным интерфейсом Spring для импорта внешней конфигурации и играет решающую роль в автоматической настройке SpringBoot и @EnableXXX (функциональная аннотация). Когда класс реализации ImportSelector вводится с помощью @Import в классе, отмеченном @Configuration, все имена классов, возвращаемые в классе реализации, определяются как bean-компоненты.
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotationMetadata;
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);
}
Схема наследования интерфейса
DeferredImportSelector
Из рисунка выше видно, что интерфейс DeferredImportSelector наследуется от ImportSelector, разница между ним и ImportSelector заключается во времени загрузки бинов.DeferredImportSelector необходимо дождаться выполнения всех @Configuration перед загрузкой.
Случай автопроводки Spring Boot
Портал кейсов проекта:autoConfig
основной код корпуса
- Создайте класс конфигурации, который создает bean-компоненты, но мы не объявляем его с помощью аннотации @Configuration.
public class MyConfig {
@Bean(value = "chenfu", name = "chenfu")
public Map<Object, Object> getMap() {
HashMap<Object, Object> map = new HashMap<>();
map.put("code", 200);
map.put("msg", "success");
String nowDate = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
map.put("data", nowDate);
return map;
}
}
2. Реализуйте интерфейс ImportSelector и верните указанное выше имя класса конфигурации.public class MyConfigImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 返回配置名称
return new String[]{MyConfig.class.getName()};
}
}
3. Запустите тест@SpringBootApplication
@Import(value = MyConfigImportSelector.class)
public class AutoConfigApp {
public static void main(String[] args) {
ConfigurableApplicationContext app = SpringApplication.run(AutoConfigApp.class, args);
Object chenfu = app.getBean("chenfu");
System.out.println(chenfu);
}
}
Общее содержание кейса такое же, как и выше.Из кейса видно, что объекты, автоматически собранные Spring Boot, не используют объявления аннотаций создания объектов Spring (@Controller, @Service, @Repostiroty), а динамически загружают bean-компоненты путем программирования . Этапы синтаксического анализа этих объектов в Spring Boor в основном выполняются в методе processImports класса ConfigurationClassParser.
Процесс разбора ImportSelector
ConfigurationClassParser
Исходный код класса ConfigurationClassParser
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//对ImportSelector类型的类进行处理
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
//上边我们说过的另外一种Selector类型,可以理解为延迟加载
if (selector instanceof DeferredImportSelector) {
//该方法内部会将该Selector保存到一个集合【deferredImportSelectors】中
this.deferredImportSelectorHandler.handle(
configClass, (DeferredImportSelector) selector);
} else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
} else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
// 直接把上边的官方英文注释硬翻译了,当前类不是ImportSelector或ImportBeanDefinitionRegistrar类型,直接让其走@Configuration类的处理流程
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
} catch (BeanDefinitionStoreException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
} finally {
this.importStack.pop();
}
}
}
Подводя итог, общий процесс заключается в том, что возвращаемое значение интерфейса ImportSelector будет рекурсивно анализироваться, а затем полное имя анализируемого класса будет окончательно обработано в соответствии с @Configuration.
ImportSelectorSummary
Стандартные функции SpringBoot во многом связаны с ImportSelector.
Анализ исходного кода Spring Boot
Spring Boot сделал несколько расширений на основе Spring.
- Аннотация @EnableAutoConfiguration объявлена в аннотации SpringBootApplication SpringBoot.
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
2. @Import({AutoConfigurationImportSelector.class}), определенный SpringBoot, вводится через Import в @EnableAutoConfiguration.@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
Затем запустите анализ исходного кода AutoConfigurationImportSelector.
AutoConfigurationImportSelector
AutoConfigurationImportSelector — это класс реализации selectImports, давайте посмотрим на метод selectImports.
selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
Основная логика этого метода находится в методе getAutoConfigurationEntry
getAutoConfigurationEntry
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 2.1通过getCandidateConfigurations方法获取所有需要加载的bean
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = this.removeDuplicates(configurations);
// 获取不需要加载的bean,我们可以通过spring.autoconfigure.exclude配置
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
// 发送事件,通知所有的AutoConfigurationImportListener进行监听
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
Затем взгляните на метод getCandidateConfigurations, вызываемый в исходном коде выше.
getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 这里的getSpringFactoriesLoaderFactoryClass()最终返回的是EnableAutoConfiguration.class
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;
}
Как видно из приведенной выше логики, конечный канал получения бинов находится в SpringFactoriesLoader.loadFactoryNames SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
SpringFactoriesLoader
loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 通过factoryClassName获取相应的bean全称
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
// 获取项目中所有META-INF/spring.factories文件,将其组装成Map
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
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 factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
Каждый пакет jar может определить свой собственный META-INF/spring.factories, и bean-компоненты, определенные в spring.factories, будут автоматически загружены при загрузке jar. Мы можем взглянуть на содержимое этого файла конфигурации Spring Boot [вынуть только часть содержимого]
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# 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.EnableAutoConfiguration=\
top.chenfu.auto.config.MyConfig
Удалите аннотации @import или пользовательские @EnableXXX в классе запуска.