转载本文需注明出处:微信公众号EAWorld,违者必究。
前言:
在Java服务端领域,Spring框架已是声名远扬,人们在使用其强大功能辅助开发的过程中,却也渐渐感受到随着项目规模的扩大,需要引入的Spring相关配置也越来越多,令人不胜其烦,而由Pivotal团队基于Spring框架推出的开源轻量级框架Spring Boot,就很好的解决了Spring时代项目配置繁琐的问题,至于Spring Boot是如何做到简化配置的,这就引出了我们今天的主题—Spring Boot自动装配。
目录:
1、什么是自动装配
2、Spring Boot自动装配之前世今生
Spring Framework手动装配
Spring Boot自动装配
3、Spring Boot自动装配实践
1.什么是自动装配
在机械制造工程中,机器装配的自动化已在多年前运用到实际的生产线上,例如,生产一台电动机,大量的零部件生产出来后,如果仍由手工装配,则劳动强度大、效率低、质量也不能保证,在数控装配机、自动装配线等工业技术问世后,大量零件自动装配成一台合格的机器已成为现实,毫无疑问,机械制造领域的自动装配大大地提升了生产力。
机械制造工程中的自动装配指的是零件直接自动组装成机器,同理,软件工程中的自动装配自然指的是软件模块之间的自动组装,最终成型一个完整的软件。
那么在SpringBoot框架中,仅通过少量代码,就实现了Spring框架各个组件的自动组装,一个完整的服务端项目便被轻松构建出来,这,就是SpringBoot的自动装配。
2.Spring Boot自动装配之前世今生
Spring Boot的自动装配源于Spring Framework的手动装配,在Spring Boot场景下,基于约定大于配置的原则,实现Spring组件自动装配的目的。其中使用了以下前三种Spring Framework手动装配技术:
底层装配技术
Spring 模式注解装配
Spring @Enable 模块装配
Spring 条件装配
Spring 工厂加载机制
所以要明白SpringBoot自动自动装配是如何实现的,就必须了解Spring Framework的手动装配。
Spring Framework是一个强大的开源轻量级应用开发框架,主要用于Java企业开发。相信做Java Web开发的同学对它应该比较熟悉,那么上述几种Spring Framework的装配方式,其具体的使用又是如何呢,下面我们会一一介绍到。
Spring Framework手动装配
Spring模式注解装配(Stereotype Annotations)
- 模式注解定义
定义:一种用于声明在应用中扮演“组件”角色的注解。
怎么理解上述模式注解的定义呢,通俗的来讲,将一个Spring项目比作一座工厂,其创建的各种Java对象会被赋予不同的职责(即各种角色),这些Java对象就类似于流水线上的各类工人,各自负责一小段商品生产任务。各种流水线工人的职责可通过不同的工作服来标识,而在Spring Framework中,Java对象的职责则由模式注解来标识。
上图为Spring官方文档中对于模式注解(Stereotype Annotations)的说明,可以看到,@Component作为一种由Spring容器托管的通用模式组件,任何被@Component标识的组件均为组件扫描的候选对象,包括被@Component元标注的注解,当任何组件标识它时,也会被视作组件扫描的候选对象。以@Service为例,我们来看看它的源码:
可以看到,@Service确实被@Component标注了,所以被@Service注解的组件也将会被Spring容器扫描到。
- 常用模式注解整理
将常用模式注解整理成下方表格:
- 装配方式
装配方式分为两种:
@ComponentScan方式
先来看看context:component-scan方式:
如上图所示,通过xml配置的方式,<context:annotation-config />激活注解驱动特性,<context:component-scan base-package=”com.example.demo” />指定扫描组件的根路径,将被@Component注解的类加载进Spring容器。
再来看看@ComponentScan方式:
只需要在主配置类上添加@ComponentScan(value="需要扫描的包名前缀")注解即可。
模式注解在基于Spring框架开发Web服务时是极常用的注解,相信做Java Web开发的同学一定深有体会。
当我们了解完Spring Framework的模式注解装配,接着来到第二部分@Enable模块装配。
Spring @Enable模块装配
- Enable 模块定义
定义:具备相同领域的功能组件集合,组合所形成的一个独立的单元。
Spring Framework 3.1 开始支持“@Enable模块驱动”。所谓“模块”是指具备相同领域的功能组件集合,组合所形成一个独立的单元。在Spring Framework中,存在Web MVC模块,AspectJ代理模块,Caching(缓存)模块,JMX(java管理扩展)模块,Async(异步处理)模块等。
- 常见@Enable注解模块整理
将常见@Enable注解模块整理成下方表格:
- 装配方式
装配方式分为两种:
注解驱动方式
接口编程方式
先来看看注解驱动方式,我们以@EnableWebMvc注解为例,贴上其源码。
可以看到,@EnableWebMvc被@Import标注了,@Import的作用是将DelegatingWebMvcConfiguration装载进Spring容器,我们再进入到DelegatingWebMvcConfiguration中,发现其已被@Configuration标注,如下图所示:
看到@Configuration注解,就能知道,DelegatingWebMvcConfiguration将会被Spring容器扫描到并加载进容器,这种方式,就是Enable模块的注解驱动方式。
再来说说接口编程方式,这里我们以@EnableCaching为例,@EnableCaching的作用是激活Spring的缓存。贴上源码:
从源码可以看到,@Import引入的是CachingConfigurationSelector类,再进入到CachingConfigurationSelector类,下图为其源码:
这里我们就发现,CachingConfigurationSelector继承了AdviceModelImportSelector,而AdviceModelImportSelector是一个抽象类,其实现了ImportSelector接口,selectImports方法为ImportSelector接口的抽象方法,该方法的作用是获取需要加载进Spring容器的配置类。正如CachingConfigurationSelector是通过实现ImportSelector方法来选择相应的配置类导入进Spring容器,这就是第二种装配方式—接口编程方式。
相较于注解驱动方式,接口编程方式显得更为灵活,ImportSelector方法中可通过条件判断语句实现不同配置类的引入。
以上就是Enable模块装配的两种方式,接下来,我们来看看Spring的条件装配。
Spring 条件装配
- 条件装配定义
定义:Bean装配的前置判断。
从Spring Framework 3.1 开始,Spring允许在Bean装配进Spring容器时增加前置条件判断。
- 实现方式
Spring提供的条件装配实现方式有两种
@Profile(配置化条件装配,Spring起始版本3.1)
@Conditional(编程条件装配,Spring起始版本4.0)
先来看看@Profile是如何实现条件装配的。
Spring中的@Profile与maven中的profile类似,能根据当前环境来选择性地向Spring容器注入相应的Bean。
请看以下示例:
创建一个SpringBoot项目,在项目下新建TestService接口,代码如下
新建Test1Service,Test2Service类,分别实现TestService接口
可以看到,Test1Service与Test2Service均被@Profile标注,但value不同,一个为test1,另一个为test2。
接着在启动类中设置profile参数,并从Spring容器中获取TestService的实现类对象,如下所示
此时我们启动该类,控制台打印如下所示:
说明此时Spring容器中TestService的实现类对象是Test1Service对象,当把profiles参数值改为test2时,启动Application,控制台便会打印Hello World---222,此处不再贴图。
上述示例标明了@Profile可做到条件装配Bean进Spring容器。
自Spring 4.0之后,@Profile的实现方式发生变动,其内部也是通过@Conditional注解来实现条件装配的;所以接下来,我们来看看如何使用@Conditional方式做到条件装配。
@Conditional方式实现,贴上源码
可以看到,它的value是一个Condition子类字节码对象,让我们再进到Condition中
发现存在matches方法,那么这个方法就是用来判断组件是否能被注册进Spring容器,如果返回true,则被@Conditional标注的组件可以被装载到Spring容器,反之,则无法注入。
我们来简单看一个具体的源码示例。
在Spring Boot中,有时需要控制配置类是否生效,可以使用@ConditionalOnProperty注解来控制@Configuration是否生效。
新建一个配置类,并标注上@Configuration和@ConditionalOnProperty
进入到@ConditionalOnProperty,发现其条件判断由OnPropertyCondition实现,源码如下:
这里我们忽略具体逻辑实现,只看其实现流程。再进入到OnPropertyCondition类中,贴上类图:
从图中能看到,OnPropertyCondition继承了SpringBootCondition,而SpringBootCondition实现了Condition,上文我们说到,判断配置类是否注入需要实现Condition中的matches方法,此时我们进入SpringBootCondition,发现matches方法已被其实现,如下图所示:
所以OnPropertyCondition作为子类也会继承这个方法实现,最终OnPropertyCondition作为@Conditional的判断条件,根据其内部的matches方法返回值判断组件是否能被注入。
以上就是Spring Framework手动装配的几种方式,限于篇幅,在这里只是粗略的做个介绍,有兴趣的同学可以去更深入的了解下。
说完了Spring Framework的手动装配,自然该说到基于Spring Framework手动装配的SpringBoot自动装配了。
Spring Boot自动装配
Spring Boot 自动装配定义
定义:基于约定大于配置的原则,实现Spring组件自动装配的目的。
在Spring Boot中,约定大于配置可以从以下两个方面来理解:
开发人员仅需规定应用中不符合约定的部分。
在没有规定配置的地方,采用默认配置,以力求最简配置为核心思想。简单来说,就是减少人为配置,尽量使用默认配置。这样可以大大减少配置工作,这就是所谓的“约定”。
Spring Boot自动装配的底层实现机制:模式注解,@Enable模块,条件装配,工厂加载机制,其中模式注解,@Enable模块,条件装配在上文已经提到,而工厂加载机制其实也是Spring Framework中的一个机制,我们来看看其是如何实现的。
Spring工厂加载机制
Spring Framework中有一个SpringFactoriesLoader类,它是实现工厂加载机制的核心。
他的主要作用是从META-INF/spring.factories文件中加载指定接口的实现类,该文件可能存在于工程类路径下或jar包内,所以会存在多个spring.factories文件。下图所示META-INF/spring.factories路径配置在其源码中。
SpringFactoriesLoader通过它的loadFactories方法从FACTORIES_RESOURCE_LOCATION文件中实例化指定类型的工厂实现类,且spring.factories文件必须采用Properties格式,我们来看看一个spring.factories文件内部是怎样的:
可以看到在该文件中,键是接口或抽象类的全路径名,值是逗号分隔的实现类列表。至于SpringFactoriesLoader具体是如何实例化这些实现类列表的,接下来我会以Spring Boot自动装配为例说明。
Spring手动装配和Spring工厂加载机制在Spring Boot自动装配中的应用
在我们开启Spring的自动装配功能时,会使用到@EnableAutoConfiguration这个注解,贴上其源码:
分析源码我们看到,@EnableAutoConfiguration被@Import标注,引入AutoConfigurationImportSelector类,此处就是用到了Spring的Enable模块装配,AutoConfigurationImportSelector类实现了ImportSelector接口,在Enable模块装配方式中我们提到通过实现ImportSelector接口的selectImports抽象方法来选择要注入Spring容器的配置类。在AutoConfigurationImportSelector类中,可以发现存在这样一个方法会被selectImports方法调用,该方法如下图:
可以看到方法内部调用了SpringFactoriesLoader的loadFactories方法。贴上loadFactories源码:
该方法会加载所有以factoryClass为key的实现类,它会调用loadFactoryNames方法,而loadFactoryNames方法又会调用loadSpringFactories方法,从spring.factories文件中获取到所有实现类的全路径名。下图为loadSpringFactories方法源码:
下图为spring.factories文件源码:
那么到这里,Spring Boot已经实例化了上图的EnableAutoConfiguration的配置实现类集合,我们以WebMvcAutoConfiguration为例,看看其内部做了什么,贴上源码:
发现其被@Configuration以及@ConditionalXXX标注,而@Configuration是Spring 模式注解中的一种,@ConditionalXXX则是Spring 条件装配。
所以从整个SpringBoot自动装配的流程来看,@EnableAutoConfiguration用于激活自动装配,它通过@Import注解加载EnableAutoConfiguration的实现配置类集合进Spring容器,而大量的EnableAutoConfiguration的实现配置类被注入Spring容器就意味着,Spring Framework的许多功能模块会被装配到Spring工程中,最终我们发现,只需要在启动类上标注@EnableAutoConfiguration,Spring的功能模块就会自动被装配进工厂,这就是SpringBoot的自动装配。
接下来,我们来看一个自定义实现Spring Boot自动装配的例子。
3.Spring Boot自动装配实践
整个过程分为三步:
激活自动装配:
@EnableAutoConfiguration
实现自动装配:
XXXAutoConfiguration
配置自动装配实现:
META-INF/spring.factories
具体步骤
新建一个SpringBoot项目。
自定义@Enable模块注解
**1.**在项目下新建一个配置类TestConfiguration,源码如下:
package com.example.demo;
**2.**新建文件TestImportSelector,代码如下:
package com.example.demo;
此文件中实现ImportSelector接口的selectImports方法,返回TestConfiguration名称。
**3.**新建文件EnableTest,源码如下:
package com.example.demo;
@EnableTest被@Import引入TestImportSelector类,此时,我们已经完成了自定义@Enable模块注解。
自定义@Conditional条件注解
**1.**新建文件ConditionalOnSystemProperty,源码如下:
package com.example.demo;
**2.**新建文件OnSystemPropertyCondition,源码如下:
package com.example.demo;
此时我们就完成了自定义@Conditional条件注解,该注解会根据传入的name,获取系统属性,并匹配传入的value,如果相等,则返回true,表明条件满足,反之,则不满足。
自定义配置实现类
**1.**新建文件TestAutoConfiguration,源码如下:
package com.example.demo.configuration;
此时,我们可以看到这个类中已经用到了Spring Framework的三种手动装配。
**2.**Resources目录下新建文件META-INF/spring.factories,源码如下:
# 自动装配
**3.**最后,我们在项目下新建启动类EnableAutoConfigurationApplication,源码如下:
package com.example.demo;
到此,我们的自定义SpringBoot自动装配已经完成,启动EnableAutoConfigurationApplication,会发现控制台打印如下:
以上就是Spring Boot的自动装配实践,Spring Boot的自动装配是一个很复杂的功能,本文只是粗略地讲述其过程,若有兴趣深入了解,仍需搜寻更多资料补充。
关于作者:拙言,普元Java开发工程师,参与太平洋保险,普元移动8.0项目的开发。专注于java领域,路漫漫其修远兮,吾将上下而求索。
关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享。