18

面试官:你说你精通SpringBoot,你给我说一下类的自动装配吧-前程有光的博客

 3 years ago
source link: https://blog.51cto.com/14801695/2536462
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK