Как связаны значения свойств конфигурации SpringBoot? Исходный код SpringBoot (5)

Spring Boot
Как связаны значения свойств конфигурации SpringBoot? Исходный код SpringBoot (5)

Примечание. Анализ исходного кода соответствует версии SpringBoot 2.1.0.RELEASE.

1. Введение

Эта статья продолжаетКак SpringBoot реализует автоматическую настройку? -- Исходный код SpringBoot (4)

После рассмотрения старого и изучения нового, давайте кратко рассмотрим содержание предыдущей статьи. В последней статье мы проанализировали соответствующий исходный код автоматической настройки SpringBoot. Автоматическая настройка соответствующего исходного кода в основном включает следующие важные шаги:

  1. Загрузите классы автоконфигурации из конфигурационного файла spring.factories;

  2. Исключено из загруженных классов автоконфигурации@EnableAutoConfigurationаннотированныйexcludeКласс автоконфигурации, указанный свойством;

  3. затем используйтеAutoConfigurationImportFilterИнтерфейс для фильтрации, соответствует ли класс автоконфигурации его аннотации аннотации (если есть аннотация)@ConditionalOnClass,@ConditionalOnBeanи@ConditionalOnWebApplicationЕсли все условия соблюдены, будет возвращен соответствующий результат;

  4. затем вызватьAutoConfigurationImportEventсобытие, расскажиConditionEvaluationReportОбъект отчета об оценке условий для записи соответствующих условий иexcludeКласс автоконфигурации.

  5. Наконец, spring импортирует окончательный отфильтрованный класс автоконфигурации в контейнер IOC.

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

Например: возьмем в качестве примера настройку порта сервера веб-проекта, если мы хотим настроить порт сервера как8081, тогда будемapplication.propertiesНастраивается в конфигурационном файлеserver.port=8081, в это время значение конфигурации8081будет привязан к@ConfigurationPropertiesАннотированный классServerPropertiesсвойстваportчтобы конфигурация вступила в силу.

2 @EnableConfigurationProperties

Давайте разберем каштаны, которые ставят перед нами серверный порт.Давайте посмотрим сначала непосредственно.ServerPropertiesИсходный код должен быть в состоянии найти запись исходного кода:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
	/**
	 * Server HTTP port.
	 */
	private Integer port;
	// ...省略非关键代码
}

можно увидеть,ServerPropertiesкласс отмечен@ConfigurationPropertiesЭта аннотация, префикс конфигурации свойства сервераserver, игнорировать ли неизвестные значения конфигурации (ignoreUnknownFields)Установить какtrue.

Тогда давайте посмотрим снова@ConfigurationPropertiesИсходный код этой аннотации:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
    
	// 前缀别名
	@AliasFor("prefix")
	String value() default "";
    
	// 前缀
	@AliasFor("value")
	String prefix() default "";
    
	// 忽略无效的配置属性
	boolean ignoreInvalidFields() default false;
    
	// 忽略未知的配置属性
	boolean ignoreUnknownFields() default true;
}

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

Вот автоматическая настройка сервера Естественно, давайте посмотрим на класс автоматической настройки.ServletWebServerFactoryAutoConfigurationИсходный код:

@Configuration
@EnableConfigurationProperties(ServerProperties.class)
// ...省略非关键注解
public class ServletWebServerFactoryAutoConfiguration {
	// ...省略非关键代码
}

Для акцента я поставилServletWebServerFactoryAutoConfigurationНекритичный код и некритические комментарии опущены. можно увидеть,ServletWebServerFactoryAutoConfigurationЕсть один в классе автоконфигурации@EnableConfigurationPropertiesAnnotation, а значение аннотации указано ранее.ServerProperties.class,следовательно@EnableConfigurationPropertiesАннотации, безусловно, находятся в центре нашего внимания.

Снова посмотри на@EnableConfigurationPropertiesАннотированный исходный код:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {

	// 这个值指定的类就是@ConfigurationProperties注解标注的类,其将会被注册到spring容器中
	Class<?>[] value() default {};

}

@EnableConfigurationPropertiesОсновная функция аннотации состоит в том, чтобы@ConfigurationPropertiesАннотированные классы обеспечивают поддержку привязки внешних значений свойств конфигурации (таких как значения конфигурации application.properties) к@ConfigurationPropertiesв свойствах отмеченного класса.

Уведомление: Исходный код SpringBoot также существуетConfigurationPropertiesAutoConfigurationЭтот класс автоконфигурации, в то время какspring.factoriesв файле конфигурацииEnableAutoConfigurationИнтерфейс тоже настроен.ConfigurationPropertiesAutoConfiguration, этот класс автоконфигурации также имеет@EnableConfigurationPropertiesС помощью этой аннотации привязка свойств кучи включена по умолчанию.

Так,@EnableConfigurationPropertiesКак эта аннотация поддерживает привязку свойств?

можно увидеть@EnableConfigurationPropertiesЭта заметка также отмечена@Import(EnableConfigurationPropertiesImportSelector.class), который импортируетEnableConfigurationPropertiesImportSelector, так что несомненно, что@EnableConfigurationPropertiesПоддержка, предоставляемая этой аннотацией для привязки свойств, должна следоватьEnableConfigurationPropertiesImportSelectorСвязанный.

здесь,EnableConfigurationPropertiesImportSelectorЭтот приятель — объект, который мы будем анализировать дальше, так что давайте продолжим анализEnableConfigurationPropertiesImportSelectorкак выполнить привязку значений свойств внешней конфигурации к@ConfigurationPropertiesв свойствах аннотированного класса.

3 EnableConfigurationPropertiesImportSelector

EnableConfigurationPropertiesImportSelectorРоль класса в основном используется для работы со связанной логикой привязки внешних свойств, которая реализуетImportSelectorИнтерфейсы, как известно, реализуютImportSelectorинтерфейсselectImportsспособ регистрации bean-компонентов в контейнере.

Итак, давайте посмотримEnableConfigurationPropertiesImportSelectorперезаписанныйselectImportsметод:

// EnableConfigurationPropertiesImportSelector.java

