1、自动配置原理
1、通常我们都会在启动类上加个@SpringBootApplication
注解,表示当前类是springboot的启动类(入口类)。
package com.baidou;
import org.springframework.boot.SpringApplication;
import org.springframework.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:支持自动配置
。 -
@ComponetScan:组件扫描,扫描主类所在的包以及子包里的bean。
2、查看@SpringBootConfiguration注解源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 表示这个注解它也是spring的配置类
public @interface SpringBootConfiguration {
...
}
3、@ComponetScan组件扫描,扫描并加载符合条件的Bean到容器中
@ComponetScan(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, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
使用debug查看它扫描哪个包下的组件:
这里我们要注意,在定义项目包结构的时候,要定义的非常规范,我们写的代码要放到启动类所在包或子包下,这样才能保证定义的类能够被组件扫描器扫描到。
4.2、@Import(AutoConfigurationImportSelector.class)注解:
导入了一个自动配置类AutoConfigurationImportSelector,表示向spring容器中导入一些组件。
// 实现DeferredImportSelector接口,需要重写一个selectImports方法
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, 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<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);、
// 去除重复的配置类
configurations = removeDuplicates(configurations);
// 获取注解中exclude或excludeName排除的类集合
Set<String> exclusions = getExclusions(annotationMetadata, attributes
// 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常
checkExcludedClasses(configurations, exclusions);
// 去除被排除的类
configurations.removeAll(exclusions);
// 使用spring.factories文件中配置的过滤器对自动配置类进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> 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为:org.springframework.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<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
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 password;
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 = new Jedis();
private final Lettuce lettuce = new Lettuce();
...
}
扫描到这些自动配置类后,要不要创建呢?
这个要结合每个自动配置类上的条件,若条件满足就会创建,一旦创建好自动配置类之后,配置类中所有具有@Bean注解的方法就有可能执行,这些方法返回的就是自动配置的核心对象。
小结:
1、SpringBoot启动会加载大量的自动配置类。
2、看看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
- xxxxAutoConfigurartion:自动配置类,给容器中添加组件。
- xxxxProperties:属性类,封装配置文件中相关属性;
【ctrl + n 搜索 *AutoConfiguration
查看默认的写好的所有配置类】
扩展:@Conditional 条件注解
作用:必须是@Conditional指定的条件成立,才会给容器中添加组件,自动配置类里面的所有内容才生效。
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionOnJava | 系统的Java版本是否符合要求 |
@ConditionOnBean | 容器中存在指定Bean |
@ConditionOnMissingBean | 容器中不存在指定Bean |
@ConditionOnExpression | 满足SpEL表达式指定 |
@ConditionOnClass | 系统中有指定的类 |
@ConditionOnMissingClass | 系统中没有指定的类 |
@ConditionOnSingleCandidate | 容器中只有一个指定的bean,或者这个bean是首选bean |
@ConditionOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNoWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
2、自定义starter启动器
约定大于配置
2.1 启动器介绍
SpringBoot启动器的作用:
- 配置一个启动器它会把整合这个框架或模块的依赖全部导入。
- 每一个启动器都有一个自动配置类,实现自动整合Spring。
- 每一个启动器都有一个属性类,提供了默认的属性配置。
2.2 自定义启动器
自定义Jedis的starter,参考Redis的自动配置类
starter命名规范:
- 官方提供:spring-boot-starter-xxx,例如:比如:spring-boot-starter-web、spring-boot-starter-data-redis等等。
- 自定义:xxx-spring-boot-starter,例如:mybatis-spring-boot-starter等等。
1、创建一个Maven模块,取名为:jedis-spring-boot-starter。
starter是一个空jar,它唯一的目的是提供这个库所必须的依赖。
2、导入相关依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<dependencies>
<!-- 自定义starter -->
<!-- starter依赖包含autoconfigure,所以直接导入stater即可-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Compile dependencies -->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-autoconfigure</artifactId>-->
<!--</dependency>-->
<!-- 能够让我们编写的配置文件在其他人引用的时候有一定提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--jedis(redis的java客户端)-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
3、编写jedis属性配置类JedisProperties。
package com.baidou.autoconfigure;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Jedis属性配置类
*
* @author 白豆五
* @version 2023/1/16
* @since JDK8
*/
@ConfigurationProperties(prefix = "my.redis") //批量注入配置文件中前缀为my.redis的配置信息
@Data
public class JedisProperties {
// redis服务器的主机ip
private String host = "localhost";
// redis端口号
private int port = 6379;
}
4、编写JedisTemplate类,封装对字符串增删改查操作。
package com.baidou.autoconfigure;
import redis.clients.jedis.Jedis;
/**
* JedisTemplate,操作Redis数据库的模板对象
*
* @author 白豆五
* @version 2023/1/16 20:29
* @since JDK8
*/
public class JedisTemplate {
private JedisProperties jedisProperties;
// 通过构造器方式注入对象
public JedisTemplate(JedisProperties jedisProperties) {
this.jedisProperties = jedisProperties;
}
// 封装对字符串增删改查操作
public void set(String key, String value) {
Jedis jedis = new Jedis(jedisProperties.getHost(), jedisProperties.getPort());
jedis.set(key, value);
jedis.close();
}
public String get(String key) {
Jedis jedis = new Jedis(jedisProperties.getHost(), jedisProperties.getPort());
String value = jedis.get(key);
jedis.close();
return value;
}
public void del(String key) {
Jedis jedis = new Jedis(jedisProperties.getHost(), jedisProperties.getPort());
jedis.del(key);
jedis.close();
}
}
5、编写自动配置类,JedisAutoConfiguration。
package com.baidou.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
/**
* 自定义的自动配置类
* @author 白豆五
* @version 2023/1/16
* @since JDK8
*/
@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 new JedisTemplate(jedisProperties);
}
}
6、在resources资源目录下创建META-INF/spring.factories
配置文件,配置我们的自动装配类。
key还是:org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.baidou.autoconfigure.JedisAutoConfiguration
7、将项目安装到本地仓库。
8、创建一个springboot项目,使用这个自定义starter。
8.1、导入依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>com.baidou</groupId>
<artifactId>jedis-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!--打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
8.2、在springboot核心配置文件application.yml中,设置redis的ip和端口号
my:
redis:
host: 192.168.203.157
port: 6379
8.3、编写测试
package com.baidou.test;
import com.baidou.autoconfigure.JedisTemplate;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 测试类
*
* @author 白豆五
* @version 2023/1/16
* @since JDK8
*/
@SpringBootTest
public class JedisStarterTest {
@Autowired
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);
}
}