乐趣区

关于面试:淘宝一面说一下-Spring-Boot-自动装配原理呗

本文曾经收录进 Github 95k+ Star 的 Java 我的项目 JavaGuide。JavaGuide 我的项目地址 : https://github.com/Snailclimb…。

作者:Miki-byte-1024 & Snailclimb

每次问到 Spring Boot,面试官十分喜爱问这个问题:“讲述一下 SpringBoot 主动拆卸原理?”。

我感觉咱们能够从以下几个方面答复:

  1. 什么是 SpringBoot 主动拆卸?
  2. SpringBoot 是如何实现主动拆卸的?如何实现按需加载?
  3. 如何实现一个 Starter?

篇幅问题,这篇文章并没有深刻,小伙伴们也能够间接应用 debug 的形式去看看 SpringBoot 主动拆卸局部的源代码。

前言

应用过 Spring 的小伙伴,肯定有被 XML 配置统治的恐怖。即便 Spring 前面引入了基于注解的配置,咱们在开启某些 Spring 个性或者引入第三方依赖的时候,还是须要用 XML 或 Java 进行显式配置。

举个例子。没有 Spring Boot 的时候,咱们写一个 RestFul Web 服务,还首先须要进行如下配置。

@Configuration
public class RESTConfiguration
{
    @Bean
    public View jsonTemplate() {MappingJackson2JsonView view = new MappingJackson2JsonView();
        view.setPrettyPrint(true);
        return view;
    }

    @Bean
    public ViewResolver viewResolver() {return new BeanNameViewResolver();
    }
}

spring-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.howtodoinjava.demo" />
    <mvc:annotation-driven />

    <!-- JSON Support -->
    <bean name="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
    <bean name="jsonTemplate" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>

</beans>

然而,Spring Boot 我的项目,咱们只须要增加相干依赖,无需配置,通过启动上面的 main 办法即可。

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

并且,咱们通过 Spring Boot 的全局配置文件 application.propertiesapplication.yml 即可对我的项目进行设置比方更换端口号,配置 JPA 属性等等。

为什么 Spring Boot 应用起来这么酸爽呢? 这得益于其主动拆卸。主动拆卸能够说是 Spring Boot 的外围,那到底什么是主动拆卸呢?

什么是 SpringBoot 主动拆卸?

咱们当初提到主动拆卸的时候,个别会和 Spring Boot 分割在一起。然而,实际上 Spring Framework 早就实现了这个性能。Spring Boot 只是在其根底上,通过 SPI 的形式,做了进一步优化。

SpringBoot 定义了一套接口标准,这套标准规定:SpringBoot 在启动时会扫描内部援用 jar 包中的 META-INF/spring.factories 文件,将文件中配置的类型信息加载到 Spring 容器(此处波及到 JVM 类加载机制与 Spring 的容器常识),并执行类中定义的各种操作。对于内部 jar 来说,只须要依照 SpringBoot 定义的规范,就能将本人的性能安装进 SpringBoot。

没有 Spring Boot 的状况下,如果咱们须要引入第三方依赖,须要手动配置,十分麻烦。然而,Spring Boot 中,咱们间接引入一个 starter 即可。比方你想要在我的项目中应用 redis 的话,间接在我的项目中引入对应的 starter 即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

引入 starter 之后,咱们通过大量注解和一些简略的配置就能应用第三方组件提供的性能了。

在我看来,主动拆卸能够简略了解为:通过注解或者一些简略的配置就能在 Spring Boot 的帮忙下实现某块性能。

SpringBoot 是如何实现主动拆卸的?

咱们先看一下 SpringBoot 的外围注解 SpringBootApplication

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
<1.>@SpringBootConfiguration
<2.>@ComponentScan
<3.>@EnableAutoConfiguration
public @interface SpringBootApplication {

}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 实际上它也是一个配置类
public @interface SpringBootConfiguration {}

大略能够把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的汇合。依据 SpringBoot 官网,这三个注解的作用别离是:

  • @EnableAutoConfiguration:启用 SpringBoot 的主动配置机制
  • @Configuration:容许在上下文中注册额定的 bean 或导入其余配置类
  • @ComponentScan:扫描被 @Component (@Service,@Controller) 注解的 bean,注解默认会扫描启动类所在的包下所有的类,能够自定义不扫描某些 bean。如下图所示,容器中将排除 TypeExcludeFilterAutoConfigurationExcludeFilter