class EnableConfigurationPropertiesImportSelector implements ImportSelector {
        // IMPORTS数组即是要向spring容器中注册的bean
	private static final String[] IMPORTS = {
			ConfigurationPropertiesBeanRegistrar.class.getName(),
			ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

	@Override
	public String[] selectImports(AnnotationMetadata metadata) {
		// 返回ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar的全限定名
		// 即上面两个类将会被注册到Spring容器中
		return IMPORTS;
	}
	
}

можно увидетьEnableConfigurationPropertiesImportSelectorв классеselectImportsметод возвращаетIMPORTSмассив, и этоIMPORTSпредставляет собой массив констант, значения которыхConfigurationPropertiesBeanRegistrarиConfigurationPropertiesBindingPostProcessorRegistrar. которыйEnableConfigurationPropertiesImportSelectorРоль заключается в регистрации в контейнере Spring.ConfigurationPropertiesBeanRegistrarиConfigurationPropertiesBindingPostProcessorRegistrarэти двоеbean.

мы вEnableConfigurationPropertiesImportSelectorКласс не видит соответствующей логики для обработки внешней привязки свойств, он просто регистрируетConfigurationPropertiesBeanRegistrarиConfigurationPropertiesBindingPostProcessorRegistrarэти двоеbean, давайте посмотрим на два зарегистрированныхbeanсвоего рода.

4 ConfigurationPropertiesBeanRegistrar

Давайте сначала посмотримConfigurationPropertiesBeanRegistrarэтот класс.

ConfigurationPropertiesBeanRegistrarдаEnableConfigurationPropertiesImportSelectorвнутренний класс, который реализуетImportBeanDefinitionRegistrarинтерфейс, переопределенныйregisterBeanDefinitionsметод. видимый,ConfigurationPropertiesBeanRegistrarиспользуется для регистрации некоторыхbean definition, то есть кSpringЗарегистрируйте несколько бобов в контейнере.

Первый взглядConfigurationPropertiesBeanRegistrarИсходный код:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

public static class ConfigurationPropertiesBeanRegistrar
			implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,  // metadata是AnnotationMetadataReadingVisitor对象,存储了某个配置类的元数据
			BeanDefinitionRegistry registry) {
		// (1)得到@EnableConfigurationProperties注解的所有属性值,
		// 比如@EnableConfigurationProperties(ServerProperties.class),那么得到的值是ServerProperties.class
		// (2)然后再将得到的@EnableConfigurationProperties注解的所有属性值注册到容器中
		getTypes(metadata).forEach((type) -> register(registry,
				(ConfigurableListableBeanFactory) registry, type));
	}
}

существуетConfigurationPropertiesBeanRegistrarосуществленныйregisterBeanDefinitions, вы можете видеть, что две основные вещи сделаны:

  1. перечислитьgetTypesспособ получить@EnableConfigurationPropertiesЗначение атрибута аннотацииXxxProperties;
  2. перечислитьregisterЗначение свойства, которое получит методXxxPropertiesзарегистрироваться наSpringВ контейнере он используется при связывании с внешними свойствами позже.

Давайте взглянемgetTypesИсходный код метода:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

private List<Class<?>> getTypes(AnnotationMetadata metadata) {
	// 得到@EnableConfigurationProperties注解的所有属性值,
	// 比如@EnableConfigurationProperties(ServerProperties.class),那么得到的值是ServerProperties.class
	MultiValueMap<String, Object> attributes = metadata
			.getAllAnnotationAttributes(
					EnableConfigurationProperties.class.getName(), false);
	// 将属性值取出装进List集合并返回
	return collectClasses((attributes != null) ? attributes.get("value")
			: Collections.emptyList());
}

getTypesЛогика метода очень проста@EnableConfigurationPropertiesЗначения атрибутов в аннотацияхXxxProperties(НапримерServerProperties.class) вынуть и вставитьListсбор и возврат.

Зависит отgetTypesспособ получить@EnableConfigurationPropertiesЗначения атрибутов в аннотацияхXxxProperties(НапримерServerProperties.class), затем пройдя черезXxxPropertiesЗарегистрируйтесь по одномуSpringВ контейнере, давайте посмотримregisterметод:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

private void register(BeanDefinitionRegistry registry,
		ConfigurableListableBeanFactory beanFactory, Class<?> type) {
	// 得到type的名字,一般用类的全限定名作为bean name
	String name = getName(type);
	// 根据bean name判断beanFactory容器中是否包含该bean
	if (!containsBeanDefinition(beanFactory, name)) {
		// 若不包含,那么注册bean definition
		registerBeanDefinition(registry, name, type);
	}
}

Давайте посмотрим наEnableConfigurationPropertiesImportSelectorимпортировать другой классConfigurationPropertiesBindingPostProcessorRegistrarДля чего это?

5 ConfigurationPropertiesBindingPostProcessorRegistrar

можно увидетьConfigurationPropertiesBindingPostProcessorRegistrarИмя класса сноваRegistrarСлово заканчивается, указывая на то, что его нужно снова импортироватьbean definitionиз. Посмотрите прямо на исходный код:

// ConfigurationPropertiesBindingPostProcessorRegistrar.java

public class ConfigurationPropertiesBindingPostProcessorRegistrar
		implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
			BeanDefinitionRegistry registry) {
		// 若容器中没有注册ConfigurationPropertiesBindingPostProcessor这个处理属性绑定的后置处理器,
		// 那么将注册ConfigurationPropertiesBindingPostProcessor和ConfigurationBeanFactoryMetadata这两个bean
		// 注意onApplicationEnvironmentPreparedEvent事件加载配置属性在先,然后再注册一些后置处理器用来处理这些配置属性
		if (!registry.containsBeanDefinition(
				ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
			// (1)注册ConfigurationPropertiesBindingPostProcessor后置处理器,用来对配置属性进行后置处理
			registerConfigurationPropertiesBindingPostProcessor(registry);
			// (2)注册一个ConfigurationBeanFactoryMetadata类型的bean,
			// 注意ConfigurationBeanFactoryMetadata实现了BeanFactoryPostProcessor,然后其会在postProcessBeanFactory中注册一些元数据
			registerConfigurationBeanFactoryMetadata(registry);
		}
	}
	// 注册ConfigurationPropertiesBindingPostProcessor后置处理器
	private void registerConfigurationPropertiesBindingPostProcessor(
			BeanDefinitionRegistry registry) {
		GenericBeanDefinition definition = new GenericBeanDefinition();
		definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
		definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(
				ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);

	}
	// 注册ConfigurationBeanFactoryMetadata后置处理器
	private void registerConfigurationBeanFactoryMetadata(
			BeanDefinitionRegistry registry) {
		GenericBeanDefinition definition = new GenericBeanDefinition();
		definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
		definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME,
				definition);
	}

}

