Spring boot 自动装配原理
创始人
2024-03-15 13:23:35

Spring boot 为了解决Bean的复杂配置,引入了自动装配机制;那么什么是自动装配,它的原理又是什么呢?我们先通过以下例子来了解以一下什么是自动装配。

Spring boot 集成 redis

  • 引入依赖包

    		org.springframework.bootspring-boot-starter-data-redis2.6.0
    
  • 配置相关参数

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=
    spring.redis.database=0
    
  • controller

    @RestController
    @RequestMapping(value = "/redis")
    public class RedisController {@Autowiredprivate RedisTemplate redisTemplate;@RequestMapping("/save")public String save(@RequestParam String key, @RequestParam String value) {redisTemplate.opsForValue().set(key, value);return "ok";}@RequestMapping("/get")public String get(@RequestParam String key) {return (String) redisTemplate.opsForValue().get(key);}}
    

这是一个很简单的集成 redis 的案例,通过这个案例我们可以看出:RedisTemplate 这个类的 bean 对象,我们并没有通过 xml 的形式或者 注解 的形式注入到 ioc 容器中,但是我们可以直接通过 Autowired 注解自动从容器里面拿到相应的 bean 对象,从而进行属性的注入。

这就是Spring boot 中的自动装配,那这是怎么做到的呢?我们来分析一下它的原理,从而来理解RedisTemplate 是如何注入的。

自动装配原理

我们先来分析下上面案例的流程:

首先我们在集成的时候只做了一件事:引入依赖包;然后启动项目后,对象就自动进入到 IOC 容器中了。

那它是如何自动注入的呢?我们平常手动注入对象的时候,是通过 component 或者就是configuration中的bean注入。那它会不会也是呢?如果是这样,那按照理论来说应该也有一个componentscan来扫描对应路径,从而将对象注入;但是 spring boot 中可以引入各种各样的依赖,扫描的路径肯定也是千奇百怪的,那我们猜测应该是有一个固定的路径让其去扫描,从而能将依赖自动注入到容器中。

这个逻辑是不是有点眼熟,这不就是SPI机制吗?接下来我们从源码中逐步看下是如何实现的。

@SpringBootApplication注解是入口:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}

其中比较重要的是有以下三个注解:

  • @SpringBootConfiguration

    继承了 Configuration,表示当前是注解类

  • @EnableAutoConfiguration

    开启 spring boot 的注解功能,自动装配机制,借助 @import注解的功能。

  • @ComponentScan(excludeFilters = {})

    自动扫描并加载符合条件的组件(比如component,controller等),并将这些对象加载到 ioc 容器中。

    我们可以通过basepackages等属性来定制其自动扫描的范围,如果不指定,则默认会从声明。@ComponentScan所在类的 package 进行扫描,这就是为什么我们希望启动类放在根目录下的原因。

@EnableAutoConfiguration

这个注解我们看名字就是可以自动装配,显而易见就是最重要的那个了。

@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({AutoConfigurationImportSelector.class})

AutoConfigurationPackage

我们先来看这个注解,看看这个注解做了那些事情:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {String[] basePackages() default {};Class[] basePackageClasses() default {};
}

首先通过 import注解导入了Registrar类:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}// 注册当前启动类的根 packagepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));}public Set determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));}
}
 

我们可以这样理解它的作用:将添加该注解的类所在的package 作为自动配置package进行管理。就是我们上文所说的当Spring boot应用启动时会将启动类所在的package作为自动配置的package。

AutoConfigurationImportSelector

接下来我们来看@Import({AutoConfigurationImportSelector.class}),借助AutoConfigurationImportSelector,EnableAutoConfiguration可以帮助spring boot 项目将所有符合条件(Spring.factories)的bean定义都加载到当前IOC容器中。

该类最大的作用就是帮助我们找相关的配置类,而找配置类的过程就是我们上面所说的SPI机制。

关于SPI机制可以看这篇文章:SPI 机制详解

我们继续来看整体流程:首先看selectImports方法,该方法是找配置文件的入口。

为什么是这个方法呢?因为这个类继承了DeferredImportSelector接口,接口又继承了ImportSelector接口,所以在spring的流程中会进行调用。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {public AutoConfigurationImportSelector() {}// 找配置文件public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
}

然后进入getAutoConfigurationEntry方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);// 获取配置信息List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);configurations = this.removeDuplicates(configurations);Set exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}
}

继续往下,我们来看看它是如何获取配置信息的getCandidateConfigurations方法:

该方法主要是去加载各个组件jar下的 spring.factories文件

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

看到SpringFactoriesLoader.loadFactoryNames是不是很眼熟?和我们SPI机制中的ServiceLoader.load是不是很类似?看来我们找对地方了,SpringFactoriesLoader的底层原理其实就是借鉴于JDK的SPI机制。

我们知道SPI机制都是从classpath下的service目录查找对应的文件?那么SpringFactoriesLoader从哪里查找呢?我们进入该类查看:

一进来我们就看到路径也被写死了,这样我们上面的问题就被解决了。

public final class SpringFactoriesLoader {public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);static final Map>> cache = new ConcurrentReferenceHashMap();
}

我们到spring boot 的自动装配jar中查看一下:

整个流程是不是很符合SPI机制,这样redis对象自动装配是不是能稍微想通了?

在这里插入图片描述

既然找到了文件,接下来肯定就是读取配置了,我们打断点来看下效果:

可以很清楚的看到spring.factories文件中的配置信息都读取到了。

在这里插入图片描述

我们来看下loadFactoryNames方法,它的入参为工厂类名称和对应的类加载器,方法会根据指定的类加载器,加载该类加载器搜索路径下的指定文件,即spring.factories文件。

传入的工厂类为接口,而文件中对应的类则是接口的实现类,或最终作为实现类。

配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名:org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的KEY,获取对应的一组Configuration类。

总结

看过源码之后,我们对Spring boot 的装配原理进行一个简单的总结,方便我们记忆:

  • 首先 spring boot 启动

  • 扫描 @SpringBootApplication注解

  • 扫描@EnableAutoConfiguration注解

    这个注解里面带有一个@import注解,导入了AutoConfigurationImportSelector类,这个类实现了DeferredImportSelector接口,这个接口又实现了ImportSelector接口,在这个接口里面有一个叫做selectImports的方法,这个方法实现了配置类的寻找。

    整个寻找的过程就是Springboot 的SPI机制。

相关内容

热门资讯

应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...