《SpringBoot揭秘:快速构建微服务体系》—第3章3.2节@SpringBootApplication背后的秘密

本节书摘来自华章出版社《SpringBoot揭秘:快速构建微服务体系》一书中的第3章,第3.2节@SpringBootApplication背后的秘密,作者王福强,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.2 @SpringBootApplication背后的秘密
@SpringBootApplication是一个“三体”结构,实际上它是一个复合Annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication{
    ...
}

虽然它的定义使用了多个Annotation进行元信息标注,但实际上对于SpringBoot应用来说,重要的只有三个Annotation,而“三体”结构实际上指的就是这三个Annotation:
@Configuration
@EnableAutoConfiguration
@ComponentScan
所以,如果我们使用如下的SpringBoot启动类,整个SpringBoot应用依然可以与之前的启动类功能对等:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoApplication {

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

但每次都写三个Annotation显然过于繁琐,所以写一个@SpringBoot-Application这样的一站式复合Annotation显然更方便些。
3.2.1 @Configuration创世纪
这里的@Configuration对我们来说并不陌生,它就是JavaConfig形式的Spring IoC容器的配置类使用的那个@Configuration,既然SpringBoot应用骨子里就是一个Spring应用,那么,自然也需要加载某个IoC容器的配置,而SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,很明显,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类!
很多SpringBoot的代码示例都喜欢在启动类上直接标注@Configuration或者@SpringBootApplication,对于初接触SpringBoot的开发者来说,其实这种做法不便于理解,如果我们将上面的SpringBoot启动类拆分为两个独立的Java类,整个形势就明朗了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoConfiguration {
    @Bean
    public Controller controller() {
        return new Controller();
    }
}


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

所以,启动类DemoApplication其实就是一个标准的Standalone类型Java程序的main函数启动类,没有什么特殊的。
而@Configuration标注的DemoConfiguration定义其实也是一个普通的JavaConfig形式的IoC容器配置类,没啥新东西,全是Spring框架里的概念!
3.2.2 @EnableAutoConfiguration的功效
@EnableAutoConfiguration其实也没啥“创意”,各位是否还记得Spring框架提供的各种名字为@Enable开头的Annotation定义?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和“做事方式”其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义:
@EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。
@EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。
而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!
@EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器,就跟一只“八爪鱼”一样(如图3-1所示)。
借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以“智能”地自动配置功效才得以大功告成!


《SpringBoot揭秘:快速构建微服务体系》—第3章3.2节@SpringBootApplication背后的秘密

自动配置的幕后英雄:SpringFactoriesLoader详解
SpringFactoriesLoader属于Spring框架私有的一种扩展方案(类似于Java的SPI方案java.util.ServiceLoader),其主要功能就是从指定的配置文件META-INF/spring.factories加载配置,spring.factories是一个典型的java properties文件,配置的格式为Key = Value形式,只不过Key和Value都是Java类型的完整类名(Fully qualified name),比如:
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
然后框架就可以根据某个类型作为Key来查找对应的类型名称列表了:
public abstract class SpringFactoriesLoader {
    // ...
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        ...
    }
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ...
    }
    // ...
}

对于@EnableAutoConfiguration来说,SpringFactoriesLoader的用途稍微不同一些,其本意是为了提供SPI扩展的场景,而在@EnableAutoConfiguration的场景中,它更多是提供了一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdmin- JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAuto- Configuration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAuto-Configuration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationProperties-AutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceException-TranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.Cassandra-DataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.Cassandra-RepositoriesAutoConfiguration,\
...
以上是从SpringBoot的autoconfigure依赖包中的META-INF/spring.factories配置文件中摘录的一段内容,可以很好地说明问题。
所以,@EnableAutoConfiguration自动配置的魔法其实就变成了:从classpath中搜寻所有META-INF/spring.factories配置文件,并将其中org.spring-framework.boot.autoconfigure.EnableAutoConfiguration对应的配置项通过反射(Java Reflection)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
目前为止,还是Spring框架的原有概念和支持,依然没有“新鲜事”!
3.2.3 可有可无的@ComponentScan
为啥说@ComponentScan是可有可无的?因为原则上来说,作为Spring框架里的“老一辈革命家”,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中。加载bean定义到Spring的IoC容器,我们可以手工单个注册,不一定非要通过批量的自动扫描完成,所以说@ComponentScan是可有可无的。
对于SpringBoot应用来说,同样如此,比如我们本章的启动类:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoApplication {

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

如果我们当前应用没有任何bean定义需要通过@ComponentScan加载到当前SpringBoot应用对应使用的IoC容器,那么,除去@ComponentScan的声明,当前SpringBoot应用依然可以照常运行,功能对等!
看,还是没有啥新东西!

上一篇:使用ECS+Docker运行OpenGauss


下一篇:如何使用Docker Machine部署Swarm集群