ConfigurationPropertiesBindingPostProcessorRegistrarЛогика класса очень проста, и в основном он используется для регистрации постпроцессора, связанного с привязкой внешних свойств конфигурации.ConfigurationBeanFactoryMetadataиConfigurationPropertiesBindingPostProcessor.

Затем давайте рассмотрим, какую логику постобработки выполняют два зарегистрированных постпроцессора?

6 ConfigurationBeanFactoryMetadata

Первый взглядConfigurationBeanFactoryMetadataЭто постпроцессор, реализующийBeanFactoryPostProcessorинтерфейсpostProcessBeanFactoryметод при инициализацииbean factoryкогда@BeanМетаданные аннотации сохраняются для использования в связанной логике последующей привязки свойств внешней конфигурации.

Давайте взглянемConfigurationBeanFactoryMetadataреализация классаBeanFactoryPostProcessorинтерфейсpostProcessBeanFactoryИсходный код метода:

// ConfigurationBeanFactoryMetadata

public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcessor {

	/**
	 * The bean name that this class is registered with.
	 */
	public static final String BEAN_NAME = ConfigurationBeanFactoryMetadata.class
			.getName();

	private ConfigurableListableBeanFactory beanFactory;
	/**
	 * beansFactoryMetadata集合存储beansFactory的元数据
	 * key:某个bean的名字  value:FactoryMetadata对象(封装了工厂bean名和工厂方法名)
	 * 比如下面这个配置类:
	 *
	 * @Configuration
	 * public class ConfigA {
	 *      @Bean
	 *      public BeanXXX methodB(configA, ) {
	 *          return new BeanXXX();
	 *      }
	 * }
	 *
	 * 那么:key值为"methodB",value为FactoryMetadata(configA, methodB)对象,其bean属性值为"configA",method属性值为"methodB"
	 */
	private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
			throws BeansException {
		this.beanFactory = beanFactory;
		// 遍历beanFactory的beanDefinitionName,即每个bean的名字(比如工厂方法对应的bean名字)
		for (String name : beanFactory.getBeanDefinitionNames()) {
			// 根据name得到beanDefinition
			BeanDefinition definition = beanFactory.getBeanDefinition(name);
			// 工厂方法名:一般是注解@Bean的方法名
			String method = definition.getFactoryMethodName();
			// 工厂bean名:一般是注解@Configuration的类名
			String bean = definition.getFactoryBeanName();
			if (method != null && bean != null) {
				// 将beanDefinitionName作为Key,封装了工厂bean名和工厂方法名的FactoryMetadata对象作为value装入beansFactoryMetadata中
				this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method));
			}
		}
	}
}

Из вышеуказанного кода можно увидетьConfigurationBeanFactoryMetadataкласс переопределенpostProcessBeanFactoryЧто делает метод, так это ставит фабрикуBean(можно понимать как@Configurationаннотированные классы) и их@BeanНекоторые метаданные аннотированного фабричного метода кэшируются вbeansFactoryMetadataСбор для последующего использования, о котором будет подробно рассказано позже.

Из приведенного выше кода мы видимConfigurationBeanFactoryMetadataКатегорияbeansFactoryMetadataТип коллекцииMap<String, FactoryMetadata>, тогда давайте взглянем на инкапсуляцию связанных фабричных метаданныхFactoryMetadataсвоего рода:

// ConfigurationBeanFactoryMetadata$FactoryMetadata.java

private static class FactoryMetadata {
	// @Configuration注解的配置类的类名
	private final String bean;
	// @Bean注解的方法名
	private final String method;

	FactoryMetadata(String bean, String method) {
		this.bean = bean;
		this.method = method;
	}

	public String getBean() {
		return this.bean;
	}

	public String getMethod() {
		return this.method;
	}

}

FactoryMetadataтолько два свойстваbeanиmethod,Соответственно@ConfigurationАннотированная фабрикаbeanи@BeanАннотированный фабричный метод.

Так много было сказано выше, будет более интуитивно давать каштан напрямую:

/**
 * beansFactoryMetadata集合存储beansFactory的元数据
 * key:某个bean的名字  value:FactoryMetadata对象(封装了工厂bean名和工厂方法名)
 * 比如下面这个配置类:
 *
 * @Configuration
 * public class ConfigA {
 *      @Bean
 *      public BeanXXX methodB(configA, ) {
 *          return new BeanXXX();
 *      }
 * }
 *
 * 那么:key值为"methodB",value为FactoryMetadata(configA, methodB)对象,其bean属性值为"configA",method属性值为"methodB"
 */
 private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();

Чтобы лучше понять вышеизложенноеbeansFactoryMetadataКакие данные хранятся в коллекции, рекомендуется отлаживать ее самостоятельно, чтобы посмотреть, что в ней. В любом случае, вот что нужно иметь в виду:ConfigurationBeanFactoryMetadataКатегорияbeansFactoryMetadataКоллекционные магазины - это фабрикиbeanсоответствующие метаданные дляConfigurationPropertiesBindingPostProcessorиспользуется в постпроцессоре.

7 ConfigurationPropertiesBindingPostProcessor

давайте посмотрим еще разConfigurationPropertiesBindingPostProcessorRegistrarЕще один постпроцессор для регистрации классовConfigurationPropertiesBindingPostProcessor, постпроцессор естьособенно важно, в основном отвечает заПривязать внешние свойства конфигурации к@ConfigurationPropertiesВ свойствах класса XxxProperties отмечен аннотацией(Напримерapplication.propertiesнастраивается в конфигурационном файлеserver.port=8081,Так8081будет привязан кServerPropertiesКатегорияportсвойства) логика реализации.

Аналогично, давайте посмотримConfigurationPropertiesBindingPostProcessorИсходный код:

// ConfigurationPropertiesBindingPostProcessor.java

public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
	PriorityOrdered, ApplicationContextAware, InitializingBean {
	@Override
	public void afterPropertiesSet() throws Exception {
	    // ...这里省略实现代码先
	}
	
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
	    // ...这里省略实现代码先
	}
	
	// ...省略非关键代码
}

можно увидетьConfigurationPropertiesBindingPostProcessorПостпроцессор реализует два важных интерфейсаInitializingBeanиBeanPostProcessor.

мы все знаем:

  1. InitializingBeanинтерфейсafterPropertiesSetметод будет вbeanВызывается после назначения свойства для выполнения некоторой пользовательской логики инициализации, такой как проверка того, были ли назначены некоторые обязательные свойства, проверка некоторых конфигураций или присвоение значений некоторым неназначенным свойствам.
  2. BeanPostProcessorинтерфейсbeanпостпроцессор, которыйpostProcessBeforeInitializationиpostProcessAfterInitializationДва метода ловушек, которые будут вbeanВызывается до и после инициализации для выполнения некоторой логики постобработки, такой как проверка интерфейса маркера или того, обернут ли он прокси-сервером.bean.