@EnableAutoConfiguration 是实现主动拆卸的重要注解,咱们以这个注解动手。

@EnableAutoConfiguration: 实现主动拆卸的外围注解

EnableAutoConfiguration 只是一个简略地注解,主动拆卸外围性能的实现理论是通过 AutoConfigurationImportSelector类。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 作用:将 main 包下的所欲组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) // 加载主动拆卸类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};}

咱们当初重点剖析下AutoConfigurationImportSelector 类到底做了什么?

AutoConfigurationImportSelector: 加载主动拆卸类

AutoConfigurationImportSelector类的继承体系如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}

public interface DeferredImportSelector extends ImportSelector {

}

public interface ImportSelector {String[] selectImports(AnnotationMetadata var1);
}

能够看出,AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports办法,该办法次要用于 获取所有符合条件的类的全限定类名,这些类须要被加载到 IoC 容器中

private static final String[] NO_IMPORTS = new String[0];

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // <1>. 判断主动拆卸开关是否关上
        if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {
          //<2>. 获取所有须要拆卸的 bean
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

这里咱们须要重点关注一下 getAutoConfigurationEntry() 办法,这个办法次要负责加载主动配置类的。

该办法调用链如下:

当初咱们联合 getAutoConfigurationEntry() 的源码来详细分析一下:

private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        //<1>.
        if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {
            //<2>.
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //<3>.
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //<4>.
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

第 1 步:

判断主动拆卸开关是否关上。默认spring.boot.enableautoconfiguration=true,可在 application.propertiesapplication.yml 中设置

第 2 步

用于获取 EnableAutoConfiguration 注解中的 excludeexcludeName

第 3 步

获取须要主动拆卸的所有配置类,读取META-INF/spring.factories

spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

从下图能够看到这个文件的配置内容都被咱们读取到了。XXXAutoConfiguration的作用就是按需加载组件。

不光是这个依赖下的 META-INF/spring.factories 被读取到,所有 Spring Boot Starter 下的 META-INF/spring.factories 都会被读取到。

所以,你能够分明滴看到,druid 数据库连接池的 Spring Boot Starter 就创立了 META-INF/spring.factories 文件。

如果,咱们本人要创立一个 Spring Boot Starter,这一步是必不可少的。

第 4 步

到这里可能面试官会问你:“spring.factories中这么多配置,每次启动都要全副加载么?”。

很显著,这是不事实的。咱们 debug 到前面你会发现,configurations 的值变小了。

因为,这一步有经验了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会失效。

@Configuration
// 查看相干的类:RabbitTemplate 和 Channel 是否存在
// 存在才会加载
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {}

有趣味的童鞋能够具体理解下 Spring Boot 提供的条件注解

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的状况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者尽管有多个然而指定首选 Bean
  • @ConditionalOnClass:当类门路下有指定类的条件下
  • @ConditionalOnMissingClass:当类门路下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类门路是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的地位
  • @ConditionalOnNotWebApplication:以后我的项目不是 Web 我的项目的条件下
  • @ConditionalOnWebApplication:以后我的项目是 Web 项 目标条件下

如何实现一个 Starter

光说不练假把式,当初就来撸一个 starter,实现自定义线程池

第一步,创立 threadpool-spring-boot-starter 工程

第二步,引入 Spring Boot 相干依赖

第三步,创立ThreadPoolAutoConfiguration

第四步,在 threadpool-spring-boot-starter 工程的 resources 包下创立 META-INF/spring.factories 文件

最初新建工程引入threadpool-spring-boot-starter

测试通过!!!

总结

Spring Boot 通过 @EnableAutoConfiguration 开启主动拆卸,通过 SpringFactoriesLoader 最终加载 META-INF/spring.factories 中的主动配置类实现主动拆卸,主动配置类其实就是通过 @Conditional 按需加载的配置类,想要其失效必须引入 spring-boot-starter-xxx 包实现起步依赖

文章结尾

我是 Guide 哥,一 Java 后端开发,会一点前端,自在的少年。咱们下期再见!微信搜“JavaGuide”回复“面试突击”支付我整顿的 4 本原创 PDF

退出移动版