Аннотация @ConfigurationProperties в Spring Boot реализует принцип анализа исходного кода.

Spring Boot
Аннотация @ConfigurationProperties в Spring Boot реализует принцип анализа исходного кода.

0. Рекомендация проекта с открытым исходным кодом

Pepper Metricsэто инструмент с открытым исходным кодом, разработанный мной и моими коллегами (GitHub.com/live-actioncool/pep…), который собирает текущую статистику производительности jedis/mybatis/httpservlet/dubbo/motan и предоставляет ее для основных данных, совместимых с базой данных временных рядов, таких как prometheus, и отображает тенденции через grafana. Его подключаемая архитектура также очень удобна для пользователей, позволяющих расширять и интегрировать другие компоненты с открытым исходным кодом.
Пожалуйста, поставьте звезду и пригласите всех стать разработчиками и отправить PR, чтобы вместе улучшить проект.

1 Обзор

Излишне говорить, что все знают, что Spring Boot очень удобен и быстр, позволяя разработчикам запускать и использовать проект с помощью всего нескольких строк кода плюс несколько строк конфигурации или даже нулевой конфигурации, Мы также можем часто использовать его в проектах. @ConfigurationProperties привязывает бин к префиксу в конфигурации свойств, чтобы значение конфигурации было отделено от бина, определяющего конфигурацию, что удобно для управления.
Итак, каков механизм этого @ConfigurationProperties и как он реализован? Давайте сегодня поговорим на эту тему

2. Текст

2.1 Начните с EnableConfigurationProperties

Зачем говорить из EnableConfigurationProperties? Большое количество автоконфигураций в самом проекте Spring Boot использует аннотацию EnableConfigurationProperties для включения функции XXXProperties, например, spring-data-redis Эта RedisAutoConfiguration

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) //看这里
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    // ...
}

И RedisProperties аннотируется с помощью @ConfigurationProperties (prefix = "spring.redis"), так что элемент конфигурации с префиксом spring.redis и RedisProperties Этот класс сущностей связан.

2.2 Внутреннее разрешение реализации EnableConfigurationProperties

После разговора о причине, давайте поговорим о внутренней реализации, давайте сначала посмотрим

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
	Class<?>[] value() default {};
}

@Import(EnableConfigurationPropertiesImportSelector.class) указывает класс обработки EnableConfigurationPropertiesImportSelector этой аннотации, Просмотр исходного кода EnableConfigurationPropertiesImportSelector

class EnableConfigurationPropertiesImportSelector implements ImportSelector {

	private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(),
			ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

	@Override
	public String[] selectImports(AnnotationMetadata metadata) {
		return IMPORTS;
	}
	
	// 省略部分其他方法
}

Давайте сначала посмотрим, что эта ключевая часть возвращает массив IMPORTS, который содержит два элемента: ConfigurationPropertiesBeanRegistrar.class, ConfigurationPropertiesBindingPostProcessorRegistrar.class.
По принципу интерфейса @Import и ImportSelector (по принципу можно сослаться на статью коллеги:С любовью к @Import и @EnableXXX), мы знаем, что Spring инициализирует два вышеупомянутых Registrar в контейнере spring, и оба Registrar реализуют интерфейс ImportBeanDefinitionRegistrar, ImportBeanDefinitionRegistrar инициирует вызов при обработке Конфигурации (принцип можно посмотреть в статье:Найдите статью здесь), давайте углубимся в исходный код двух регистраторов соответственно:

  • ConfigurationPropertiesBeanRegistrar
  • ConfigurationPropertiesBindingPostProcessorRegistrar

2.2.1 ConfigurationPropertiesBindingPostProcessorRegistrar

смотри прямо на код

public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
			registerConfigurationPropertiesBindingPostProcessor(registry);
			registerConfigurationBeanFactoryMetadata(registry);
		}
	}

	private void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) {
		GenericBeanDefinition definition = new GenericBeanDefinition();
		definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
		definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
	}

	private void registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) {
		GenericBeanDefinition definition = new GenericBeanDefinition();
		definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
		definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition);
	}
}

