SpringBoot自动配置原理
1、自动配置原理
1、通常我们都会在启动类上加个@SpringBootApplication注解,表示当前类是springboot的启动类(入口类)。
package .baidou; import .springframeork.boot.SpringApplication; import .springframeork.boot.autoconfigure.SpringBootApplication; // @SpringBootApplication: 一切魔力的开始 @SpringBootApplication //表示当前类是springboot的启动类 public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
点击@SpringBootApplication查看源码
// 前四个是元注解 @Target(ElementType.TYPE) // 说明这个注解作用在类或接口上 @Retention(RetentionPolicy.RUNTIME) // 控制注解的生命周期,RUNTIME表示一直存活(源码阶段、字节码文件阶段、运行阶段) @Documented // 是否可以生成文档 @Inherited // 是否可以继承 // 核心注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ... }
@SpringBootApplication它是一个组合注解:
-
@SpringBootConfiguration声明当前类是一个springboot的配置类。
-
@EnableAutoConfiguration支持自动配置。
-
@CompoScan组件扫描,扫描主类所在的包以及子包里的bean。
2、查看@SpringBootConfiguration注解源码
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration // 表示这个注解它也是spring的配置类 public @interface SpringBootConfiguration { ... }
3、@CompoScan组件扫描,扫描并加载符合条件的Bean到容器中
@CompoScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
4、查看@EnableAutoConfiguration注解源码
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage //指定需要扫描配置的包 @Import(AutoConfigurationImportSelector.class)//导入AutoConfigurationImportSelector这个配置类(加载自动配置的类) public @interface EnableAutoConfiguration {
4.1、点击@AutoConfigurationPackage注解,发现导入这么一个静态内部类AutoConfigurationPackages.Registrar。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) //导入Registrar中注册的组件 // @AutoConfigurationPackage注解的主要作用就是将启动类所在包及所有子包下的组件到扫描到spring容器中。 public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class>[] basePackageClasses() default {}; }
接着点击Registrar类查看源码
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { // 给容器中导入某个组件类 // 根据传入的元注解信息获取所在的包, 将包中组件类封装为数组进行注册 @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, ne PackageImports(metadata).getPackageNames().toArray(ne String[0])); } @Override public Set
使用debug查看它扫描哪个包下的组件
这里我们要注意,在定义项目包结构的时候,要定义的非常规范,我们写的代码要放到启动类所在包或子包下,这样才能保证定义的类能够被组件扫描器扫描到。
4.2、@Import(AutoConfigurationImportSelector.class)注解
导入了一个自动配置类AutoConfigurationImportSelector,表示向spring容器中导入一些组件。
// 实现DeferredImportSelector接口,需要重写一个selectImports方法 public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAare, ResourceLoaderAare, BeanFactoryAare, EnvironmentAare, Ordered { ... // 此方法的返回值都会加载到spring容器中(bean的全限定名数组) @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 判断SpringBoot是否开启自动配置 if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } // 获取需要自动配置的bean信息 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { // 判断是否开启自动配置 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 获取@EnableAutoConfiguration注解的属性,也就是exclude和excludeName AnnotationAttributes attributes = getAttributes(annotationMetadata); // 获取候选的配置 // 获取到所有需要导入到容器中的配置类 Listconfigurations = getCandidateConfigurations(annotationMetadata, attributes);、 // 去除重复的配置类 configurations = removeDuplicates(configurations); // 获取注解中exclude或excludeName排除的类集合 Set exclusions = getExclusions(annotationMetadata, attributes // 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常 checkExcludedClasses(configurations, exclusions); // 去除被排除的类 configurations.removeAll(exclusions); // 使用spring.factories文件中配置的过滤器对自动配置类进行过滤 configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return ne AutoConfigurationEntry(configurations, exclusions); } protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List configurations = // 扫描所有jar包类路径下 "META-INF/spring.factories文件 // 在spring-boot-autoconfigure中 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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; } }
加载当前项目中所有jar包的META-INF/spring.factories下key为:.springframeork.boot.autoconfigure.EnableAutoConfiguration的value值,他的value值就是这130个自动配置类。(第三方stater整合springboot也要提供spring.factories,stater机制)
每一个这样的xxxAutoConfiguration类都是容器中的一个组件,最终都会加入到容器中;用他们来做自动配置!!!
虽然我们130个自动配置类默认是全部加载,最终它会按照@Conditional条件装配。(生效的配置类就会给容器中装配很多组件)
例如RedisAutoConfiguration
@Configuration(proxyBeanMethods = false) //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件 @ConditionalOnClass(RedisOperations.class) //条件 当项目导入starter-data-redis依赖时才会下限执行 @EnableConfigurationProperties(RedisProperties.class) //让RedisProperties对象读取配置文件中的信息 @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) // public class RedisAutoConfiguration { //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @Bean @ConditionalOnMissingBean(name = "redisTemplate")//判断容器有没有这个组件,springioc容器中没有则创建 @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public RedisTemplate
RedisProperties:
// 批量注入配置文件中前缀为spring.redis的配置信息 @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { private int database = 0; private String url; private String host = "localhost"; private String username; private String passord; private int port = 6379; private boolean ssl; private Duration timeout; private Duration connectTimeout; private String clientName; private ClientType clientType; private Sentinel sentinel; private Cluster cluster; private final Jedis jedis = ne Jedis(); private final Lettuce lettuce = ne Lettuce(); ... }
扫描到这些自动配置类后,要不要创建呢?
这个要结合每个自动配置类上的条件,若条件满足就会创建,一旦创建好自动配置类之后,配置类中所有具有@Bean注解的方法就有可能执行,这些方法返回的就是自动配置的核心对象。
小结
1、SpringBoot启动会加载大量的自动配置类。
2、看看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
- xxxxAutoConfigurartion自动配置类,给容器中添加组件。
- xxxxProperties:属性类,封装配置文件中相关属性;
【ctrl + n 搜索 AutoConfiguration 查看默认的写好的所有配置类】
扩展@Conditional 条件注解
作用必须是@Conditional指定的条件成立,才会给容器中添加组件,自动配置类里面的所有内容才生效。
2、自定义starter启动器
2.1 启动器介绍约定大于配置
官方启动器介绍: https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/using-boot-build-systems.html#using-boot-starter
SpringBoot启动器的作用
- 配置一个启动器它会把整合这个框架或模块的依赖全部导入。
- 每一个启动器都有一个自动配置类,实现自动整合Spring。
- 每一个启动器都有一个属性类,提供了默认的属性配置。
2.2 自定义启动器
自定义Jedis的starter,参考Redis的自动配置类
starter命名规范
- 官方提供spring-boot-starter-xxx,例如比如spring-boot-starter-eb、spring-boot-starter-data-redis等等。
- 自定义xxx-spring-boot-starter,例如mybatis-spring-boot-starter等等。
1、创建一个Maven模块,取名为jedis-spring-boot-starter。
starter是一个空jar,它唯一的目的是提供这个库所必须的依赖。
2、导入相关依赖
.springframeork.boot spring-boot-starter-parent2.4.5 .springframeork.boot spring-boot-starter.springframeork.boot spring-boot-configuration-processorredis.clients jedis2.9.0 .projectlombok lombok
3、编写jedis属性配置类JedisProperties。
package .baidou.autoconfigure; import lombok.Data; import .springframeork.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "my.redis") //批量注入配置文件中前缀为my.redis的配置信息 @Data public class JedisProperties { // redis服务器的主机ip private String host = "localhost"; // redis端口号 private int port = 6379; }
4、编写JedisTemplate类,封装对字符串增删改查操作。
package .baidou.autoconfigure; import redis.clients.jedis.Jedis; public class JedisTemplate { private JedisProperties jedisProperties; // 通过构造器方式注入对象 public JedisTemplate(JedisProperties jedisProperties) { this.jedisProperties = jedisProperties; } // 封装对字符串增删改查操作 public void set(String key, String value) { Jedis jedis = ne Jedis(jedisProperties.getHost(), jedisProperties.getPort()); jedis.set(key, value); jedis.close(); } public String get(String key) { Jedis jedis = ne Jedis(jedisProperties.getHost(), jedisProperties.getPort()); String value = jedis.get(key); jedis.close(); return value; } public void del(String key) { Jedis jedis = ne Jedis(jedisProperties.getHost(), jedisProperties.getPort()); jedis.del(key); jedis.close(); } }
5、编写自动配置类,JedisAutoConfiguration。
package .baidou.autoconfigure; import .springframeork.boot.autoconfigure.condition.ConditionalOnClass; import .springframeork.boot.context.properties.EnableConfigurationProperties; import .springframeork.context.annotation.Bean; import .springframeork.context.annotation.Configuration; import redis.clients.jedis.Jedis; @Configuration //声明当前类是一个配置类 @ConditionalOnClass({Jedis.class,JedisTemplate.class})//当项目导入Jedis和我们自定义的stater时,才会自动装配该对象 @EnableConfigurationProperties(JedisProperties.class) //创建JedisProperties对象,读取配置文件中的信息 public class JedisAutoConfiguration { // 定义bean // @Configuration+@Bean方式定义bean,方法返回值的bean交给springioc容器处理。 @Bean public JedisTemplate jedisTemplate(JedisProperties jedisProperties){ return ne JedisTemplate(jedisProperties); } }
6、在resources资源目录下创建META-INF/spring.factories配置文件,配置我们的自动装配类。
key还是.springframeork.boot.autoconfigure.EnableAutoConfiguration
.springframeork.boot.autoconfigure.EnableAutoConfiguration=.baidou.autoconfigure.JedisAutoConfiguration
7、将项目安装到本地仓库。
8、创建一个springboot项目,使用这个自定义starter。
8.1、导入依赖
.springframeork.boot spring-boot-starter-parent2.4.5 .baidou jedis-spring-boot-starter1.0-SNAPSHOT .springframeork.boot spring-boot-starter-test.springframeork.boot spring-boot-maven-plugin
8.2、在springboot核心配置文件application.yml中,设置redis的ip和端口号
my: redis: host: 192.168.203.157 port: 6379
8.3、编写测试
package .baidou.test; import .baidou.autoconfigure.JedisTemplate; import .junit.jupiter.api.Test; import .springframeork.beans.factory.annotation.Autoired; import .springframeork.boot.test.context.SpringBootTest; @SpringBootTest public class JedisStarterTest { @Autoired JedisTemplate jedisTemplate; //注入starter中的模板对象 @Test public void test0() { // 添加字符串数据 jedisTemplate.set("dog", "旺财"); // 获取key的value值 String value = jedisTemplate.get("dog"); System.out.println("dog---" + value); // 修改数据 jedisTemplate.set("dog", "巴拉巴拉"); value = jedisTemplate.get("dog"); System.out.println("dog---" + value); // 删除数据 // jedisTemplate.del("dog"); // value = jedisTemplate.get("dog"); // System.out.println("dog---" + value); } }