В то же время вы можете видеть из приведенного выше кодаConfigurationPropertiesBindingPostProcessorпостпроцессор перегруженInitializingBeanизafterPropertiesSetМетоды иBeanPostProcessorизpostProcessBeforeInitializationметод.

Далее мы будем исследоватьConfigurationPropertiesBindingPostProcessorИсходный код двух методов, переопределяемых постпроцессором.

7.1 Подготовьте соответствующие метаданные и настройте привязки свойств перед выполнением логики привязки внешних свойств

Давайте сначала проанализируемConfigurationPropertiesBindingPostProcessorперезаписыватьInitializingBeanинтерфейсafterPropertiesSetметод:

// ConfigurationPropertiesBindingPostProcessor.java

        /**
	 * 配置属性校验器名字
	 */
	public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
	/**
	 * 工厂bean相关元数据
	 */
	private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
	/**
	 * 上下文
	 */
	private ApplicationContext applicationContext;
	/**
	 * 配置属性绑定器
	 */
	private ConfigurationPropertiesBinder configurationPropertiesBinder;
	
	
    // 这里主要是给beanFactoryMetadata和configurationPropertiesBinder的属性赋值,用于后面的后置处理器方法处理属性绑定的时候用
	@Override
	public void afterPropertiesSet() throws Exception {
		// We can't use constructor injection of the application context because
		// it causes eager factory bean initialization
		// 【1】利用afterPropertiesSet这个勾子方法从容器中获取之前注册的ConfigurationBeanFactoryMetadata对象赋给beanFactoryMetadata属性
		// (问1)beanFactoryMetadata这个bean是什么时候注册到容器中的?
		// (答1)在ConfigurationPropertiesBindingPostProcessorRegistrar类的registerBeanDefinitions方法中将beanFactoryMetadata这个bean注册到容器中
		// (问2)从容器中获取beanFactoryMetadata对象后,什么时候会被用到?
		// (答2)beanFactoryMetadata对象的beansFactoryMetadata集合保存的工厂bean相关的元数据,在ConfigurationPropertiesBindingPostProcessor类
		//        要判断某个bean是否有FactoryAnnotation或FactoryMethod时会根据这个beanFactoryMetadata对象的beansFactoryMetadata集合的元数据来查找
		this.beanFactoryMetadata = this.applicationContext.getBean(
				ConfigurationBeanFactoryMetadata.BEAN_NAME,
				ConfigurationBeanFactoryMetadata.class);
		// 【2】new一个ConfigurationPropertiesBinder,用于后面的外部属性绑定时使用
		this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(
				this.applicationContext, VALIDATOR_BEAN_NAME); // VALIDATOR_BEAN_NAME="configurationPropertiesValidator"
	}

Вы можете видеть, что основная логика приведенного выше кода такова.Подготовьте соответствующие метаданные и настройте привязки свойств перед выполнением логики привязки внешних свойств., т.е. изSpringВ контейнере получается ранее зарегистрированный контейнерConfigurationBeanFactoryMetadataназначенный объектConfigurationPropertiesBindingPostProcessorпостпроцессорbeanFactoryMetadataсвойства и создать новыйConfigurationPropertiesBinderНастройте объект привязки свойства и назначьте егоconfigurationPropertiesBinderАтрибуты.

давайте посмотрим еще разConfigurationPropertiesBinderКак устроен этот объект связывания свойства конфигурации.

// ConfigurationPropertiesBinder.java

ConfigurationPropertiesBinder(ApplicationContext applicationContext,
		String validatorBeanName) {
	this.applicationContext = applicationContext;
	// 将applicationContext封装到PropertySourcesDeducer对象中并返回
	this.propertySources = new PropertySourcesDeducer(applicationContext)
			.getPropertySources(); // 获取属性源,主要用于在ConfigurableListableBeanFactory的后置处理方法postProcessBeanFactory中处理
	// 如果没有配置validator的话,这里一般返回的是null
	this.configurationPropertiesValidator = getConfigurationPropertiesValidator(
			applicationContext, validatorBeanName);
	// 检查实现JSR-303规范的bean校验器相关类在classpath中是否存在
	this.jsr303Present = ConfigurationPropertiesJsr303Validator
			.isJsr303Present(applicationContext);
}

можно увидеть в конструкцииConfigurationPropertiesBinderКогда объект в основном назначается связанным с ним свойствам (общая логика конструктора такова):

  1. даватьapplicationContextНазначение свойства вводится в объект контекста;
  2. даватьpropertySourcesНазначение атрибута, источником атрибута является внешнее значение конфигурации, такое какapplication.propertiesНастроенные значения свойств, обратите внимание, что источник свойства здесь определяетсяConfigFileApplicationListenerЭтот слушатель отвечает за чтение,ConfigFileApplicationListenerЭто будет подробно описано в главе об анализе исходного кода позже.
  3. даватьconfigurationPropertiesValidatorприсвоение свойства, значение исходит изSpringконтейнер с именемconfigurationPropertiesValidatorизbean.
  4. даватьjsr303Presentуступка имущества, когдаjavax.validation.Validator,javax.validation.ValidatorFactoryиjavax.validation.bootstrap.GenericBootstrap"Эти три класса существуют одновременно вclasspathсерединаjsr303Presentстоимость недвижимостиtrue.

О JSR303:JSR-303это подспецификация в JAVA EE 6, называемаяBean Validation,Hibernate ValidatorдаBean ValidationЭталонная реализация .Hibernate Validatorпри условииJSR 303Все встроенныеconstraintреализации, помимо некоторых дополнительныхconstraint.

7.2 Выполнение логики привязки реального внешнего свойства [основная строка]

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

Прежде чем выполнять логику внешней привязки атрибутов, после подготовки соответствующих метаданных и настройки привязки атрибутов, давайте взглянем на это время.ConfigurationPropertiesBindingPostProcessorвыполнитьBeanPostProcessorинтерфейсpostProcessBeforeInitializationметод постобработки,Логика привязки внешнего свойстваВсе они реализованы в этом методе постобработки, что нас и беспокоит.высший приоритет.

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

// ConfigurationPropertiesBindingPostProcessor.java

