Таким образом, вы сможете понять принцип автоматической настройки SpringBoot.

Spring Boot Java
Таким образом, вы сможете понять принцип автоматической настройки SpringBoot.

предисловие

Вы, ребята, помните страх оказаться во власти SSM-интеграции? Я считаю, что у многих мелких партнеров был такой опыт, много проблем с конфигурацией, различные проверки исключений, импорт новой зависимости и добавление новой конфигурации. Начиная с SpringBoot, мы взлетели! Все виды нулевой конфигурации доступны из коробки, и причина, по которой мы можем так хорошо ее разработать,Автоматическая конфигурацияКредит непременный.Сегодня мы обсудим принцип автоматической настройки SpringBoot.

Эта статья в основном разделена на три части:

  1. Краткое изложение общих аннотаций в исходном коде SpringBoot

  2. Процесс запуска SpringBoot

  3. Принцип автоматической настройки SpringBoot

1. Дополнения общих аннотаций в исходном коде SpringBoot

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

Объединение аннотаций

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

  • Мета-аннотация: аннотация, которую можно аннотировать к другим аннотациям.

  • Комбинированные аннотации. Аннотированные аннотации называются комбинированными аннотациями.

@Value [предоставлено Spring]

@ValueЭто эквивалентно полю значения в традиционном файле конфигурации xml.

Предполагая, что код существует:

@Component 
public class Person { 

@Value("i am name") 
private String name; 

} 

Приведенный выше код эквивалентен файлу конфигурации:

<bean class="Person"> 
<property name ="name" value="i am name"></property>
</bean> 

Мы знаем, что значение параметра в конфигурационном файле может быть:

  • буквальный

  • пройти через${key}способ получить значение из переменной окружения

  • пройти через${key}способ получить значение в файле глобальной конфигурации

  • #{SpEL}

Итак, мы можем пройти@Value(${key})способ получения указанного элемента конфигурации в файле глобальной конфигурации.

@ConfigurationProperties [предоставлено SpringBoot]

Если нам нужно получить N элементов конфигурации, нам нужно получить элементы конфигурации один за другим через @Value, что кажется немного низким. мы можем использовать@ConfigurationProperties.

отмечены@ConfigurationPropertiesВсе свойства класса привязаны к соответствующим элементам конфигурации в файле конфигурации. (По умолчанию значение конфигурации берется из файла глобальной конфигурации.) После привязки мы можем получить доступ к значению свойства в файле глобальной конфигурации через этот класс.

Давайте посмотрим на пример:

  1. Добавьте следующую конфигурацию в основной файл конфигурации
person.name=kundy 
person.age=13 
person.sex=male 
  1. Чтобы создать класс конфигурации, методы setter и getter здесь опущены из-за нехватки места, но это необходимо в фактической разработке, иначе его нельзя будет успешно внедрить. Кроме того, необходимо добавить аннотацию @Component.
@Component 
@ConfigurationProperties(prefix = "person") 
public class Person { 

private String name; 
private Integer age; 
private String sex; 

} 

Здесь @ConfigurationProperties имеетprefixПараметр в основном используется для указания префикса элемента конфигурации в файле конфигурации.

  1. Для тестирования в среде SpringBoot напишите тестовый метод и внедрите класс Person, чтобы получить значение файла конфигурации через объект Person.

@Import [предоставлено Spring]

@ImportАннотация поддерживает импорт обычного класса Java и объявление его как bean-компонента. В основном используется для объединения нескольких разрозненных классов конфигурации Java в более крупный класс конфигурации.

  • @ImportАннотации до версии 4.2 поддерживали только импорт классов конфигурации.

  • после 4.2@ImportАннотация поддерживает импорт обычного класса Java и объявление его как bean-компонента.

**@Импортировать три способа использования **

  • Прямой импорт обычных классов Java.

  • Используйте с пользовательским ImportSelector.

  • Используется с ImportBeanDefinitionRegistrar.

1. Прямой импорт обычных классов Java

  1. Создайте обычный класс Java.
public class Circle { 

public void sayHi() { 
System.out.println("Circle sayHi()"); 
} 

} 
  1. Создайте класс конфигурации без явного объявления каких-либо bean-компонентов и импортируйте только что созданный Circle.
@Import({Circle.class}) 
@Configuration 
public class MainConfig { 

} 
  1. Создайте тестовый класс.
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); 
Circle circle = context.getBean(Circle.class); 
circle.sayHi(); 

} 
  1. результат операции:

Circle sayHi()

