Примечание. Анализ исходного кода соответствует версии SpringBoot 2.1.0.RELEASE.
1. Введение
Эта статья продолжаетКак SpringBoot реализует автоматическую настройку? -- Исходный код SpringBoot (4)
После рассмотрения старого и изучения нового, давайте кратко рассмотрим содержание предыдущей статьи. В последней статье мы проанализировали соответствующий исходный код автоматической настройки SpringBoot. Автоматическая настройка соответствующего исходного кода в основном включает следующие важные шаги:
-
Загрузите классы автоконфигурации из конфигурационного файла spring.factories;
-
Исключено из загруженных классов автоконфигурации
@EnableAutoConfiguration
аннотированныйexclude
Класс автоконфигурации, указанный свойством; -
затем используйте
AutoConfigurationImportFilter
Интерфейс для фильтрации, соответствует ли класс автоконфигурации его аннотации аннотации (если есть аннотация)@ConditionalOnClass
,@ConditionalOnBean
и@ConditionalOnWebApplication
Если все условия соблюдены, будет возвращен соответствующий результат; -
затем вызвать
AutoConfigurationImportEvent
событие, расскажиConditionEvaluationReport
Объект отчета об оценке условий для записи соответствующих условий иexclude
Класс автоконфигурации. -
Наконец, 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
Есть один в классе автоконфигурации@EnableConfigurationProperties
Annotation, а значение аннотации указано ранее.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
, вы можете видеть, что две основные вещи сделаны:
- перечислить
getTypes
способ получить@EnableConfigurationProperties
Значение атрибута аннотацииXxxProperties
; - перечислить
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
.
мы все знаем:
-
InitializingBean
интерфейсafterPropertiesSet
метод будет вbean
Вызывается после назначения свойства для выполнения некоторой пользовательской логики инициализации, такой как проверка того, были ли назначены некоторые обязательные свойства, проверка некоторых конфигураций или присвоение значений некоторым неназначенным свойствам. -
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
Когда объект в основном назначается связанным с ним свойствам (общая логика конструктора такова):
- давать
applicationContext
Назначение свойства вводится в объект контекста; - давать
propertySources
Назначение атрибута, источником атрибута является внешнее значение конфигурации, такое какapplication.properties
Настроенные значения свойств, обратите внимание, что источник свойства здесь определяетсяConfigFileApplicationListener
Этот слушатель отвечает за чтение,ConfigFileApplicationListener
Это будет подробно описано в главе об анализе исходного кода позже. - давать
configurationPropertiesValidator
присвоение свойства, значение исходит изSpring
контейнер с именемconfigurationPropertiesValidator
изbean
. - давать
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
На уроке основные шаги резюмируются следующим образом:
- от
bean
продолжать@ConfigurationProperties
аннотация; - Если отмечено
@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);
}
Основная логика приведенного выше кода такова:
- стать первым
target
предмет (соответствуетXxxProperties
класс) на@ConfigurationProperties
Аннотации и валидаторы (если есть); - Затем по полученному
@ConfigurationProperties
аннотации и валидаторы для полученияBindHandler
объект,BindHandler
Его роль заключается в обработке некоторой логики прикрепления во время привязки свойств, проанализированной в разделе 8.1. - Наконец получить один
Binder
объект, назовите егоbind
метод для выполнения логики привязки внешних свойств, как описано в Разделе 8.2.
8.1 Получите объект BindHandler для обработки некоторой логики вложения, когда свойство привязано
мы наблюдаемgetBindHandler
Давайте сначала разберемся с логикой методаBindHandler
Для чего это.
BindHandler
Интерфейс родительского класса, используемый для обработки некоторой логики вложения при привязке свойств. Давайте взглянемBindHandler
Диаграмма классов, хорошо иметь общее представление:
можно увидетьAbstractBindHandler
Реализован как абстрактный базовый классBindHandler
интерфейс, который имеет четыре конкретных подкласса:IgnoreTopLevelConverterNotFoundBindHandler
,NoUnboundElementsBindHandler
,IgnoreErrorsBindHandler
иValidationBindHandler
.
-
IgnoreTopLevelConverterNotFoundBindHandler
: по умолчанию при работе с привязками внешних свойств.BindHandler
, который игнорирует самый верхний, когда привязка свойства не удаласьConverterNotFoundException
; -
NoUnboundElementsBindHandler
: Неизвестные свойства, используемые для обработки конфигураций файла конфигурации; -
IgnoreErrorsBindHandler
: используется для игнорирования недопустимых свойств конфигурации, таких как ошибки типа; -
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
Конкретный класс реализации:
- во-первых
new
ОдинIgnoreTopLevelConverterNotFoundBindHandler
по умолчаниюBindHandler
; - как
@ConfigurationProperties
Аннотированные свойстваignoreInvalidFields
значениеtrue
, тогда сноваnew
ОдинIgnoreErrorsBindHandler
объект, поместите вновь созданныйIgnoreTopLevelConverterNotFoundBindHandler
Объект передается как параметр конструктора и присваиваетсяAbstractBindHandler
родительский классparent
Атрибуты; - как
@ConfigurationProperties
Аннотированные свойстваignoreUnknownFields
значениеfalse
, тогда сноваnew
ОдинUnboundElementsSourceFilter
объект, поставить ранее построенныйBindHandler
Объект передается как параметр конструктора и присваиваетсяAbstractBindHandler
родительский классparent
Атрибуты; - ...и так далее, предыдущий
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);
.
Вы можете видеть, что этот код делает две вещи:
- перечислить
getBinder
способ получить привязку свойстваBinder
объект; - перечислить
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
Логика выполнения привязки атрибутов в атрибутах будет включать различную логику привязки в соответствии с разными типами атрибутов, например:
-
application.properties
настраивается в конфигурационном файлеspring.profiles.active=dev
, то войдетreturn bindAggregate(name, target, handler, context, aggregateBinder);
Логика кода привязки этого свойства; -
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
На этом анализ исходного кода по атрибутам класса заканчивается. Это довольно длинная статья. Не знаю, ясно ли я выразился. Важные шаги приведены ниже:
- прежде всего
@EnableConfigurationProperties
аннотацияimport
охватыватьEnableConfigurationPropertiesImportSelector
постпроцессор; -
EnableConfigurationPropertiesImportSelector
постпроцессор вSpring
зарегистрирован в контейнереConfigurationPropertiesBeanRegistrar
иConfigurationPropertiesBindingPostProcessorRegistrar
эти двоеbean
; - в
ConfigurationPropertiesBeanRegistrar
В направленииSpring
зарегистрирован в контейнереXxxProperties
Типbean
;ConfigurationPropertiesBindingPostProcessorRegistrar
В направленииSpring
зарегистрирован в контейнереConfigurationBeanFactoryMetadata
иConfigurationPropertiesBindingPostProcessor
два постпроцессора; -
ConfigurationBeanFactoryMetadata
Постпроцессор инициализируетсяbean
factory
когда@Bean
Метаданные аннотации сохраняются для использования в связанной логике последующей привязки атрибутов внешней конфигурации; -
ConfigurationPropertiesBindingPostProcessor
Постпроцессоры привязывают значения свойств внешней конфигурации кXxxProperties
Логика свойства класса делегируетсяConfigurationPropertiesBinder
объект, тоConfigurationPropertiesBinder
Объект, в свою очередь, в конечном итоге делегирует логику привязки свойстваBinder
объект для завершения.
Видно, что главное вышеШаг 5.
PS: Изначально я планировал начать анализ процесса запуска SpringBoot в этой статье, но я вернулся и посмотрел на соответствующий исходный код автоматической настройки, и там довольно много непроанализированных, так что начнем с очередной волны исходный код, связанный с автоматической настройкой.
В связи с ограниченным уровнем автора, если есть ошибки в тексте, просьба указать, спасибо.
Ссылаться на: 1,JSR-303