Практика и принцип разработки плагинов Spring Boot

Spring Boot

Заявление об авторских правах: эта статья является оригинальной статьей блоггера и соответствует соглашению об авторских правах CC 4.0 BY-SA. Пожалуйста, приложите ссылку на оригинальный источник и это заявление для перепечатки.
Ссылка на эту статью:Гу Депэн.GitHub.IO/note/2020/0…

1. Настоящий бой: написание плагинов для весенней загрузки

1. Зачем писать загрузочный плагин

Поскольку нам нужно предоставить некоторые общие функции во время разработки, мы пишем общий пакет jar. При использовании пакета jar разработчикам не нужно учитывать содержимое пакета jar, и они могут напрямую использовать определенные функции.Однако написанный вами bean-компонент может не быть инициализирован в контейнере spring из-за разных путей пакетов. Разработчиков не следует просить сканировать путь вашего пакета для инициализации bean-компонентов. Таким образом, мы должны сами инициализировать bean-компонент в контейнере bean-компонента, что также является источником возможностей расширения Spring (spriing.factories).

2. Настоящий бой

Напишите код плагина, напишите класс конфигурации (например: DemoAutoConfig) и определите в нем нужные вам bean-компоненты. Создайте META-INF/spring.factories под ресурсами написать spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.demo.DemoAutoConfig

Два.общий интерфейс настройки spring.factories

1. org.springframework.boot.SpringApplicationRunListener

SpringApplicationRunListener отслеживает процесс запуска Spring Boot и обрабатывает собственную логику в каждом процессе. Когда приложение запускается, соответствующие методы вызываются на каждом этапе инициализации контейнера Spring.

2. org.springframework.context.ApplicationContextInitializer

ApplicationContextInitializer вызывается перед (обновлением) метода обновления контекста ConfigurableApplicationContext в процессе запуска Springboot для дальнейшей установки или обработки экземпляра ConfigurableApplicationContext.

3.org.springframework.boot.autoconfigure.EnableAutoConfiguration

Определяет классы, которые система автоматически связывает.

4.org.springframework.boot.env.EnvironmentPostProcessor

Централизованное управление средой конфигурации. Например, расширение должно исключать стандартные классы конфигурации системы загрузки, что удобно для пользовательских расширений.

5.org.springframework.boot.autoconfigure.AutoConfigurationImportFilter

Исключения класса Autowire

3. Принцип пружинных фабрик

1. Получите процесс настройки

В аннотации класса запуска @SpringBootApplication вы можете видеть, что @EnableAutoConfiguration упоминается. Где @Import(AutoConfigurationImportSelector.class)

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());
}

Где метод getAutoConfigurationEntry

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);
}

где getCandidateConfigurations

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;
}

SpringFactoryLoader.loadFactoryNames вызывается

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

метод loadSpringFactory

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

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);
    }
}

2. Процесс загрузки конфигурации

Когда запустится основной метод, мы вызовем метод SpringApplication.run getSpringFactoryInstances вызывается в методе запуска вызов createSpringFactoryInstances

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

4. Резюме

Это метод проектирования, аналогичный подключаемому модулю.Пока будет введен соответствующий пакет jar, spring.factories в банке будут сканироваться, и будет создан экземпляр соответствующего класса реализации.