Видно, что мы успешно получили объект Circle из контейнера IOC, что доказывает, что класс Circle, который мы импортировали в класс конфигурации, действительно объявлен как bean-компонент.

2. Используйте с пользовательским ImportSelector

ImportSelector— это интерфейс только с одним методом selectImports, который возвращает массив полных имен классов. Таким образом, используя эту функцию, мы можем дать контейнеруДинамически импортировать NБоб.

  1. Создайте простой треугольник класса Java.
public class Triangle { 

public void sayHi(){ 
System.out.println("Triangle sayHi()"); 
} 

}
  1. Создайте класс реализации ImportSelector, selectImports возвращает полное имя класса Triangle.
public class MyImportSelector implements ImportSelector { 

@Override 
public String[] selectImports(AnnotationMetadata annotationMetadata) { 
return new String[]{"annotation.importannotation.waytwo.Triangle"}; 
} 

} 
  1. Создайте класс конфигурации и импортируйте MyImportSelector на исходной основе.
@Import({Circle.class,MyImportSelector.class}) 
@Configuration 
public class MainConfigTwo { 

} 
  1. Создать тестовый класс
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigTwo.class); 
Circle circle = context.getBean(Circle.class); 
Triangle triangle = context.getBean(Triangle.class); 
circle.sayHi(); 
triangle.sayHi(); 

} 
  1. результат операции:

Circle sayHi()

Triangle sayHi()

Видно, что объект Triangle также был успешно создан IOC-контейнером.

3. Используйте с ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrarтакже интерфейс, который можетВручную зарегистрировать бобы в контейнере, чтобы мы могли персонализировать класс. (Требуется использование с @Import и @Configuration.)

  1. Создайте простой класс Java Rectangle.
public class Rectangle { 

public void sayHi() { 
System.out.println("Rectangle sayHi()"); 
} 

}
  1. Создайте класс реализации ImportBeanDefinitionRegistrar, и метод реализации непосредственно вручную зарегистрирует именованный прямоугольник компонента в контейнере IOC.
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { 

@Override 
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { 

RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rectangle.class); 
// 注册一个名字叫做 rectangle 的 bean 
beanDefinitionRegistry.registerBeanDefinition("rectangle", rootBeanDefinition); 
} 

} 
  1. Создайте класс конфигурации и импортируйте класс MyImportBeanDefinitionRegistrar.
@Import({Circle.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) 
@Configuration 
public class MainConfigThree { 

} 
  1. Создайте тестовый класс.
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigThree.class); 
Circle circle = context.getBean(Circle.class); 
Triangle triangle = context.getBean(Triangle.class); 
Rectangle rectangle = context.getBean(Rectangle.class); 
circle.sayHi(); 
triangle.sayHi(); 
rectangle.sayHi(); 

} 
  1. результат операции

Circle sayHi()

Triangle sayHi()

Rectangle sayHi()

Ну и объект Rectangle тоже прописан.

@Conditional [предоставлено Spring]

> @ConditionalАннотации позволяют включать некоторые конфигурации только при соблюдении определенных условий.

Давайте рассмотрим простой пример:

  1. Создайте обычный Java-класс ConditionBean, который в основном используется для проверки успешности загрузки компонента.
public class ConditionBean { 

public void sayHi() { 
System.out.println("ConditionBean sayHi()"); 
} 

} 
  1. Создайте класс реализации Condition. Аннотация @Conditional имеет только один параметр типа Condition. Condition — это интерфейс только с одним методом match(), который возвращает логическое значение. Если метод возвращает true, условие выполняется, и класс конфигурации принимает эффект. В противном случае он не вступит в силу. В этом примере мы просто возвращаем true.
public class MyCondition implements Condition { 

@Override 
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { 
return true; 
} 

} 
  1. Создайте класс конфигурации, вы можете видеть, что @Conditional конфигурации передает класс реализации Condition, который мы только что создали, в него для условного суждения.
@Configuration 
@Conditional(MyCondition.class) 
public class ConditionConfig { 

@Bean 
public ConditionBean conditionBean(){ 
return new ConditionBean(); 
} 

} 
  1. Напишите методы тестирования.
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class); 
ConditionBean conditionBean = context.getBean(ConditionBean.class); 
conditionBean.sayHi(); 

} 
  1. Анализ результатов

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

Помимо настройки Condition, Spring также расширяет для нас некоторые часто используемые Condition.