Вы можете видеть, что два bean-компонента зарегистрированы в контейнере spring.

  • ConfigurationPropertiesBindingPostProcessor
    • Он реализует следующий интерфейс: BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean
      • PriorityOrdered
        Ordered.HIGHEST_PRECEDENCE + 1 гарантирует досрочное исполнение, а не первое
      • ApplicationContextAware
        Получите ApplicationContext и установите его во внутреннюю переменную
      • InitializingBean
        Метод afterPropertiesSet вызывается при создании bean-компонента, чтобы убедиться, что внутренняя переменная configurationPropertiesBinder инициализирована.Этот класс связывания является ключевым классом инструмента для связывания значений префикса и свойстваBean.
      • BeanPostProcessor Метод postProcessBeforeInitialization обрабатывает конкретную логику привязки следующим образом:
      @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
          ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
          if (annotation != null) {
              bind(bean, beanName, annotation);
          }
          return bean;
      }
      private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
          ResolvableType type = getBeanType(bean, beanName);
          Validated validated = getAnnotation(bean, beanName, Validated.class);
          Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
                  : new Annotation[] { annotation };
          Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
          try {
              // 在这里完成了,关键的prefix到PropertyBean的值绑定部分,所以各种@ConfigurationProperties注解最终生效就靠这部分代码了
              this.configurationPropertiesBinder.bind(target);
          }
          catch (Exception ex) {
              throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
          }
      }
  • ConfigurationBeanFactoryMetadata
    Если некоторые bean-компоненты создаются с помощью FactoryBean, этот класс используется для сохранения различной исходной информации FactoryBean, которая используется для запроса метаданных в ConfigurationPropertiesBindingPostProcessor, который здесь не будет раскрываться.

2.2.2 ConfigurationPropertiesBeanRegistrar

На самом деле ConfigurationPropertiesBeanRegistrar — это статический внутренний класс EnableConfigurationPropertiesImportSelector, часть, которая была пропущена при вставке кода ранее, вышеприведенный код

	public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
		}

		private List<Class<?>> getTypes(AnnotationMetadata metadata) {
			MultiValueMap<String, Object> attributes = metadata
					.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
			return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
		}

		private List<Class<?>> collectClasses(List<?> values) {
			return values.stream().flatMap((value) -> Arrays.stream((Object[]) value)).map((o) -> (Class<?>) o)
					.filter((type) -> void.class != type).collect(Collectors.toList());
		}

		private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,
				Class<?> type) {
			String name = getName(type);
			if (!containsBeanDefinition(beanFactory, name)) {
				registerBeanDefinition(registry, name, type);
			}
		}

		private String getName(Class<?> type) {
			ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);
			String prefix = (annotation != null) ? annotation.prefix() : "";
			return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
		}

		private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) {
			assertHasAnnotation(type);
			GenericBeanDefinition definition = new GenericBeanDefinition();
			definition.setBeanClass(type);
			registry.registerBeanDefinition(name, definition);
		}

		private void assertHasAnnotation(Class<?> type) {...}
		private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {...}
	}

Логическая интерпретация:
Основная логическая запись (registerBeanDefinitions) 1 -> getTypes(metadata) получает значения конфигурации, помеченные аннотацией EnableConfigurationProperties, упорядочивает их в List> и обрабатывает их одно за другим 2 -> Вызвать метод регистрации для каждого элемента (класс>) 3 -> Метод register регистрирует Class> в реестре через вызов register.registerBeanDefinition через значение префикса аннотации ConfigurationProperties в классе и имя класса как beanName Когда BeanDefinition из XXXProperties с аннотацией @ConfigurationProperties добавляется в реестр, инициализация bean-компонента передается контейнеру Spring. В этом процессе ConfigurationPropertiesBindingPostProcessorRegistrar, упомянутый ранее, выполняет серию пост-операций, чтобы помочь нам завершить привязку окончательного значения.

3. Резюме

Общий процесс обработки @ConfigurationProperties был в основном описан в этой статье, и теперь он в общих чертах: EnableConfigurationProperties завершает знакомство с ConfigurationPropertiesBindingPostProcessorRegistrar и ConfigurationPropertiesBeanRegistrar. в:

  • ConfigurationPropertiesBeanRegistrar завершает поиск классов с пометкой @ConfigurationProperties и собирает их в BeanDefinition для присоединения к реестру.
  • ConfigurationPropertiesBindingPostProcessorRegistrar завершает ConfigurationPropertiesBindingPostProcessor и ConfigurationBeanFactoryMetadata
    • ConfigurationPropertiesBindingPostProcessor завершает привязку всех bean-компонентов, отмеченных @ConfigurationProperties, к значению свойств префикса
    • ConfigurationBeanFactoryMetadata используется только для предоставления некоторой информации метаданных, необходимой для вышеуказанной обработки.

4. Другие статьи автора

GitHub.com/live версия классная/нет…

5. Аккаунт подписки WeChat