关注Java后端技术全栈”**

回复“面试”获取全套面试材料

目前Spring Boot有多火,置信大家都能感触到,SpringBoot好像当初成为一个java开发中必备的技能之一。另外一方面,其实真正只有应用过的人才晓得Spring Boot的痛快,那是一种享受。然而想做一个合格的、有优良的java开发者,Spring Boot其背地的相干原理也是不得不把握的。所以这一篇中咱们来说Spring Boot的配置。

依赖配置的解决

在应用SpringMVC的时候,咱们会波及到大量的配置、大量的依赖。然而Spring Boot的依赖是怎么样的呢?麻烦吗?

人总是懈怠的,各种各样的工具呈现都是为人们的懈怠而呈现的。

有了洗衣机就不必手洗衣服了。

有了车就不必走路了。

有了电话就不必写信了。

…..

看看Spring Boot的pom文件,咱们能够看到 pom 文件中的 parent,点击 spring-boot-starter-parent:

<parent>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-parent</artifactId>      <version>2.1.6.RELEASE</version>      <relativePath /></parent>

点击spring-boot-starter-parent进去之后发现。

相熟的配置文件yml、yaml、properties。另外看到有这一段parent

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-dependencies</artifactId>    <version>2.1.6.RELEASE</version>    <relativePath>../../spring-boot-dependencies</relativePath> </parent>

spring-boot-dependencies顾名思义,Spring Boot 的相干援用。点击spring-boot-dependencies进去

发现parent有<properties>值

这里就是为咱们配置好了罕用 jar 的版本。所以咱们不必显性的配置版本号。(这里太多了只截图了局部,有趣味依照我这步骤自行查看)。

如何实现主动配置

回到咱们代码中的启动类里。

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

这外面有个神奇的main函数,还有一个注解 @SpringBootApplication ,大家都晓得注解的作用就是用来标记的,示意在某一时刻做某件事。咱们点进去看下。

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented//能够记录在javac中@Inherited//能够被子类击沉改注解@SpringBootConfiguration//表明改类为配置类@EnableAutoConfiguration//启动主动拆卸性能//扫描(后面文中中有说过)@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {    /**     * Exclude specific auto-configuration classes such that they will never be applied.     * @return the classes to exclude     */    @AliasFor(annotation = EnableAutoConfiguration.class)    Class<?>[] exclude() default {};

能够看出注解 @SpringBootApplication 其实应用三个外围注解组合而成的。

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

@SpringBootConfiguration

第一个注解@SpringBootConfiguration就是一个标记类为配置类.能够被组件扫描器扫描到,和 @Configuration 注解性能雷同。

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

@EnableAutoConfiguration

第二个注解@EnableAutoConfiguration,该 注解是启动主动配置性能。

这个注解是 Spring Boot 框架最重要的注解,也是实现自动化配置的注解。咱们看下源码:

@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

注解@AutoConfigurationPackage。这个注解的作用就是主动配置包。

看下源码:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited//spring 框架的底层注解,它的作用就是给容器导入某个组件类//这里就是将Registrar组件类导入到容器中@Import(AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage {}
Registrar

再看看Registrar类的源码,该类是一个动态外部类,