Расширенная аннотация эффект
ConditionalOnBean Если указанный bean-компонент существует в контейнере, он вступит в силу.
ConditionalOnMissingBean Если указанный bean-компонент не существует в контейнере, он вступит в силу.
ConditionalOnClass Если в системе есть указанный класс, он вступит в силу.
ConditionalOnMissingClass Если в системе нет указанного класса, он вступит в силу.
ConditionalOnProperty Имеет ли свойство, указанное в системе, указанное значение.
ConditionalOnWebApplication Текущая веб-среда вступит в силу.

2. Процесс запуска SpringBoot

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

  • ApplicationContextInitializer

  • ApplicationRunner

  • CommandLineRunner

  • SpringApplicationRunListener

Приступим к анализу исходного кода, начнем с метода run() стартового класса SpringBoot, цепочка вызовов следующая:SpringApplication.run() -> run(new Class[]{primarySource}, args) -> new SpringApplication(primarySources)).run(args).

Он работал все время, и, наконец, это точка, давайте посмотрим на это напрямуюnew SpringApplication(primarySources)).run(args)Сюда.

1.png

Вышеупомянутый метод в основном включает два этапа:

  • Создайте объект SpringApplication.

  • Запустите метод run().

Создайте объект SpringApplication

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { 

this.sources = new LinkedHashSet(); 
this.bannerMode = Mode.CONSOLE; 
this.logStartupInfo = true; 
this.addCommandLineProperties = true; 
this.addConversionService = true; 
this.headless = true; 
this.registerShutdownHook = true; 
this.additionalProfiles = new HashSet(); 
this.isCustomEnvironment = false; 
this.resourceLoader = resourceLoader; 
Assert.notNull(primarySources, "PrimarySources must not be null"); 
// 保存主配置类(这里是一个数组,说明可以有多个主配置类) 
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); 
// 判断当前是否是一个 Web 应用 
this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
// 从类路径下找到 META/INF/Spring.factories 配置的所有 ApplicationContextInitializer,然后保存起来 
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); 
// 从类路径下找到 META/INF/Spring.factories 配置的所有 ApplicationListener,然后保存起来 
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); 
// 从多个配置类中找到有 main 方法的主配置类(只有一个) 
this.mainApplicationClass = this.deduceMainApplicationClass(); 

} 

Запустите метод run()

public ConfigurableApplicationContext run(String... args) { 

// 创建计时器 
StopWatch stopWatch = new StopWatch(); 
stopWatch.start(); 
// 声明 IOC 容器 
ConfigurableApplicationContext context = null; 
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); 
this.configureHeadlessProperty(); 
// 从类路径下找到 META/INF/Spring.factories 获取 SpringApplicationRunListeners 
SpringApplicationRunListeners listeners = this.getRunListeners(args); 
// 回调所有 SpringApplicationRunListeners 的 starting() 方法 
listeners.starting(); 
Collection exceptionReporters; 
try { 
// 封装命令行参数 
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 
// 准备环境,包括创建环境,创建环境完成后回调 SpringApplicationRunListeners#environmentPrepared()方法,表示环境准备完成 
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); 
this.configureIgnoreBeanInfo(environment); 
// 打印 Banner 
Banner printedBanner = this.printBanner(environment); 
// 创建 IOC 容器(决定创建 web 的 IOC 容器还是普通的 IOC 容器) 
context = this.createApplicationContext(); 
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); 
/*
 * 准备上下文环境,将 environment 保存到 IOC 容器中,并且调用 applyInitializers() 方法
 * applyInitializers() 方法回调之前保存的所有的 ApplicationContextInitializer 的 initialize() 方法
 * 然后回调所有的 SpringApplicationRunListener#contextPrepared() 方法 
 * 最后回调所有的 SpringApplicationRunListener#contextLoaded() 方法 
 */
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); 
// 刷新容器,IOC 容器初始化(如果是 Web 应用还会创建嵌入式的 Tomcat),扫描、创建、加载所有组件的地方 
this.refreshContext(context); 
// 从 IOC 容器中获取所有的 ApplicationRunner 和 CommandLineRunner 进行回调 
this.afterRefresh(context, applicationArguments); 
stopWatch.stop(); 
if (this.logStartupInfo) { 
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); 
} 
// 调用 所有 SpringApplicationRunListeners#started()方法 
listeners.started(context); 
this.callRunners(context, applicationArguments); 
} catch (Throwable var10) { 
this.handleRunFailure(context, var10, exceptionReporters, listeners); 
throw new IllegalStateException(var10); 
} 
try { 
listeners.running(context); 
return context; 
} catch (Throwable var9) { 
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); 
throw new IllegalStateException(var9); 
} 
} 

резюме:

