zoukankan      html  css  js  c++  java
  • 面试官:你说你精通SpringBoot,你给我说一下类的自动装配吧

    ## 剖析@SpringBootApplication注解


    创建一个SpringBoot工程后,SpringBoot会为用户提供一个Application类,该类负责项目的启动:

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

    ````

    这是一个被`@SpringBootApplication`注解的类,该注解完成了SpringBoot中类的自动装配任务:

    ```
    @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 {

    }

    ````

    抛却元注解不谈,@SpringBootApplication继承了三个注解:

    ## @SpringBootConfiguration

    ```
    /**
    * Indicates that a class provides Spring Boot application
    * {@link Configuration @Configuration}. Can be used as an
    * alternative to the Spring's standard @Configuration
    * annotation so that configuration can be found
    * automatically (for example in tests).
    *
    * Application should only ever include one
    * @SpringBootConfiguration and most idiomatic Spring Boot
    * applications will inherit it from @SpringBootApplication.
    */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    ...
    }

    ```

    在说明中提到,`@SpringBootConfiguration`注解是用来替代Spring的`@Configuration`,方便SpringBoot自动找到配置。
    ## @ComponentScan

    ````
    /**
    * Configures component scanning directives
    * for use with Configuration classes.
    * Provides support parallel with Spring XML's
    * <context:component-scan> element.
    *
    * Either #basePackageClasses or #basePackages
    * (or its alias #value} may be specified to
    * define specific packages to scan. If specific
    * packages are not defined, scanning will occur
    * from the package of the class that declares
    * this annotation.
    *
    * Note that the <context:component-scan> element
    * has an annotation-config attribute; however,
    * this annotation does not. This is because
    * in almost all cases when using @ComponentScan,
    * default annotation config processing
    * (e.g. processing @Autowired and friends)
    * is assumed. Furthermore, when using
    * AnnotationConfigApplicationContext,
    * annotation config processors are always
    * registered, meaning that any attempt to disable
    * them at the @ComponentScan level would be ignored.
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
    ...
    }

    ````

    在说明中我们可以得知:`@ComponentScan`只负责指定要扫描的包,并没有装配其中的类,这个真正装配这些类是`@EnableAutoConfiguration`。
    ## @EnableAutoConfiguration

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

    ```

    该类真正完成了SpringBoot对于类的装配工作,具体内容在后续会作出解释。

    ## 以@Enable开头的注解


    以@Enable开头的注解(`@EnableXxx`)一般用于开启某一项功能,是为了简化代码的导入。它是一个组合注解,一般情况下`@EnableXxx`注解中都会组合一个`@Import`注解,而该`@Import`注解用于导入指定的类,而被导入的类一般有三种:

    ## 配置类

    * 类的特征:@Import中指定的类一般以Configuration结尾
    * 类的配置:该类上会注解@Configuration
    * 类的案例:定时任务启动注解:`SchedulingConfiguration`

    ```
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(SchedulingConfiguration.class)
    @Documented
    public @interface EnableScheduling {
    }

    ```

    ## 选择器

    * 类的特征:@Import中指定的类一般以 Selector 结尾
    * 类的配置:该类直接或间接实现了`ImportSelector`接口,表示当前类会根据条件选择导入不同的类。
    * 类的案例:Redis配置类:`CachingConfigurationSelector`

    ```
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(CachingConfigurationSelector.class)
    public @interface EnableCaching {
    ...
    }

    ```

    ```
    public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    ...
    @Override
    public String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
    case PROXY:
    return getProxyImports();
    case ASPECTJ:
    return getAspectJImports();
    default:
    return null;
    }
    }
    ...
    }

    ```

    ## 注册器

    * 类的特征:@Import 中指定的类一般以 Registrar 结尾。
    * 类的配置:该类直接或间接实现了`ImportBeanDefinitionRegistrar`接口,用于导入注册器,该类可以在代码运行时动态注册指定类的实例。
    * 类的案例:AspectJ:`AspectJAutoProxyRegistrar`

    ```
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
    ...
    }

    ```

    ```
    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(
    AnnotationMetadata importingClassMetadata,
    BeanDefinitionRegistry registry) {

    AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

    AnnotationAttributes enableAspectJAutoProxy =
    AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
    if (enableAspectJAutoProxy != null) {
    if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
    }
    if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
    AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
    }
    }
    }
    }

    ```

    ## 解析@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 {};

    }

    ```

    该注解是一个组合注解,用于完成自动配置,它是Spring Boot的核心注解。所谓自动配置是指,将用户自定义的类及框架本身用到的类进行装配。

    ## @AutoConfigurationPackage

    ```
    /**
    * Registers packages with AutoConfigurationPackages.
    * When no #basePackages base packages or
    * #basePackageClasses base package classes are
    * specified, the package of the annotated class is
    * registered.
    */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    ...
    }

    ```

    从类的说明中我的得知,该注解用于导入并装配用户自定义类,即自动扫描包中的类。若该注解未通过`basePackages`或`basePackageClasses`参数指明要扫描的包路径,则默认扫描含该注解的类所在包及其子包。
    ## @Import

    用于导入并装配框架本身的类。其参数`AutoConfigurationImportSelector.java`类,该类用于导入自动配置的类。其装配跟踪入口:`#getCandidateConfigurations`

    ```
    public class AutoConfigurationImportSelector implements
    DeferredImportSelector, BeanClassLoaderAware,
    ResourceLoaderAware, BeanFactoryAware,
    EnvironmentAware, Ordered {
    ...
    protected List<String> getCandidateConfigurations(
    AnnotationMetadata metadata,
    AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
    getSpringFactoriesLoaderFactoryClass(),
    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;
    }
    ...
    }

    ```

    `#getCandidateConfigurations` -> `SpringFactoriesLoader.loadFactoryNames`

    ```
    public final class SpringFactoriesLoader {
    ...
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    ...
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    ...
    try {
    Enumeration<URL> urls = (classLoader != null ?
    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    ...
    } catch (IOException ex) {
    ...
    }
    }
    }

    ```

    追踪到这里,我们得知,框架本身定义的类是从`META-INF/spring.factories`文件中获取的。该文件目录在哪儿呢?
    在创建SpringBoot Web项目时,我们在pom.xml文件中会自动导入一个依赖:

    ```
    <!-- pom.xml -->
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    </dependencies>

    ```

    打开一个starter,如`spring-boot-starter-web`依赖,我们可以看到其中包含了一个子依赖:

    ```
    <!-- spring-boot-starter-web-2.3.4.RELEASE.pom -->
    <dependencies>
    ...
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    ...
    </dependencies>

    ```

    打开`spring-boot-starter`依赖,可以看到这么一个子依赖:

    ```
    <!-- spring-boot-starter-2.3.4.RELEASE.pom -->
    <dependencies>
    ...
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    ...
    </dependencies>

    ```

    查看该依赖的内容,打开spring.factories文件:

    ![](https://upload-images.jianshu.io/upload_images/23140115-7a032a14b5816253.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


    ![](https://upload-images.jianshu.io/upload_images/23140115-4f7429a7e416bec2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


    这些就是框架定义的,需要装配的类。

    ## application.yml的加载


    `application.yml`文件对于 Spring Boot 来说是核心配置文件,至关重要!那么,该文件是如何加载到内存的呢?我们需要从启动类的`run()`方法开始跟踪,该跟踪过程比较深,耐心差的读者慎入。

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

    ```

    进入run方法:

    ```
    public class SpringApplication {
    ...
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
    }

    public ConfigurableApplicationContext run(String... args) {
    ...
    // 准备运行环境
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    ...
    }

    private ConfigurableEnvironment prepareEnvironment(
    SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments) {
    ...
    // 让监听器监听环境准备过程
    listeners.environmentPrepared(environment);
    ...
    }
    ...
    }

    ```

    让监听器监听环境准备过程

    ```
    class SpringApplicationRunListeners {
    ...
    void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
    listener.environmentPrepared(environment);
    }
    }
    ...
    }

    ```

    发布环境准备事件

    ```
    public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    ...
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(
    new ApplicationEnvironmentPreparedEvent(
    this.application,
    this.args,
    environment
    )
    );
    }

    @Override
    public void multicastEvent(ApplicationEvent event) {
    multicastEvent(event, resolveDefaultEventType(event));
    }

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    if (executor != null) {
    // 触发监听器
    executor.execute(() -> invokeListener(listener, event));
    }
    else {
    invokeListener(listener, event);
    }
    }
    }
    ...
    }

    ```

    触发监听器

    ```
    public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    ...
    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
    try {
    doInvokeListener(listener, event);
    }
    catch (Throwable err) {
    errorHandler.handleError(err);
    }
    }
    else {
    doInvokeListener(listener, event);
    }
    }

    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    ...
    listener.onApplicationEvent(event);
    ...
    }
    ...
    }

    ```

    `ApplicationListener#onApplicationEvent`是一个接口方法,我们主要看它的`ConfigFileApplicationListener`实现类的实现

    ```
    public class ConfigFileApplicationListener implements ... {
    ...
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
    onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    ...
    }

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
    postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
    }
    ...
    }

    ```

    `EnvironmentPostProcessor#postProcessEnvironment`是一个接口方法,我们主要看它的`ConfigFileApplicationListener`实现类的实现

    ```
    public class ConfigFileApplicationListener implements ... {
    ...
    @Override
    public void postProcessEnvironment(
    ConfigurableEnvironment environment,
    SpringApplication application) {
    // 加载配置文件
    addPropertySources(environment, application.getResourceLoader());
    }

    protected void addPropertySources(
    ConfigurableEnvironment environment,
    ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    new Loader(environment, resourceLoader).load();
    }

    private class Loader {
    void load() {
    FilteredPropertySource.apply(
    this.environment,
    DEFAULT_PROPERTIES,
    LOAD_FILTERED_PROPERTY,
    (defaultProperties) -> {
    ...
    while (!this.profiles.isEmpty()) {
    ...
    load(profile, this::getPositiveProfileFilter,
    addToLoaded(MutablePropertySources::addLast, false));
    ...
    }
    ...
    });
    }

    private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    getSearchLocations().forEach((location) -> {
    boolean isDirectory = location.endsWith("/");
    Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
    names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
    });
    }

    private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
    DocumentConsumer consumer) {
    ...
    for (PropertySourceLoader loader : this.propertySourceLoaders) {
    for (String fileExtension : loader.getFileExtensions()) {
    if (processed.add(fileExtension)) {
    loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer);
    }
    }
    }
    }

    private void loadForFileExtension(
    PropertySourceLoader loader,
    String prefix,
    String fileExtension,
    Profile profile,
    DocumentFilterFactory filterFactory,
    DocumentConsumer consumer) {
    ...
    load(loader, prefix + fileExtension, profile, profileFilter, consumer);
    }

    private void load(
    PropertySourceLoader loader,
    String location,
    Profile profile,
    DocumentFilter filter,
    DocumentConsumer consumer) {
    ...
    List<Document> documents = loadDocuments(loader, name, resource);
    ...
    }

    private List<Document> loadDocuments(
    PropertySourceLoader loader,
    String name,
    Resource resource) throws IOException {
    DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
    List<Document> documents = this.loadDocumentsCache.get(cacheKey);
    if (documents == null) {
    List<PropertySource<?>> loaded = loader.load(name, resource);
    documents = asDocuments(loaded);
    this.loadDocumentsCache.put(cacheKey, documents);
    }
    return documents;
    }
    }
    ...
    }

    ```

    `PropertySourceLoader#getFileExtensions`和`PropertySourceLoader#load`都是接口方法,我们主要看它的`YamlPropertySourceLoader`实现类的实现

    ```
    public class YamlPropertySourceLoader implements PropertySourceLoader {
    @Override
    public String[] getFileExtensions() {
    return new String[] { "yml", "yaml" };
    }

    @Override
    public List<PropertySource<?>> load(
    String name,
    Resource resource) throws IOException {
    ...
    return propertySources;
    }
    }
    ```
    ## 最后
    感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞!

  • 相关阅读:
    centos安装时各个版本的含义
    centos或者ubuntu设置ssh免密码登陆
    centos配置网卡
    如何卸载centos中自带的Java
    基于VHDL的8255可编程并行接口电路设计
    Norns.Urd 中的一些设计
    手把手教你写DI_3_小白徒手支持 `Singleton` 和 `Scoped` 生命周期
    手把手教你写DI_2_小白徒手撸构造函数注入
    手把手教你写DI_1_DI框架有什么?
    手把手教你写DI_0_DI是什么?
  • 原文地址:https://www.cnblogs.com/lwh1019/p/13705117.html
Copyright © 2011-2022 走看看