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);
// 获取候选的配置
// 获取到所有需要导入到容器中的配置类
List configurations = 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);
}
}
空调维修
- 我的世界电脑版运行身份怎么弄出来(我的世界
- 空调抽湿是什么意思,设置抽湿的温度有什么意
- 方太燃气灶有一个打不着火 怎么修复与排查方法
- 夏季免费清洗汽车空调的宣传口号
- 清洗完空调后出现漏水现象
- iphone6能玩什么游戏(iphone6游戏)
- 如何设置电脑密码锁屏(如何设置电脑密码锁屏
- win10删除开机密码提示不符合密码策略要求
- 电脑w7显示不是正版(w7不是正版怎么解决)
- 万家乐z8热水器显示e7解决 怎么修复与排查方法
- 1匹空调多少瓦数(1匹空调多少瓦)
- 安卓手机连接电脑用什么软件好(关于安卓手机
- 电脑网页看视频卡是什么原因(爱拍看视频卡)
- 华帝燃气灶点火器一直响然后熄火怎么办:问题
- 电脑壁纸怎么换(关于电脑壁纸怎么换的介绍)
- 冬天空调的出风口应该朝什么方向(冬天空调风