美文网首页程序员java架构经验分享
面试官:你说你精通SpringBoot,你给我说一下类的自动装配

面试官:你说你精通SpringBoot,你给我说一下类的自动装配

作者: 前程有光 | 来源:发表于2020-09-21 13:48 被阅读0次

    剖析@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 {
        ...
    }
    
    

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

    @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文件:

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

    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#getFileExtensionsPropertySourceLoader#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;
        }
    }
    

    最后

    感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞!

    相关文章

      网友评论

        本文标题:面试官:你说你精通SpringBoot,你给我说一下类的自动装配

        本文链接:https://www.haomeiwen.com/subject/jrsvyktx.html