Принцип запуска Springboot @SpringBootApplication

Spring Boot

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

Я не знаю, заметили ли вы, что когда мы создаем проект Springboot, мы будем использовать следующие классы запуска

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

С точки зрения кода очевидно, что классы @SpringBootApplication и SpringApplication и их методы запуска являются ядром.

Итак, в этой статье давайте сначала проанализируем @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 {
    ...
}

Три важные аннотации: @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan. Давайте посмотрим на каждый.

Один: @SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

Мы узнали, что он применил @Configuration.

Аннотация @Configuration используется для определения класса конфигурации в форме JavaConfig, который может заменить файл конфигурации xml.Аннотированный класс содержит один или несколько методов, аннотированных с помощью @Bean, и эти методы будут сканироваться классом AnnotationConfigApplicationContext или AnnotationConfigWebApplicationContext. . , и используется для создания определений bean-компонентов, инициализации контейнера Spring. Это также форма конфигурации, рекомендованная сообществом SpringBoot.

Различия между @Configuration и традиционными файлами конфигурации xml:
  1. файловая структура
    Традиционный xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
        xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
            http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false">
    
    
    </beans>

    Использование @Конфигурация:

    @Configuration
    public class TestConfig {
    }

  2. определение фасоли
    Традиционный xml:

    <bean id="testService" class="TestServiceImpl">
    </bean>

    Использование @Конфигурация:

    @Configuration
    public class TestConfig {
        @Bean
        public TestService testService(){
             return new TestServiceImpl();
        }  
    }

Таким образом, любой класс Java, который использует @Configuration, означает, что это класс конфигурации.
Для любого метода, отмеченного @Bean, его возвращаемое значение будет зарегистрировано в контейнере IoC Spring как определение компонента, а имя метода по умолчанию будет соответствовать идентификатору определения компонента.

Два: @ComponentScan

Эта аннотация очень важна для весны, если вы понимаете @ComponentScan, вы понимаете весну.
Мы все знаем, что Spring — это фреймворк для внедрения зависимостей, и все вращается вокруг определений bean-компонентов и их зависимостей.
Но Spring не знает, какие bean-компоненты вы определяете, пока вы не сообщите Spring, где найти те bean-компоненты, которые вы определили.
И роль @ComponentScan состоит в том, чтобы сообщить Spring, где найти определенный bean-компонент.

Областью автоматического сканирования @ComponentScan можно управлять с помощью свойства basePackages.Если область сканирования @ComponentScan не указана, областью сканирования по умолчанию является сканирование из пакета, в котором объявлен класс, в котором @ComponentScan.

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


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

Три: @EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class[] exclude() default {};

    String[] excludeName() default {};
}

Здесь есть две важные аннотации, а именно @AutoConfigurationPackage и @Import.

@AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

Здесь обнаружено, что он использовал аннотацию @Import для введения класса Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    }

    public Set determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
    }
}

Итак, для чего используется этот класс? Давайте возьмем точку останова и посмотрим, что делает метод registerBeanDefinitions.


нашел здесьnew PackageImport(metadata).getPackageName()Возвращаемый возвращает родственные и дочерние компоненты пакета текущего основного класса программы.

Это также доказывает, что @ComponentScan по умолчанию сканирует пакет своего класса.

@Import({AutoConfigurationImportSelector.class})

Далее давайте рассмотрим класс AutoConfigurationImportSelector.

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

Этот метод вызывает метод getAutoConfigurationEntry

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        Set exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.filter(configurations, autoConfigurationMetadata);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

Здесь мы сосредоточимся на методе getCandidateConfigurations, первой точке останова, чтобы увидеть возвращаемое значение метода.


Этот метод возвращает список информации о классе, который необходимо создать, с его помощью Spring может загрузить классы, которые необходимо создать, в jvm через загрузчик классов.
Теперь посмотрим на код этого метода и обнаружим, что он заимствует метод класса SpringFactoriesLoader.

protected List 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;
}

Давайте еще раз взглянем на метод loadFactoryNames.

public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            //....省略

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}

Выяснил, что он прочитал файл с именем spring.factories


Например, найдем конфигурацию redis


Нажмите, чтобы посмотреть

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

@ConditionalOnClass({RedisOperations.class}) Указывает, что класс RedisOperations должен существовать, иначе класс конфигурации, измененный аннотацией, не будет проанализирован.

Посмотрите еще раз на класс RedisProperties.

public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
    private boolean ssl;
    private Duration timeout;
    private RedisProperties.Sentinel sentinel;
    private RedisProperties.Cluster cluster;
    private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
    private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
    ......省略
}

Это выглядит знакомо? Это информация, которую мы пишем в application.properties, когда используем Redis, и нам нужно настроить Redis
Соответствующая конфигурация должна быть:

spring.redis.host=127.0.0.1
spring.redis.port=6379   
spring.redis.timeout=0
spring.redis.password=

В то же время вы также можете видеть, что Redis по умолчанию настраивает для нас свойства хоста и порта. Так что при настройке redis, если номер порта около дефолтного 6379, тоже можно не писать причину.


Таким образом, общий принцип @EnableAutoConfiguration заключается в поиске всех файлов конфигурации META-INF/spring.factories из пути к классам и создании экземпляров элементов конфигурации, соответствующих org.springframework.boot.autoconfigure.EnableutoConfiguration, в соответствующем @Configuration посредством отражения Контейнер IoC классы конфигурации в форме JavaConfig затем объединяются в один и загружаются в контейнер IoC.

Четыре: конец

@SpringBootApplication мы закончили разбор, в следующей статье мы разберем класс SpringApplication


Для тех, кто не знает о springBoot, вы можете прочитать мойСерия руководств по SpringBoot