SpringBoot自动配置原理

家电维修 2023-07-16 19:16www.caominkang.com家电维修技术

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 determineImports(AnnotationMetadata metadata) {
  return Collections.singleton(ne PackageImports(metadata));
 }

}
 
 

使用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 redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate template = ne RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
		StringRedisTemplate template = ne 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 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指定的条件成立,才会给容器中添加组件,自动配置类里面的所有内容才生效。

@Conditional扩展注解作用(判断是否满足当前指定条件)@ConditionOnJava系统的Java版本是否符合要求@ConditionOnBean容器中存在指定Bean@ConditionOnMissingBean容器中不存在指定Bean@ConditionOnExpression满足SpEL表达式指定@ConditionOnClass系统中有指定的类@ConditionOnMissingClass系统中没有指定的类@ConditionOnSingleCandidate容器中只有一个指定的bean,或者这个bean是首选bean@ConditionOnProperty系统中指定的属性是否有指定的值@ConditionOnResource类路径下是否存在指定资源文件@ConditionalOnWebApplication当前是eb环境@ConditionalOnNoWebApplication当前不是eb环境@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-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-parent
 2.4.5



  
  
  
   .springframeork.boot
   spring-boot-starter
  
  
  
  
  
  

  
  
   .springframeork.boot
   spring-boot-configuration-processor
  
  
  
   redis.clients
   jedis
   2.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-parent
 2.4.5
  



 
  .baidou
  jedis-spring-boot-starter
  1.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);

 }
}

Copyright © 2016-2025 www.jianfeikang.com 建飞家电维修 版权所有 Power by