// 因为是外部配置属性后置处理器,因此这里对@ConfigurationProperties注解标注的XxxProperties类进行后置处理完成属性绑定
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
		throws BeansException {
	// 注意,BeanPostProcessor后置处理器默认会对所有的bean进行处理,因此需要根据bean的一些条件进行过滤得到最终要处理的目的bean,
	// 这里的过滤条件就是判断某个bean是否有@ConfigurationProperties注解
	// 【1】从bean上获取@ConfigurationProperties注解,若bean有标注,那么返回该注解;若没有,则返回Null。比如ServerProperty上标注了@ConfigurationProperties注解
	ConfigurationProperties annotation = getAnnotation(bean, beanName,
			ConfigurationProperties.class);
	// 【2】若标注有@ConfigurationProperties注解的bean,那么则进行进一步处理:将配置文件的配置注入到bean的属性值中
	if (annotation != null) {
		/********主线,重点关注】********/
		bind(bean, beanName, annotation); 
	}
	// 【3】返回外部配置属性值绑定后的bean(一般是XxxProperties对象)
	return bean;
}

ConfigurationPropertiesBindingPostProcessorкласс переопределенpostProcessBeforeInitializationЧто делает метод, так это привязывает конфигурацию внешнего свойства к@ConfigurationPropertiesаннотированныйXxxPropertiesНа уроке основные шаги резюмируются следующим образом:

  1. отbeanпродолжать@ConfigurationPropertiesаннотация;
  2. Если отмечено@ConfigurationPropertiesаннотированныйbean, затем дальнейшая обработка: привязать значение свойства внешней конфигурации к значению свойства bean-компонента, а затем вернутьbean; если не отмечен@ConfigurationPropertiesаннотированныйbean, то он вернется прямо как естьbean.

Уведомление: Задний процессор будет по умолчанию в каждом контейнереbeanПостобработка, т.к. здесь только для этикеток с@ConfigurationPropertiesаннотированныйbeanДелайте привязки внешних свойств, чтобы не было аннотаций@ConfigurationPropertiesаннотированныйbeanне будет обработано.

Далее идем по основной линии и еще раз смотрим на нееКак внешние свойства конфигурации связаны с@ConfigurationPropertiesаннотированныйXxxPropertiesА атрибуты класса?

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

// ConfigurationPropertiesBindingPostProcessor.java

private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
	// 【1】得到bean的类型,比如ServerPropertie这个bean得到的类型是:org.springframework.boot.autoconfigure.web.ServerProperties
	ResolvableType type = getBeanType(bean, beanName);
	// 【2】获取bean上标注的@Validated注解
	Validated validated = getAnnotation(bean, beanName, Validated.class);
	// 若标注有@Validated注解的话则跟@ConfigurationProperties注解一起组成一个Annotation数组
	Annotation[] annotations = (validated != null)
			? new Annotation[] { annotation, validated }
			: new Annotation[] { annotation };
	// 【3】返回一个绑定了XxxProperties类的Bindable对象target,这个target对象即被外部属性值注入的目标对象
	// (比如封装了标注有@ConfigurationProperties注解的ServerProperties对象的Bindable对象)
	Bindable<?> target = Bindable.of(type).withExistingValue(bean)
			.withAnnotations(annotations); // 设置annotations属性数组
	try {
		// 【4】执行外部配置属性绑定逻辑
		/********【主线,重点关注】********/
		this.configurationPropertiesBinder.bind(target);
	}
	catch (Exception ex) {
		throw new ConfigurationPropertiesBindException(beanName, bean, annotation,
				ex);
	}
}

Ключевые шаги выше кода были отмечены【x】, вот точка знаний, прежде чем продолжить объяснение основной логики привязки свойств внешней конфигурации (проанализировано в разделе 8 ConfigurationPropertiesBinder), помнитеConfigurationBeanFactoryMetadataперезаписанныйpostProcessBeanFactoryСвязанная фабрика была введена в методbeanМетаданные инкапсулируются вConfigurationBeanFactoryMetadataКатегорияbeansFactoryMetadataЭто что-то о коллекциях?

Давайте посмотрим на код выше【1】getBeanTypeи【2】getAnnotationИсходный код метода:

// ConfigurationPropertiesBindingPostProcessor.java

private ResolvableType getBeanType(Object bean, String beanName) {
	// 首先获取有没有工厂方法
	Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);
	// 若有工厂方法
	if (factoryMethod != null) { 
		return ResolvableType.forMethodReturnType(factoryMethod);
	} 
	// 没有工厂方法,则说明是普通的配置类
	return ResolvableType.forClass(bean.getClass());
}

private <A extends Annotation> A getAnnotation(Object bean, String beanName,
		Class<A> type) {
	A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type);
	if (annotation == null) {
		annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);
	}
	return annotation;
}

Обратите внимание на код вышеbeanFactoryMetadataнет объекта,ConfigurationPropertiesBindingPostProcessorпостпроцессорgetBeanTypeиgetAnnotationметод будет вызываться соответственноConfigurationBeanFactoryMetadataизfindFactoryMethodиfindFactoryAnnotationметод, в то время какConfigurationBeanFactoryMetadataизfindFactoryMethodиfindFactoryAnnotationМетод, в свою очередь, зависит от фабрики храненияbeanметаданныеbeansFactoryMetadataколлекция, чтобы найти, есть лиFactoryMethodиFactoryAnnotation. Итак, здесь мы знаем, чтоConfigurationBeanFactoryMetadataизbeansFactoryMetadataфабрика коллекционных магазиновbeanВ игру вступают метаданные.

8 ConfigurationPropertiesBinder

Давайте продолжим следовать основной линии привязки внешних атрибутов конфигурации и продолжим рассмотрение 7.2. Выполнение реальной логики привязки внешних атрибутов.this.configurationPropertiesBinder.bind(target);Этот код:

// ConfigurationPropertiesBinder.java

public void bind(Bindable<?> target) {
	//【1】得到@ConfigurationProperties注解
	ConfigurationProperties annotation = target
			.getAnnotation(ConfigurationProperties.class);
	Assert.state(annotation != null,
			() -> "Missing @ConfigurationProperties on " + target);
	// 【2】得到Validator对象集合,用于属性校验
	List<Validator> validators = getValidators(target);
	// 【3】得到BindHandler对象(默认是IgnoreTopLevelConverterNotFoundBindHandler对象),
	// 用于对ConfigurationProperties注解的ignoreUnknownFields等属性的处理
	BindHandler bindHandler = getBindHandler(annotation, validators);
	// 【4】得到一个Binder对象,并利用其bind方法执行外部属性绑定逻辑
	/********************【主线,重点关注】********************/
	getBinder().bind(annotation.prefix(), target, bindHandler);
}

