关于java:原创Spring-Boot终极篇上

3次阅读

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

关注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 的版本。所以咱们不必显性的配置版本号。(这里太多了只截图了局部,有趣味依照我这步骤自行查看)。

如何实现主动配置

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

@SpringBootApplication
public 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
@Configuration
public @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 关键字了!

正文完
 0