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

5次阅读

共计 13579 个字符,预计需要花费 34 分钟才能阅读完成。

分析 @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;
    }
}

最初

感激你看到这里,文章有什么有余还请斧正,感觉文章对你有帮忙的话记得给我点个赞!

正文完
 0