Основная логика приведенного выше кода такова:

  1. стать первымtargetпредмет (соответствуетXxxPropertiesкласс) на@ConfigurationPropertiesАннотации и валидаторы (если есть);
  2. Затем по полученному@ConfigurationPropertiesаннотации и валидаторы для полученияBindHandlerобъект,BindHandlerЕго роль заключается в обработке некоторой логики прикрепления во время привязки свойств, проанализированной в разделе 8.1.
  3. Наконец получить одинBinderобъект, назовите егоbindметод для выполнения логики привязки внешних свойств, как описано в Разделе 8.2.

8.1 Получите объект BindHandler для обработки некоторой логики вложения, когда свойство привязано

мы наблюдаемgetBindHandlerДавайте сначала разберемся с логикой методаBindHandlerДля чего это.

BindHandlerИнтерфейс родительского класса, используемый для обработки некоторой логики вложения при привязке свойств. Давайте взглянемBindHandlerДиаграмма классов, хорошо иметь общее представление:

Рисунок 1

можно увидетьAbstractBindHandlerРеализован как абстрактный базовый классBindHandlerинтерфейс, который имеет четыре конкретных подкласса:IgnoreTopLevelConverterNotFoundBindHandler,NoUnboundElementsBindHandler,IgnoreErrorsBindHandlerиValidationBindHandler.

  1. IgnoreTopLevelConverterNotFoundBindHandler: по умолчанию при работе с привязками внешних свойств.BindHandler, который игнорирует самый верхний, когда привязка свойства не удаласьConverterNotFoundException;
  2. NoUnboundElementsBindHandler: Неизвестные свойства, используемые для обработки конфигураций файла конфигурации;
  3. IgnoreErrorsBindHandler: используется для игнорирования недопустимых свойств конфигурации, таких как ошибки типа;
  4. ValidationBindHandler: используйте валидатор для проверки значения результата привязки.

Проанализировав отношение классов, давайте посмотрим на него еще раз.BindHandlerКакие методы предоставляет интерфейс для предоставления дополнительной логики подключения при привязке внешних свойств, см. непосредственно код:

// BindHandler.java

public interface BindHandler {

	/**
	 * Default no-op bind handler.
	 */
	BindHandler DEFAULT = new BindHandler() {

	};

	// onStart方法在外部属性绑定前被调用
	default <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
			BindContext context) {
		return target;
	}

	// onSuccess方法在外部属性成功绑定时被调用,该方法能够改变最终返回的属性值或对属性值进行校验
	default Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
			BindContext context, Object result) {
		return result;
	}

	// onFailure方法在外部属性绑定失败(包括onSuccess方法里的逻辑执行失败)时被调用,
	// 该方法可以用来catch住相关异常或者返回一个替代的结果(跟微服务的降级结果有点类似,嘿嘿)
	default Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
			BindContext context, Exception error) throws Exception {
		throw error;
	}

	// 当外部属性绑定结束时(不管绑定成功还是失败)被调用
	default void onFinish(ConfigurationPropertyName name, Bindable<?> target,
			BindContext context, Object result) throws Exception {
	}
}

можно увидетьBindHandlerинтерфейс определяетonStart,onSuccess,onFailureиonFinishметод, эти четыре метода будут вызываться в разное время при выполнении привязки внешнего свойства и используются для добавления некоторой дополнительной логики обработки во время привязки свойства, например, вonSuccessМетод изменяет окончательное значение связанного свойства или проверяет значение свойства вonFailureметодcatchПерехватите соответствующее исключение или верните альтернативное значение связанного свойства.

понялBindHandlerПосле добавления некоторой дополнительной логики обработки вложений во время привязки атрибутов давайте посмотримgetBindHandlerЛогика метода, непосредственно в коде:

// ConfigurationPropertiesBinder.java

// 注意BindHandler的设计技巧,应该是责任链模式,非常巧妙,值得借鉴
private BindHandler getBindHandler(ConfigurationProperties annotation,
		List<Validator> validators) {
	// 新建一个IgnoreTopLevelConverterNotFoundBindHandler对象,这是个默认的BindHandler对象
	BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
	// 若注解@ConfigurationProperties的ignoreInvalidFields属性设置为true,
	// 则说明可以忽略无效的配置属性例如类型错误,此时新建一个IgnoreErrorsBindHandler对象
	if (annotation.ignoreInvalidFields()) {
		handler = new IgnoreErrorsBindHandler(handler);
	}
	// 若注解@ConfigurationProperties的ignoreUnknownFields属性设置为true,
	// 则说明配置文件配置了一些未知的属性配置,此时新建一个ignoreUnknownFields对象
	if (!annotation.ignoreUnknownFields()) {
		UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
		handler = new NoUnboundElementsBindHandler(handler, filter);
	}
	// 如果@Valid注解不为空,则创建一个ValidationBindHandler对象
	if (!validators.isEmpty()) {
		handler = new ValidationBindHandler(handler,
				validators.toArray(new Validator[0]));
	}
	// 遍历获取的ConfigurationPropertiesBindHandlerAdvisor集合,
	// ConfigurationPropertiesBindHandlerAdvisor目前只在测试类中有用到
	for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
		// 对handler进一步处理
		handler = advisor.apply(handler);
	}
	// 返回handler
	return handler;
}

getBindHandlerЛогика метода очень проста, в основном на основе входящего@ConfigurationPropertiesаннотации иvalidatorsвалидатор для создания различныхBindHandlerКонкретный класс реализации:

  1. во-первыхnewОдинIgnoreTopLevelConverterNotFoundBindHandlerпо умолчаниюBindHandler;
  2. как@ConfigurationPropertiesАннотированные свойстваignoreInvalidFieldsзначениеtrue, тогда сноваnewОдинIgnoreErrorsBindHandlerобъект, поместите вновь созданныйIgnoreTopLevelConverterNotFoundBindHandlerОбъект передается как параметр конструктора и присваиваетсяAbstractBindHandlerродительский классparentАтрибуты;
  3. как@ConfigurationPropertiesАннотированные свойстваignoreUnknownFieldsзначениеfalse, тогда сноваnewОдинUnboundElementsSourceFilterобъект, поставить ранее построенныйBindHandlerОбъект передается как параметр конструктора и присваиваетсяAbstractBindHandlerродительский классparentАтрибуты;
  4. ...и так далее, предыдущийhandlerобъект как последнийhangdlerПараметры построения объекта используются такAbstractBindHandlerродительский классparentатрибут будет каждыйhandlerцепь, и, наконец, получить окончательный построенныйhandler.