Фаза run() в основном предназначена для обратного вызова методов четырех прослушивателей, упомянутых в начале этого раздела, и загрузки компонентов проекта в контейнер IOC, а все прослушиватели, которые необходимо вызвать, находятся в пути к классам.META/INF/Spring.factoriesДля выполнения различных операций настройки до и после запуска.

3. Принцип автоматической настройки SpringBoot

Аннотация @SpringBootApplication

Все в проекте SpringBoot начинается с@SpringBootApplicationЭта нота начинается.

Аннотация @SpringBootApplication говорит о классе:

  • Этот класс является основным классом конфигурации для Spring Boot.

  • SpringBoot должен запустить основной метод этого класса, чтобы запустить приложение SpringBoot.

Аннотация определяется следующим образом:

@SpringBootConfiguration 
@EnableAutoConfiguration 
@ComponentScan( 
excludeFilters = {@Filter( 
type = FilterType.CUSTOM, 
classes = {TypeExcludeFilter.class} 
), @Filter( 
type = FilterType.CUSTOM, 
classes = {AutoConfigurationExcludeFilter.class} 
)} 
) 
public @interface SpringBootApplication { 

можно увидетьSpringBootApplicationАннотация — это комбинированная аннотация (упомянутая в начале статьи о комбинированных аннотациях), которая в основном объединяет три аннотации:

  • @SpringBootConfiguration: Эта аннотация указывает, что это класс конфигурации SpringBoot, на самом деле это просто аннотация @Configuration.

  • @ComponentScan: включить сканирование компонентов.

  • @EnableAutoConfiguration: Как видно из названия, именно этот класс включает автоматическую настройку. Ну вот и вся тайна автонастройки в этой аннотации.

Аннотация @EnableAutoConfiguration

Давайте посмотрим, как определяется аннотация:

@AutoConfigurationPackage 
@Import({AutoConfigurationImportSelector.class}) 
public @interface EnableAutoConfiguration { 

@AutoConfigurationPackage

Буквально понимается пакет автоматической настройки. Нажмите, и вы увидите, что это аннотация @Import:@Import({Registrar.class}), который импортирует компонент Registrar. Статья об использовании @Import также представлена ​​выше.

Мы устанавливаем точку останова в методе registerBeanDefinitions в классе Registrar и видим, что возвращается имя пакета, который на самом деле является пакетом, в котором находится основной класс конфигурации.

1.png

Одним словом: аннотация @AutoConfigurationPackage — это основной класс конфигурации (класс, отмеченный @SpringBootConfiguration)Пакет и все подпакеты под нимВсе компоненты внутри сканируются в контейнер Spring. Поэтому компоненты, отличные от основного пакета класса конфигурации и подпакетов, не могут быть просканированы контейнером Spring по умолчанию.

@Import({AutoConfigurationImportSelector.class})

Эта аннотация импортирует N дополнительных классов автоконфигурации для текущего класса конфигурации. (Подробное использование этой аннотации упомянуто выше).

Настройка правил импорта классов

Каковы конкретные правила импорта? Давайте посмотрим на исходный код. Прежде чем приступить к просмотру исходного кода, давайте немного поговорим. Как сказал Сяо Ма, нам не нужно просматривать весь исходный код, и нам не нужно понимать, что означает каждая строка кода, нам просто нужно понять ключевые моменты.

Мы знаем, что selectImports из AutoConfigurationImportSelector используется для возврата массива полных имен классов компонентов, которые необходимо импортировать, так как же получить эти массивы?

Метод getAutoConfigurationEntry() вызывается в методе selectImports.

3.png

Из-за недостатка места я не буду делать скриншоты один за другим, я расскажу вам цепочку вызовов напрямую: в getAutoConfigurationEntry() -> getCandidateConfigurations() -> loadFactoryNames().

Здесь метод loadFactoryNames() передает параметр EnableAutoConfiguration.class. Сначала запомните этот параметр, он будет использоваться позже.

4.png

Три ключевых шага в loadFactoryNames():

  1. Получить все из пути к классам текущего проектаMETA-INF/spring.factoriesинформация в этом файле.

  2. Инкапсулируйте информацию, полученную выше, в карту для возврата.