org.springframework.boot.autoconfigure.AutoConfigurationPackages中的动态外部类

 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {        //获取到的是我的项目主程序启动类所在的目录        //        @Override        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {            //默认将会扫描@SpringBootApplication注解标注的主配置类所在的包            //以及其子包下所有组件            register(registry, new PackageImport(metadata).getPackageName());        }        @Override        public Set<Object> determineImports(AnnotationMetadata metadata) {            return Collections.singleton(new PackageImport(metadata));        }    }

这个类 registerBeanDefinitions 办法会将主程序类所在你的包以及子包下的组件都扫描到 spring 容器中。所以咱们在我的项目进行包目录制订的时候,须要有个标准,这样能力被扫描到。

@Import

这个注解,其实咱们下面看到了,就是将资源引入到容器中。这里是

@Import(AutoConfigurationImportSelector.class)

也就是说将 AutoConfigurationImportSelector 加载到组件中,咱们看下这个类中的这个办法,这个办法就是将Spring Boot须要的组件都导入进来。

 @Override    public String[] selectImports(AnnotationMetadata annotationMetadata) {        //判断enableautoconfiguration注解是否曾经开启。        //默认开启        if (!isEnabled(annotationMetadata)) {            return NO_IMPORTS;        }        //加载配置文件META_INF/spring-autoconfigure-metadata.properties        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader                .loadMetadata(this.beanClassLoader);        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,                annotationMetadata);        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());    }

次要就是加载组件,哪会加载什么组件呢?就在这个 loadMetadata 这个办法中。这个办法中传递了以后的类的结构器,看看这个办法。

final class AutoConfigurationMetadataLoader {    protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";    private AutoConfigurationMetadataLoader() {    }    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {        return loadMetadata(classLoader, PATH);    }    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {        try {            Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)                    : ClassLoader.getSystemResources(path);            Properties properties = new Properties();            while (urls.hasMoreElements()) {                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));            }            return loadMetadata(properties);        }        catch (IOException ex) {            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);        }    }

能够发现,这个办法就是加载

spring-autoconfigure-metadata.properties

这个文件,获取须要主动加载的类门路进行主动装载。能够看下这个配置文件。

我这里版本是2.1.6RELEASE。持续看看该配置文件的内容有些什么;

这个配置文件中都是须要装载的类的门路。所以这里就阐明了 loadMetadata 这个办法就是加载这些配置信息,然而加载完做什么呢?所以就是下一步 getAutoConfigurationEntry 办法了,这个办法就是取得 AutoConfigurationEntry 对象。

持续看下一个办法

 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { //判断是否开启注解,如果开启则返回空对象 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } //取得注解的属性 AnnotationAttributes attributes = getAttributes(annotationMetadata); //这个办法是用来获取默认反对的主动配置类名列表 //spring Boot在启动的时候,应用外部工具类SpringFactoriesLoader,查找classpath下所有jar包中 //的META_INF/spring.factories,而后将其值作为主动配置类导入到容器中,主动拆卸类这样就会失效了。 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //去除反复的配置类,如咱们本人写的starter可能存在反复 configurations = removeDuplicates(configurations); //排除咱们不心愿其主动拆卸的 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }

这个办法就是获取 AutoConfigurationEntry 对象,先回去到 configurations。咱们进入这个 getCandidateConfigurations

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

能够看到这里理论加载的就是 META-INF/spring.factories 文件,所以想要实现主动加载,须要在这个文件中进行配置。 

总结一下:

Spring Boot 底层实现主动配置的流程:

  1. Spring Boot 利用启动
  2. @SpringBootApplication 注解执行
  3. @EnableAutoConfifiguration
  4. AutoConfifigurationImportSelector.class 执行,它通过 selectImports,查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 springFactory 加载器进行一系列的容器创立过程。

其实咱们还有一个注解没讲,@ComponentScan 这个注解就是包扫描器,用来指定扫描器要从哪个包开始扫描。

能够参考:SpringBoot如何应用注解拆卸Bean

下面说了一堆,都是本文的重点,然而大家能够理解下,晓得为什么 Spring Boot 不必咱们在写大量的配置了,是因为 Spring Boot 在启动的时候把咱们都加载好了,然而要实现这样的成果,就须要保障制订的 starter 满足肯定的标准,比方必须在 starter 中的 META-INF/spring.factories 进行配置。

另外如何自定义一个starter,请参考后面的文章:

Spring Boot 如何手写stater

举荐浏览:

口试题:理解穷举算法吗?如何用代码实现

口试题:代码如何实现“百钱买百鸡”?

这10道 Spring 常见面试题,你能搞定吗?

终于明确为什么要加 final 关键字了!