ПОЛУЧИТЬ трюки: Вам знаком приведенный выше шаблон проектирования?Модель цепочки ответственности. Когда мы изучаем исходный код, мы также узнаем, как другие умело используют шаблоны проектирования. Существует множество вариантов применения шаблона цепочки ответственности, например:Dubboвсе видыFilterих (например,AccessLogFilterОн используется для записи журнала доступа службы.ExceptionFilterиспользуется для обработки исключений...), когда мы впервые изучали Java WebServletизFilter,MyBatisизPluginони иNettyизPipelineОба принимают модель цепочки ответственности.

мы понимаемBindHandlerПосле роли свойства давайте проследим за основной строкой и посмотрим, как связана привязка свойства?

8.2 Получить объект Binder для привязки атрибута [основная строка]

Вот разметка в коде раздела 8 ConfigurationPropertiesBinder【4】основной кодgetBinder().bind(annotation.prefix(), target, bindHandler);.

Вы можете видеть, что этот код делает две вещи:

  1. перечислитьgetBinderспособ получить привязку свойстваBinderобъект;
  2. перечислитьBinderобъектbindспособ привязки внешних свойств к@ConfigurationPropertiesаннотированныйXxxPropertiesсвойства класса.

Итак, давайте посмотримgetBinderИсходный код метода:

// ConfigurationPropertiesBinder.java

private Binder getBinder() {
	// Binder是一个能绑定ConfigurationPropertySource的容器对象
	if (this.binder == null) {
		// 新建一个Binder对象,这个binder对象封装了ConfigurationPropertySources,
		// PropertySourcesPlaceholdersResolver,ConversionService和PropertyEditorInitializer对象
		this.binder = new Binder(getConfigurationPropertySources(), // 将PropertySources对象封装成SpringConfigurationPropertySources对象并返回
				getPropertySourcesPlaceholdersResolver(), getConversionService(), // 将PropertySources对象封装成PropertySourcesPlaceholdersResolver对象并返回,从容器中获取到ConversionService对象
				getPropertyEditorInitializer()); // 得到Consumer<PropertyEditorRegistry>对象,这些初始化器用来配置property editors,property editors通常可以用来转换值
	}
	// 返回binder
	return this.binder;
}

можно увидетьBinderобъект инкапсулируетConfigurationPropertySources,PropertySourcesPlaceholdersResolver,ConversionServiceиPropertyEditorInitializerЭти четыре объекта,BinderОбъект инкапсулирует этих четырех друзей, которые должны использоваться в более поздней логике привязки атрибутов Давайте сначала посмотрим, что делают эти четыре объекта:

  • ConfigurationPropertySources: Источник свойств для внешних файлов конфигурации,ConfigFileApplicationListenerСлушатель отвечает за запуск чтения;
  • PropertySourcesPlaceholdersResolver: разрешить заполнители в источниках свойств${};
  • ConversionService: преобразование типа свойства
  • PropertyEditorInitializer: используется для настройкиproperty editors

Итак, мы получаемBinderПосле связующего свойства посмотрите на егоbindКак методы выполняют привязку свойств.

// Binder.java

public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) {
	// ConfigurationPropertyName.of(name):将name(这里指属性前缀名)封装到ConfigurationPropertyName对象中
	// 将外部配置属性绑定到目标对象target中
	return bind(ConfigurationPropertyName.of(name), target, handler); 
}

public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target,
		BindHandler handler) {
	Assert.notNull(name, "Name must not be null");
	Assert.notNull(target, "Target must not be null");
	handler = (handler != null) ? handler : BindHandler.DEFAULT;
	// Context是Binder的内部类,实现了BindContext,Context可以理解为Binder的上下文,可以用来获取binder的属性比如Binder的sources属性
	Context context = new Context();
	// 进行属性绑定,并返回绑定属性后的对象bound,注意bound的对象类型是T,T就是@ConfigurationProperties注解的类比如ServerProperties
	/********【主线,重点关注】************/
	T bound = bind(name, target, handler, context, false);
	// 将刚才返回的bound对象封装到BindResult对象中并返回
	return BindResult.of(bound);
}

Приведенный выше код сначала создаетContextобъект,ContextдаBinderвнутренний класс дляBinderконтекст, используяContextможно получить контекстBinderтакие свойства, как получитьBinderизsourcesзначение свойства и привязка кXxxPropertiesв свойствах. Тогда мы будем следовать основной линии bind(name, target, handler, context, false)Исходный код метода:

// Binder.java

protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
		BindHandler handler, Context context, boolean allowRecursiveBinding) {
	// 清空Binder的configurationProperty属性值
	context.clearConfigurationProperty(); 
	try { 
		// 【1】调用BindHandler的onStart方法,执行一系列的责任链对象的该方法
		target = handler.onStart(name, target, context);
		if (target == null) {
			return null;
		}// 【2】调用bindObject方法对Bindable对象target的属性进行绑定外部配置的值,并返回赋值给bound对象。
		// 举个栗子:比如设置了server.port=8888,那么该方法最终会调用Binder.bindProperty方法,最终返回的bound的value值为8888
		/************【主线:重点关注】***********/
		Object bound = bindObject(name, target, handler, context, 
				allowRecursiveBinding);
		// 【3】封装handleBindResult对象并返回,注意在handleBindResult的构造函数中会调用BindHandler的onSucess,onFinish方法
		return handleBindResult(name, target, handler, context, bound); 
	}
	catch (Exception ex) {
		return handleBindError(name, target, handler, context, ex);
	}
}

Комментарии к приведенному выше коду были очень подробными и не будут здесь подробно останавливаться. Давайте посмотрим на основной потокbindObjectИсходный код метода:

// Binder.java

