Spring Boot并不是一个新的技术框架,其主要作用就是简化Spring应用的开发,开发者只需要通过少量的代码就可以创建一个产品级的Spring应用,而达到这一目的最核心的思想就是“约定优于配置(Convention overConfiguration )“
约定优于配置(Convention Over Configuration)是一种软件设计范式,目的在于减少配置的数量或者降低理解难度,从而提升开发效率,需要注意的是,它并不是一种新的思想,实际上从我们开始接触Java依赖,就会发现很多地方都有这种思想的体现。比如,数据库中表名的设计对应到Java中实体类的名字,就是一种约定我们可以从这个实体类的名字知道它对应数据库中哪张表。再比如,每个公司都会有自己的开发规范,开发者按照开发规范可以在一定程度上减少Bug的数量,增加可读性和可维护性
在SpringBoot中,约定优于配置的思想主要体现在以下方面(包括但不限于):
Spring Boot是基于Spring Framework体系来构建的,所以它并没有什么新的东西,但是要想学好SpringBoot,必须知道它的核心:
其中,最核心的部分应该是自动装配,Starter组件的核心部分也是基于自动装配来实现的。由于本书并不是专门写SpringBoot的,所以笔者在后续章节中只会重点分析自动装配的原理。
在本节中,从Spring的起源,到SpringXML配置文件时代的问题,接着引出JavaConfig的方式实现无配置化注入的解决方案。可以看到,在整个发展过程中,Bean的装载方式在形式上发生了变化,但是本质问题仍然没有解决,直到Spring Boot出现。然后我们简单分析了Spring Boot的优势以及核心之所以花这么多笔墨去串联整个过程,是因为在笔者看来,比使用技术更重要的是了解技术的产生背景,这将有助于提高和改变技术人的思维方式;
在Spring Boot中,不得不说的一个点是自动装配,它是Starter的基础,也是Spring Boot的核心,那么什么叫自动装配呢?或者说什么叫装配呢?
简单来说,就是自动将Bean装配到IoC容器中,接下来,我们通过一个Spring Boot整合Redis的例子来了解一下自动装配;
在这个案例中,我们并没有通过XML形式或者注解形式把RedisTemplate注入IoC容器中,但是在HelloController中却可以直接使用@Autowired来注入redisTemplate实例,这就说明,IoC容器中已经存在RedisTemplate。这就是Spring Boot的自动装配机制。
在往下探究其原理前,可以大胆猜测一下,如何只添加一个Starter依赖,就能完成该依赖组件相关Bean的自动注入?不难猜出,这个机制的实现一定基于某种约定或者规范,只要Starter组件符合Spring Boot中自动装配约定的规范,就能实现自动装配。
自动装配在Spring Boot中是通过@EnableAutoConfiguration注解来开启的,这个注解的声明在启动类注解@SpringBootApplication内;
这里简单和大家讲解一下@Enable注解。其实Spring 31版本就已经支持@Enable注解了,它的主要作用把相关组件的Bean装配到IoC容器中,@Enable注解对JavaConfig的进一步完善,为使用Spring Framework的开发者减少了配置代码量,降低了使用的难度。比如常见的@Enable注解有@EnableWebMvc.@EnableScheduling等。
在前面的章节中讲过,如果基于JavaConfig的形式来完成Bean的装载,则必须要使用@Configuration注解及@Bean注解。而@Enable本质上就是针对这两个注解的封装,所以大家如果仔细关注过这些注解,就不难发现这些注解中都会携带一个@Import注解,比如@EnableScheduling
因此,使用@Enable注解后,Spring会解析到@Import导入的配置类,从而根据这个配置类中的描述来实现Bean的装配。大家思考一下,@EnableAutoConfiguration的实现原理是不是也一样呢?
AutoConfigurationImportSelector实现了ImportSelector,它只有一个selectImports抽象方法,并目返回一个String数组,在这个数组中可以指定需要装配到IoC容器的类,当在@Import中导入一个ImportSelectol的实现类之后,会把该实现类中返回的Class名称都装载到IoC容器中;
AutoConfigurationImportSelector实现了ImportSelector,它只有一个selectImports抽象方法,并且返回一个String数组,在这个数组中可以指定需要装配到IoC容器的类,当在@Import中导入一个ImportSelector的实现类之后,会把该实现类中返回的Class名称都装载到IoC容器中。
和@Configuration不同的是,ImportSelector可以实现批量装配,并且还可以通过逻辑处理来实现Bean的选择性装配,也就是可以根据上下文来决定哪些类能够被IC容初始化。接下来通过一个简单的例子带大家了解ImportSelector的使用。
基于前面章节的分析可以猜想到,自动装配的核心是扫描约定目录下的文件进行解析,解析完成之后把得到的Configuration配置类通过ImportSelector进行导入,从而完成Bean的自动装配过程那么接下来我们通过分析AutoConfigurationImportSelector的实现来求证这个猜想是否正确
定位到AutoConfigurationImportSelector中的selectImports方法,它是ImportSelector接口的实现,这个方法中主要有两个功能:
需要注意的是,在AutoConfigurationImportSelector中不执行selectImports方法,而是通过ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法来扫描和注册所有配置类的Bean,最终还是会调用getAutoConfigurationEntry方法获得所有需要自动装配的配置类。
我们重点分析一下配置类的收集方法getAutoConfigurationEntry,结合之前Starter的作用不难猜测到,这个方法应该会扫描指定路径下的文件解析得到需要装配的配置类,而这里面用到了SpringFactoriesLoader,这块内容后续随着代码的展开再来讲解。简单分析一下这段代码,它主要做几件事情:
总的来说,它先获得所有的配置类,通过去重、exclude排除等操作,得到最终需要实现自动装配的配置类这里需要重点关注的是getCandidateConfigurations,它是获得配置类最核心的方法;
这里用到了SpringFactoriesLoader,它是Spring内部提供的一种约定俗成的加载方式,类似于Java中的SPI。简单来说,它会扫描casspath下的META-INF/spring.factories文件,spring.factories文件中的数据以Key=Value形式存储而SpringFactoriesLoaderloadFactoryNames会根据Key得到对应的value值。因此在这个场景中,Key对应为EnableAutoConfiguration,Value是多个配置类,也就是getCandidateConfigurations方法所返回的值
打开RabbitAutoConfiguration,可以看到,它就是一个基于JavaConfig形式的配置类
除了基本的@Configuration注解,还有一个@ConditionalOnClass注解,这个条件控制机制在这里的用途是,判断casspath下是否存在RabbitTemplate和Channel这两个类,如果是,则把当前配置类注册到IoC容器。另外,@EnableConfiqurationProperties是属性配置,也就是说我们可以按照约定在applicationproperties中配置RabbitMQ的参数,而这些配置会加载到RabbitProperties中。实际上,这些东西都是Spring本身就有的功能。
至此,自动装配的原理基本上就分析完了,简单来总结一下核心过程
@Conditional是Spring Framework提供的一个核心注解,这个注解的作用是提供自动装配的条件约束,般与@Configuration和@Bean配合使用。
简单来说,Spring在解析@Configuration配置类时,如果该配置类增加了@Conditional注解,那么会根据该注解配置的条件来决定是否要实现Bean的装配。
@Conditional的注解类声明代码如下,该注解可以接收一个Condition的数组。
Condition是一个函数式接口,提供了matches方法,它主要提供一个条件匹配规则,返回true表示可以注入Bean,反之则不注入。
我们接下来基于@Conditional实现一个条件装配的案例
在BeanClass的bean声明方法中增加@Conditional(GpCondition.class),其中具体的条件是我们自定义的GpCondition类。
上述代码所表达的意思是,如果GpCondition类中的matchs返回true,则将BeanClass装载到SpringIoC容器中。
在Spring Boot中,针对@Conditional做了扩展,提供了更简单的使用形式,扩展注解如下:
除了@Conditional注解类,在Spring Boot中还提供了spring-autoconfigure-metadata.properties文件来实现批量自动装配条件配置。
它的作用和@Conditional是一样的,只是将这些条件配置放在了配置文件中。下面这段配置来自spring-boot-autoconfigure.jar包中的/META-INF/spring-autoconfiqure-metadata.properties文件
同样,这种形式也是“约定优于配置”的体现,通过这种配置化的方式来实现条件过滤必须要遵循两个条件:
这种配置方法的好处在于,它可以有效地降低Spring Boot的启动时间,通过这种过滤方式可以减少配置类的加载数量,因为这个过滤发生在配置类的装载之前,所以它可以降低Spring Boot启动时装载Bean的耗时。
对于自动装配的原理进行分析之后,我们可以基于这个机制来实现一个Starter组件,以便加深大家对自动装配及Starter组件的理解。同时,Spring Boot官方提供的Starter并不能括所有的技术组件,在工作中,如果自己的项目需要支持Spring Boot,也需要开发Starter组件。
从Spring Boot官方提供的Starter的作用来看,Starter组件主要有三个功能:
下面我们先来了解一下starter组件的命名规范
Starter的命名主要分为两类,一类是官方命名,另一类是自定义组件命名。这种命名格式并不是强制性的也是一种约定俗成的方式,可以让开发者更容易识别。
简单来说,官方命名中模块名放在最后,而自定义组件中模块名放在最前面
虽然Spring Boot官方提供了spring-boot-starter-data-redis组件来实现RedisTemplate的自动装配,但是我们仍然基于前面学到的思想实现一个基于Redis简化版本的Starter组件。
·最后一步,使用阶段只需要做两个步骤:添加Starter依赖、设置属性配置
在application.properties中配置host和port,这个属性会自动绑定到RedissonProperties中定义的
属性上。
至此,一个非常简易的手写Starter就完成了,建议大家看完这段内容之后,基于对这块内容的理解尝试自己写一个Starter组件,以便真正掌握它的原理。