SpringBoot自动配置原理

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环境
@ConditionalOnJndiJNDI存在指定项

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-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);

    }
}

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5DmxwNN0-1673878197778)(SpringBoot自动配置原理.assets/image-20230116220556792.png)]

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白豆五

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值