Spring Boot - один из многих подпроектов Spring. Его концепция заключается в том, что соглашение лучше, чем настройка. Он реализует функцию автоматической настройки (конфигурации, которую большинство пользователей привыкло устанавливать в качестве конфигурации по умолчанию) для быстрого создания стандартизированных приложений для пользователи. . Характеристики Spring Boot можно резюмировать следующим образом:
-
Встроенные Tomcat, Jetty и другие контейнеры сервлетов являются встроенными, и приложения можно запускать непосредственно в формате Jar, а не упаковывать в формате War.
-
Предоставляет несколько выбираемых «стартеров» для упрощения управления зависимостями Maven (также поддерживается Gradle), позволяя загружать необходимые функциональные модули по требованию.
-
Выполняйте автоматическую настройку в максимально возможной степени, уменьшая количество избыточных элементов конфигурации, которые пользователям необходимо писать вручную. Spring Boot поддерживает концепцию отсутствия файла конфигурации XML. Приложение, созданное Spring Boot, вообще не будет генерировать код конфигурации и файл конфигурации XML. .
-
Предоставляет полный набор функциональных модулей для мониторинга и управления статусом приложения (путем внесения привода Spring-Boot-Starter), включая информацию о потоке приложения, информацию о память, независимо от того, является ли приложение в здоровом состоянии и т. Д., Чтобы встретиться больше Требования к мониторингу ресурсов, многие модули весной облако также расширяют его.
Я не буду больше рассказывать об использовании Spring Boot, если вам интересно, пожалуйста, прочитайте официальную документацию самостоятельно.Spring Bootили другие статьи.
В настоящее время концепция микросервисов становится все более и более популярной, и число команд, трансформирующих или пробующих микросервисы, также увеличивается. Для выбора технологии Spring Cloud является лучшим выбором. Он предоставляет универсальное распределенное системное решение. , содержит множество компоненты, необходимые для создания распределенных систем и микросервисов, такие как управление услугами, шлюз API, центр конфигурации, шина сообщений и отказоустойчивые модули управления. Можно сказать, что Spring Cloud «Family Bucket» чрезвычайно подходит для команд, которые плохо знакомы с микросервисами. Кажется, это немного не по теме, но, сказав так много, я хочу подчеркнуть, что каждый компонент в Spring Cloud построен на основе Spring Boot, и, очевидно, полезно понимать принцип автоматической настройки Spring Boot.
Автоматическая настройка Spring Boot выглядит волшебно, но принцип очень прост, и все зависит от аннотации @Conditional.
Автор этой статьиSylvanasSun(sylvanas.sun@gmail.com), впервые опубликовано вБлог SylvanasSun. Исходная ссылка: https://sylvanassun.github.io/2018/01/08/2018-01-08-spring_boot_auto_configure/ (Для перепечатки просьба сохранить заявление в этом абзаце и сохранить гиперссылку.)
Что такое @Conditional?
@Conditional — это новая функция, предоставляемая Spring 4 для управления поведением создания bean-компонентов на основе определенных условий. Когда мы разрабатываем приложения на основе Spring, нам неизбежно нужно регистрировать bean-компоненты в соответствии с условиями.
Например, вы хотите, чтобы Spring зарегистрировал компонент источника данных соответствующей среды в соответствии с различными операционными средами.В этом простом случае вы можете использовать аннотацию @Profile для достижения этого, как показано в следующем коде:
@Configuration
public class AppConfig {
@Bean
@Profile("DEV")
public DataSource devDataSource() {
...
}
@Bean
@Profile("PROD")
public DataSource prodDataSource() {
...
}
}
Остальным нужно только установить соответствующее свойство профиля, есть три метода настройки следующим образом:
-
пройти через
context.getEnvironment().setActiveProfiles("PROD")
для установки свойства профиля. -
Установив jvm
spring.profiles.active
параметры для установки среды (в Spring Boot вы можете напрямуюapplication.properties
установить это свойство в файле конфигурации). -
Установив начальные параметры DispatcherServlet.
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>spring.profiles.active</param-name>
<param-value>PROD</param-value>
</init-param>
</servlet>
Но этот метод ограничен простыми случаями, и по исходному коду мы можем обнаружить, что сам @Profile также использует аннотацию @Conditional.
package org.springframework.context.annotation;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class}) // 组合了Conditional注解
public @interface Profile {
String[] value();
}
package org.springframework.context.annotation;
class ProfileCondition implements Condition {
ProfileCondition() {
}
// 通过提取出@Profile注解中的value值来与profiles配置信息进行匹配
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if(context.getEnvironment() != null) {
MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if(attrs != null) {
Iterator var4 = ((List)attrs.get("value")).iterator();
Object value;
do {
if(!var4.hasNext()) {
return false;
}
value = var4.next();
} while(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));
return true;
}
}
return true;
}
}
В случае сложного бизнеса очевидно необходимо использовать аннотацию @Conditional для предоставления более гибких условных суждений, таких как следующие условия суждения:
-
Существует ли такой класс в пути к классам.
-
Был ли определенный тип bean-компонента зарегистрирован в контейнере Spring (если он не зарегистрирован, мы можем сделать его автоматически зарегистрированным в контейнере, как и предыдущий).
-
Находится ли файл в определенном месте.
-
Существует ли конкретное системное свойство.
-
Установлено ли конкретное значение в файле конфигурации Spring.
Например, предположим, что у нас есть два DAO на основе разных реализаций баз данных, все из которых реализуют UserDao, где JdbcUserDAO подключен к MySql, а MongoUserDAO подключен к MongoDB. Теперь у нас есть требование зарегистрировать соответствующий UserDao в соответствии с системными параметрами, переданными в командной строке, напримерjava -jar app.jar -DdbType=MySQL
зарегистрирую JDBCUSERDAO, а такжеjava -jar app.jar -DdbType=MongoDB
MongoUserDao будет зарегистрирован. Использование @Conditional может легко реализовать эту функцию, просто нужно реализовать интерфейс Condition в вашем пользовательском классе условий, давайте посмотрим на код ниже. (Следующий случай взят из: https://dzone.com/articles/how-springboot-autoconfiguration-magic-works)
public interface UserDAO {
....
}
public class JdbcUserDAO implements UserDAO {
....
}
public class MongoUserDAO implements UserDAO {
....
}
public class MySQLDatabaseTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
String enabledDBType = System.getProperty("dbType"); // 获得系统参数 dbType
// 如果该值等于MySql,则条件成立
return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MySql"));
}
}
// 与上述逻辑一致
public class MongoDBDatabaseTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
String enabledDBType = System.getProperty("dbType");
return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MongoDB"));
}
}
// 根据条件来注册不同的Bean
@Configuration
public class AppConfig {
@Bean
@Conditional(MySQLDatabaseTypeCondition.class)
public UserDAO jdbcUserDAO() {
return new JdbcUserDAO();
}
@Bean
@Conditional(MongoDBDatabaseTypeCondition.class)
public UserDAO mongoUserDAO() {
return new MongoUserDAO();
}
}
Теперь у нас есть новое требование: мы хотим подтвердить, нужно ли регистрировать MongoUserDAO в соответствии с тем, есть ли класс драйвера MongoDB в пути к классам текущего проекта. Для выполнения этого требования можно создать два условных класса, которые проверяют наличие драйвера MongoDB.
public class MongoDriverPresentsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
try {
Class.forName("com.mongodb.Server");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
public class MongoDriverNotPresentsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
try {
Class.forName("com.mongodb.Server");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
}
Предположим, вы хотите зарегистрировать UserDAOBean, если UserDAO не зарегистрирован, тогда мы можем определить условный класс, чтобы проверить, зарегистрирован ли класс в контейнере.
public class UserDAOBeanNotPresentsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);
return (userDAO == null);
}
}
Если вы хотите решить, регистрировать ли MongoDAO на основе свойства в файле конфигурации, напримерapp.dbType
Будет ли оно равным?MongoDB
, мы можем реализовать следующий условный класс.
public class MongoDbTypePropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
String dbType = conditionContext.getEnvironment().getProperty("app.dbType");
return "MONGO".equalsIgnoreCase(dbType);
}
}
Мы пробовали и реализовали различные типы условий, затем мы можем выбрать более элегантный способ, как @Profile, чтобы завершить условное суждение в пути. Во-первых, нам нужно определить класс аннотации.
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DatabaseTypeCondition.class)
public @interface DatabaseType {
String value();
}
Логика определения конкретного условия находится в классе DatabaseTypeCondition, который будет основан на системных параметрах.dbType
Чтобы определить, какой Bean зарегистрировать.
public class DatabaseTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
String type = (String) attributes.get("value");
// 默认值为MySql
String enabledDBType = System.getProperty("dbType", "MySql");
return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));
}
}
Наконец, примените аннотацию к классу конфигурации.
@Configuration
@ComponentScan
public class AppConfig {
@Bean
@DatabaseType("MySql")
public UserDAO jdbcUserDAO() {
return new JdbcUserDAO();
}
@Bean
@DatabaseType("mongoDB")
public UserDAO mongoUserDAO() {
return new MongoUserDAO();
}
}
Анализ исходного кода автонастройки
Разобравшись с механизмом аннотации @Conditional, вы сможете примерно догадаться, как реализована автоматическая настройка.Далее посмотрим, как она это делает через исходный код. Исходный код, описанный в этой статье, основан на Spring Boot версии 1.5.9 (последняя официальная версия).
Детская обувь, которая использовала Spring Boot, должна быть очень четкой, она создаст для нас начальный класс, а ее спецификация именованияArtifactNameApplication
, через этот начальный класс мы можем найти некоторую информацию.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Во-первых, класс украшен аннотацией @SpringBootApplication, с нее можно начать анализ, просмотрев исходный код, можно обнаружить, что это комбинированная аннотация, содержащая множество аннотаций.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class,
attribute = "exclude"
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class,
attribute = "excludeName"
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
Эта аннотация эквивалентна одновременному объявлению трех аннотаций @Configuration, @EnableAutoConfiguration и @ComponentScan (если мы хотим настроить пользовательскую реализацию автоматической конфигурации, объявления этих трех аннотаций достаточно), и @EnableAutoConfiguration находится в центре нашего внимания, исходя из его названия. видно, он используется для включения автоматической настройки, исходный код выглядит следующим образом:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
Мы обнаружили, что @Import (аннотация, предоставляемая Spring, которая может импортировать классы конфигурации или bean-компоненты в текущий класс) импортирует класс EnableAutoConfigurationImportSelector, Судя по имени, это должна быть цель, которую мы ищем. Однако, глядя на его исходный код, обнаруживается, что он устарел, и официальный API говорит нам проверить его родительский класс AutoConfigurationImportSelector.
/** @deprecated */
@Deprecated
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
public EnableAutoConfigurationImportSelector() {
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass().equals(EnableAutoConfigurationImportSelector.class)?((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, Boolean.valueOf(true))).booleanValue():true;
}
}
Поскольку исходный код AutoConfigurationImportSelector слишком длинный, здесь я вырезал только ключевые моменты.Очевидно, что метод selectImports является основной записью для выбора автоматической конфигурации.Он вызывает несколько других методов для загрузки метаданных и другой информации, и, наконец, возвращает файл содержит множество автоматических конфигураций Строковый массив информации о классе.
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if(!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
AutoConfigurationMetadata ex = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
configurations = this.sort(configurations, ex);
Set exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, ex);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
Дело в том, что метод getCandidateConfigurations() возвращает информационный список классов автоконфигурации, сканирует и загружает jar-пакет, содержащий файл META-INF/spring.factories, вызывая SpringFactoriesLoader.loadFactoryNames(), который записывает, какие автоконфигурации классы конфигурации есть. (Для чтения исходного кода рекомендуется использовать IDE. Одна строка исходного кода слишком длинная. Считается, что эффект от просмотра в статье очень плохой.)
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(),this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(ex.hasMoreElements()) {
URL url = (URL)ex.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
Условные аннотации в классах автоконфигурации
Далее давайте найдем класс автоматической настройки в файле spring.factories, чтобы посмотреть, как он реализован. Я проверил исходный код MongoDataAutoConfiguration и обнаружил, что он объявляет аннотацию @ConditionalOnClass.Посмотрев исходный код аннотации, я могу обнаружить, что это комбинированная аннотация, которая объединяет @Conditional, а ее условный класс — OnClassCondition.
@Configuration
@ConditionalOnClass({Mongo.class, MongoTemplate.class})
@EnableConfigurationProperties({MongoProperties.class})
@AutoConfigureAfter({MongoAutoConfiguration.class})
public class MongoDataAutoConfiguration {
....
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
Затем мы начали просматривать исходный код OnClassCondition и обнаружили, что он напрямую не реализует интерфейс Condition, поэтому нам пришлось поискать и обнаружить, что его родительский класс SpringBootCondition реализует интерфейс Condition.
class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
.....
}
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(this.getClass());
public SpringBootCondition() {
}
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome ex = this.getMatchOutcome(context, metadata);
this.logOutcome(classOrMethodName, ex);
this.recordEvaluation(context, classOrMethodName, ex);
return ex.isMatch();
} catch (NoClassDefFoundError var5) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
} catch (RuntimeException var6) {
throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
}
}
public abstract ConditionOutcome getMatchOutcome(ConditionContext var1, AnnotatedTypeMetadata var2);
}
Метод совпадений, реализованный SpringBootCondition, основан на абстрактном методе this.getMatchOutcome(контекст, метаданные). Конкретную реализацию этого метода мы можем найти в его подклассе OnClassCondition.
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
// 找出所有ConditionalOnClass注解的属性
List onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
List onMissingClasses;
if(onClasses != null) {
// 找出不在类路径中的类
onMissingClasses = this.getMatches(onClasses, OnClassCondition.MatchType.MISSING, classLoader);
// 如果存在不在类路径中的类,匹配失败
if(!onMissingClasses.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.getMatches(onClasses, OnClassCondition.MatchType.PRESENT, classLoader));
}
// 接着找出所有ConditionalOnMissingClass注解的属性
// 它与ConditionalOnClass注解的含义正好相反,所以以下逻辑也与上面相反
onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
if(onMissingClasses != null) {
List present = this.getMatches(onMissingClasses, OnClassCondition.MatchType.PRESENT, classLoader);
if(!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.getMatches(onMissingClasses, OnClassCondition.MatchType.MISSING, classLoader));
}
return ConditionOutcome.match(matchMessage);
}
// 获得所有annotationType注解的属性
private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
MultiValueMap attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
ArrayList candidates = new ArrayList();
if(attributes == null) {
return Collections.emptyList();
} else {
this.addAll(candidates, (List)attributes.get("value"));
this.addAll(candidates, (List)attributes.get("name"));
return candidates;
}
}
private void addAll(List<String> list, List<Object> itemsToAdd) {
if(itemsToAdd != null) {
Iterator var3 = itemsToAdd.iterator();
while(var3.hasNext()) {
Object item = var3.next();
Collections.addAll(list, (String[])((String[])item));
}
}
}
// 根据matchType.matches方法来进行匹配
private List<String> getMatches(Collection<String> candidates, OnClassCondition.MatchType matchType, ClassLoader classLoader) {
ArrayList matches = new ArrayList(candidates.size());
Iterator var5 = candidates.iterator();
while(var5.hasNext()) {
String candidate = (String)var5.next();
if(matchType.matches(candidate, classLoader)) {
matches.add(candidate);
}
}
return matches;
}
Конкретная реализация match находится в MatchType, который представляет собой класс перечисления, который предоставляет две реализации PRESENT и MISSING.Первый возвращает, существует ли класс в пути к классу, а второй — наоборот.
private static enum MatchType {
PRESENT {
public boolean matches(String className, ClassLoader classLoader) {
return OnClassCondition.MatchType.isPresent(className, classLoader);
}
},
MISSING {
public boolean matches(String className, ClassLoader classLoader) {
return !OnClassCondition.MatchType.isPresent(className, classLoader);
}
};
private MatchType() {
}
// 跟我们之前看过的案例一样,都利用了类加载功能来进行判断
private static boolean isPresent(String className, ClassLoader classLoader) {
if(classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
forName(className, classLoader);
return true;
} catch (Throwable var3) {
return false;
}
}
private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {
return classLoader != null?classLoader.loadClass(className):Class.forName(className);
}
public abstract boolean matches(String var1, ClassLoader var2);
}
Теперь загадка, означающая @ ConditualonClass, указан класс, должен существовать в пути класса, класс Mongodataautoconfiguration, объявленный в пути класса, должен содержать Mongo.Class, Mongotemplate.Class Эти два класса, в противном случае класс не будет автоматически настроить нагрузку.
В Spring Boot везде есть похожие аннотации, такие как @ConditionalOnBean (независимо от того, есть ли в контейнере указанный bean-компонент), @ConditionalOnWebApplication (независимо от того, является ли текущий проект веб-проектом) и т. д., они являются просто расширениями аннотации @Conditional. Когда вы приподнимете завесу тайны и исследуете суть, то обнаружите, что принцип автоматической настройки Spring Boot настолько прост.Поняв эти знания, вы можете самостоятельно реализовать собственный класс автоматической настройки, а затем написать собственный starter.