private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target,
		BindHandler handler, Context context, boolean allowRecursiveBinding) {
	// 从propertySource中的配置属性,获取ConfigurationProperty对象property即application.properties配置文件中若有相关的配置的话,
	// 那么property将不会为null。举个栗子:假如你在配置文件中配置了spring.profiles.active=dev,那么相应property值为dev;否则为null
	ConfigurationProperty property = findProperty(name, context);
	// 若property为null,则不会执行后续的属性绑定相关逻辑
	if (property == null && containsNoDescendantOf(context.getSources(), name)) {
		// 如果property == null,则返回null
		return null;
	}
	// 根据target类型获取不同的Binder,可以是null(普通的类型一般是Null),MapBinder,CollectionBinder或ArrayBinder
	AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
	// 若aggregateBinder不为null比如配置了spring.profiles属性(当然包括其子属性比如spring.profiles.active等)
	if (aggregateBinder != null) {
		// 若aggregateBinder不为null,则调用bindAggregate并返回绑定后的对象
		return bindAggregate(name, target, handler, context, aggregateBinder);
	}
	// 若property不为null
	if (property != null) {
		try {
			// 绑定属性到对象中,比如配置文件中设置了server.port=8888,那么将会最终调用bindProperty方法进行属性设置
			return bindProperty(target, context, property);
		}
		catch (ConverterNotFoundException ex) {
			// We might still be able to bind it as a bean
			Object bean = bindBean(name, target, handler, context,
					allowRecursiveBinding);
			if (bean != null) {
				return bean;
			}
			throw ex;
		}
	}
	// 只有@ConfigurationProperties注解的类进行外部属性绑定才会走这里
	/***********************【主线,重点关注】****************************/
	return bindBean(name, target, handler, context, allowRecursiveBinding);
}

Как видно из кода вышеbindObjectЛогика выполнения привязки атрибутов в атрибутах будет включать различную логику привязки в соответствии с разными типами атрибутов, например:

  1. application.propertiesнастраивается в конфигурационном файлеspring.profiles.active=dev, то войдетreturn bindAggregate(name, target, handler, context, aggregateBinder);Логика кода привязки этого свойства;
  2. application.propertiesнастраивается в конфигурационном файлеserver.port=8081, то войдетreturn bindBean(name, target, handler, context, allowRecursiveBinding);Логика привязки свойств.

Итак, мы снова идем по основной линии и вводим@ConfigurationPropertiesаннотированныйXxxPropertiesв логике привязки свойств классаbindBeanВ методе:

// Binder.java

private Object bindBean(ConfigurationPropertyName name, Bindable<?> target, // name指的是ConfigurationProperties的前缀名
		BindHandler handler, Context context, boolean allowRecursiveBinding) {
	// 这里做一些ConfigurationPropertyState的相关检查
	if (containsNoDescendantOf(context.getSources(), name)
			|| isUnbindableBean(name, target, context)) {
		return null;
	}// 这里新建一个BeanPropertyBinder的实现类对象,注意这个对象实现了bindProperty方法
	BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
			name.append(propertyName), propertyTarget, handler, context, false);
	/**
	 * (propertyName, propertyTarget) -> bind(
	 * 				name.append(propertyName), propertyTarget, handler, context, false);
	 * 	等价于
	 * 	new BeanPropertyBinder() {
	 *		Object bindProperty(String propertyName, Bindable<?> target){
	 *			bind(name.append(propertyName), propertyTarget, handler, context, false);
	 *		}
	 * 	}
	 */
	// type类型即@ConfigurationProperties注解标注的XxxProperties类
	Class<?> type = target.getType().resolve(Object.class);
	if (!allowRecursiveBinding && context.hasBoundBean(type)) {
		return null;
	}
	// 这里应用了java8的lambda语法,作为没怎么学习java8的lambda语法的我,不怎么好理解下面的逻辑,哈哈
	// 真正实现将外部配置属性绑定到@ConfigurationProperties注解的XxxProperties类的属性中的逻辑应该就是在这句lambda代码了
	/*******************【主线】***************************/
	return context.withBean(type, () -> {
		Stream<?> boundBeans = BEAN_BINDERS.stream()
				.map((b) -> b.bind(name, target, context, propertyBinder));
		return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
	});
	// 根据上面的lambda语句翻译如下:
	/** 这里的T指的是各种属性绑定对象,比如ServerProperties
	 * return context.withBean(type, new Supplier<T>() {
	 * 	T get() {
	 * 		Stream<?> boundBeans = BEAN_BINDERS.stream()
	 * 					.map((b) -> b.bind(name, target, context, propertyBinder));
	 * 			return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
	 *        }
	 *  });
	 */
}

Из приведенного выше кода мы переходим к нижней части свойств внешней конфигурации, привязанных кXxxPropertiesКод более низкого уровня в атрибуте класса, вы можете видеть, что логика привязки атрибута должна быть отмечена в приведенном выше коде.【主线】изlambdaКод здесь. Я не буду здесь вдаваться в подробности, потому что эта привязка свойств принадлежит SpringBoot.Binderкатегория,BinderСвязанные классы появились только в Spring Boot 2.0, то есть предыдущий код, связанный с привязкой свойств, был отменен и переписан. Есть также больше исходных кодов, связанных с привязкой атрибутов, и необходимо открыть другую статью для анализа и изучения в будущем.

9 Резюме

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

  1. прежде всего@EnableConfigurationPropertiesаннотацияimportохватыватьEnableConfigurationPropertiesImportSelectorпостпроцессор;
  2. EnableConfigurationPropertiesImportSelectorпостпроцессор вSpringзарегистрирован в контейнереConfigurationPropertiesBeanRegistrarиConfigurationPropertiesBindingPostProcessorRegistrarэти двоеbean;
  3. вConfigurationPropertiesBeanRegistrarВ направленииSpringзарегистрирован в контейнереXxxPropertiesТипbean;ConfigurationPropertiesBindingPostProcessorRegistrarВ направленииSpringзарегистрирован в контейнереConfigurationBeanFactoryMetadataиConfigurationPropertiesBindingPostProcessorдва постпроцессора;
  4. ConfigurationBeanFactoryMetadataПостпроцессор инициализируетсяbean factoryкогда@BeanМетаданные аннотации сохраняются для использования в связанной логике последующей привязки атрибутов внешней конфигурации;
  5. ConfigurationPropertiesBindingPostProcessorПостпроцессоры привязывают значения свойств внешней конфигурации кXxxPropertiesЛогика свойства класса делегируетсяConfigurationPropertiesBinderобъект, тоConfigurationPropertiesBinderОбъект, в свою очередь, в конечном итоге делегирует логику привязки свойстваBinderобъект для завершения.

Видно, что главное вышеШаг 5.

PS: Изначально я планировал начать анализ процесса запуска SpringBoot в этой статье, но я вернулся и посмотрел на соответствующий исходный код автоматической настройки, и там довольно много непроанализированных, так что начнем с очередной волны исходный код, связанный с автоматической настройкой.

В связи с ограниченным уровнем автора, если есть ошибки в тексте, просьба указать, спасибо.

Ссылаться на: 1,JSR-303