  3. Из возвращенной Карты через только что пройденнуюEnableAutoConfiguration.classпараметр, получить все значения по ключу.

6.png

META-INF/spring.factories Подробнее

Это может немного сбить с толку, когда я говорю это, давайте посмотримMETA-INF/spring.factoriesЯ не знаю, что это за файлы. Конечно, этот файл будет во многих сторонних зависимостях.Как правило, каждый раз, когда сторонняя зависимость импортируется, в дополнение к ее собственному пакету jar также будетxxx-spring-boot-autoConfigure, это класс автоматической конфигурации, написанный третьей стороной, полагающейся на себя. Теперь возьмем зависимость spring-boot-autoconfigure.

8.png

Вы можете видеть, что в EnableAutoConfiguration есть много классов, это классы для автоматической настройки нашего проекта.

Одним словом: поместите путь к классам подMETA-INF/spring.factoriesВсе сконфигурированные в нем значения EnableAutoConfiguration добавляются в контейнер Spring.

HttpEncodingAutoConfiguration

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

Далее мы используемHttpEncodingAutoConfigurationВ качестве примера, чтобы увидеть, как работает класс автоконфигурации. Почему выбирают эту категорию? В основном простой пример сравнения этого класса.

Давайте посмотрим на аннотации, отмеченные этим классом:

@Configuration 
@EnableConfigurationProperties({HttpProperties.class}) 
@ConditionalOnWebApplication( 
type = Type.SERVLET 
) 
@ConditionalOnClass({CharacterEncodingFilter.class}) 
@ConditionalOnProperty( 
prefix = "spring.http.encoding", 
value = {"enabled"}, 
matchIfMissing = true 
) 
public class HttpEncodingAutoConfiguration { 
  • @Configuration: помечен как класс конфигурации.

  • @ConditionalOnWebApplication: действует только в веб-приложении.

  • @ConditionalOnClass: указанный класс (зависимость) вступит в силу, только если он существует.

  • @ConditionalOnProperty: вступает в силу, только если указанное свойство существует в основном файле конфигурации.

  • @EnableConfigurationProperties({HttpProperties.class}): запустить функцию ConfigurationProperties указанного класса, привязать соответствующее значение в файле конфигурации к HttpProperties и добавить HttpProperties в контейнер IOC.

Поскольку @EnableConfigurationProperties({HttpProperties.class}) привязывает элементы конфигурации в файле конфигурации к текущему классу HttpProperties. Затем HttpProperties упоминается в HttpEncodingAutoConfiguration, поэтому, наконец, вы можете использовать значение в файле конфигурации в HttpEncodingAutoConfiguration. Наконец, компоненты добавляются в контейнер через @Bean и некоторые условные решения для достижения автоматической конфигурации. (Конечно, значение свойства в bean-компоненте получается из HttpProperties)

HttpProperties

HttpProperties связывает файл конфигурации с его собственными свойствами через аннотацию @ConfigurationProperties.

Все свойства, которые можно настроить в файле конфигурации, инкапсулированы в класс xxxProperties; то, что можно настроить в файле конфигурации, может ссылаться на класс свойств, соответствующий функции.

@ConfigurationProperties( 
prefix = "spring.http" 
)// 从配置文件中获取指定的值和bean的属性进行绑定 
public class HttpProperties { 

резюме:

  1. Запуск SpringBoot загружает большое количество классов автоконфигурации.

  2. Давайте посмотрим, есть ли класс автоматической настройки, написанный SpringBoot по умолчанию для необходимых функций.

  3. Давайте посмотрим, какие компоненты настроены в этом классе автоматической настройки (пока компоненты, которые мы используем, присутствуют, нам не нужно настраивать их снова).

  4. При добавлении компонентов в класс автоконфигурации в контейнере определенные свойства получаются из класса свойств. Затем мы можем указать значения этих свойств в файле конфигурации.

  • xxxAutoConfiguration: Класс автоматической настройки для добавления компонентов в контейнер.

  • xxxProperties: инкапсулировать связанные свойства в файле конфигурации.

Я не знаю, заметили ли ваши друзья, что многие классы, которые необходимо загрузить, помещены в путь к классам.META-INF/Spring.factoriesПод файл, вместо того, чтобы напрямую писать код, нам или третьему лицу очень удобно расширяться, а также мы можем реализовать свойstarter, дайте SpringBoot загрузиться. Теперь поймите, почему SpringBoot может достичь нулевой конфигурации прямо из коробки!

Наконец

Статья немного длинновата, спасибо за прочтение! Если вы считаете, что это хорошо, вы можете поставить палец вверх!

Справочная статья:

- blog.51CTO.com/4247649/211…

- woo woo woo.cn blog on.com/short-term Earing/afraid/37…

- woo woo Краткое описание.com/afraid/oh 22, а не 9 акций отправить 3…

- blog.CSDN.net/QQ_26525215…