springboot自动配置原理解析

 更新时间:2021年04月22日 11:45:46   作者:是时候改个好点的名字  
这篇文章主要介绍了springboot自动配置原理解析,帮助大家更好的理解和学习使用springboot,感兴趣的朋友可以了解下

前言

小伙伴们都知道,现在市面上最流行的web开发框架就是springboot了,在springboot开始流行之前,我们都用的是strust2或者是springmvc框架来开发web应用,但是这两个框架都有一个特点就是配置非常的繁琐,要写一大堆的配置文件,spring在支持了注解开发之后稍微有些改观但有的时候还是会觉得比较麻烦,这个时候springboot就体现出了它的优势,springboot只需要一个properties或者yml文件就可以简化springmvc中在xml中需要配置的一大堆的bean,这就是因为springboot有自动配置,那么springboot自动配置的原理是什么呢,今天我们就来通过源码分析一下springboot的自动配置原理

开始

我以springboot整合redis为例,来向大家分析springboot的自动配置原理

首先创建一个springboot工程用来测试,然后在pom文件中引入springboot-starter-redis的启动器依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.7.RELEASE</version>
        </dependency>
</dependencies>

然后,在application.properties中配置redis属性

spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=0

然后,在启动类中注入redisTemplate类,redisTemplate为spring官方提供的对redis底层开发包(例如jedis)进行了深度封装的组件,使用redisTemplate可以优雅的操作redis。我在启动类中写了一个测试方法,向redis写入一条数据

@RequestMapping("/redistest")
    public String test(){
        redisTemplate.opsForSet().add("aaaaa","123456");
        return "OK";
    }

运行这个方法,打开redis客户端可以看到值已经写入了

先抛开这里的键和值让人看不懂的问题,大家是不是觉得springboot整合redis要比普通的springmvc整合redis简单多了?我只配置了redis的连接地址,端口号,注入了redisTemplate,就能开始操作redis了,那么springboot底层到底做了些什么使得整合变得如此的简单了呢。

首先我们来看,springboot启动类上都有一个@SpringbootApplication注解,那么这个注解是起什么作用的呢,让我们点进去看一下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication 

可以看到SpringbootApplication这个注解是由一系列的注解组合而成,这其中最重要的是@EnableAutoConfiguration和@ComponentScan,@ComponentScan的意思就是组件扫描注解,这个注解会自动注入所有在主程序所在包下的组件。比@ComponentScan注解更重要的就是@EnableAutoConfiguration注解了,这个注解的含义就是开启自动装配,直接把bean装配到ioc容器中,@EnableAutoConfiguration也是一个组合注解,我们点进去看一下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

这个地方我们主要看@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)两个注解,首先来看@AutoConfigurationPackage注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

这个注解主要是获取我们注解所在包下的组件去进行注册,大家看到这个@Import注解,那么这个注解是什么含义呢,

@Import注解用来导入@Configuration注解的配置类、声明@Bean注解的bean方法、导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类,这里这个AutoConfigurationPackages.Registrar.class就是ImportBeanDefinitionRegistrar的实现类,来看下源码

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        //metadata是注解的元信息 registry是bean定义的注册器
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //把注解所在的包下所有的组件都进行注册
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}

	}




public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		//首先判断这个bean有没有被注册
		if (registry.containsBeanDefinition(BEAN)) {
			//获取bean定义
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			//通过bean定义获取构造函数值
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			//给构造函数添加参数值
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
			//一个新的bean定义
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			//设置beanClass为beanPackages类型
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			//bean注册
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

接下来就是@Import(AutoConfigurationImportSelector.class)这个注解,我们来看看AutoConfigurationImportSelector这个类,这个类是我们自动装配的导入选择器,首先看这个类的第一个方法,其实也就是这个类的核心方法

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//加载元数据
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		//获得自动装配的实体
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		//获得属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//获得候选的配置类,核心方法
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		//去除重复
		configurations = removeDuplicates(configurations);
		//获得排除的配置
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		//检查排除的配置
		checkExcludedClasses(configurations, exclusions);
		//排除
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

在这部分中,核心方法是getCandidateConfigurations,我们来看下这个方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		//从工厂中获取自动配置类
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		//这句断言很重要,告诉了我们工厂是去哪里找自动配置类的,这里显然META-INF/spring.factories是一个路径
		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;
	}

那我们就找一下这个路径,去哪里找呢,我们看到这个类的包是org.springframework.boot.autoconfigure;那我们就到这个包的位置去找这个spring.factories,果不其然,我们点开这个文件

我们看到文件中有一行注释这Auto configure,表示这些都是自动配置相关的类,这里我们不得不说spring框架真的是强大,这里面居然有100多个自动配置类,我们找到redis有关的自动配置类

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\

这里我们需要的肯定是第一个自动配置类,我们点进去看看

@Configuration
//条件注解,某个class位于类路径上,才会实例化一个Bean,这个类是redis操作的类
@ConditionalOnClass(RedisOperations.class)
//使得@ConfigurationProperties 注解的类生效,这个类是配置redis属性的类
@EnableConfigurationProperties(RedisProperties.class)
//导入一些配置
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	//仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean,这个就是spring默认的redisTemplate
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

我们在application.properties中配置的redis属性,其实就是设置到了这个类中

//前缀spring.redis
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

	/**
	 * Database index used by the connection factory.
	 */
	private int database = 0;

	/**
	 * Connection URL. Overrides host, port, and password. User is ignored. Example:
	 * redis://user:password@example.com:6379
	 */
	private String url;

	/**
	 * Redis server host.
	 */
	private String host = "localhost";

	/**
	 * Login password of the redis server.
	 */
	private String password;

	/**
	 * Redis server port.
	 */
	private int port = 6379;

	/**
	 * Whether to enable SSL support.
	 */
	private boolean ssl;

	/**
	 * Connection timeout.
	 */
	private Duration timeout;

	private Sentinel sentinel;

	private Cluster cluster;

	private final Jedis jedis = new Jedis();

	private final Lettuce lettuce = new Lettuce();
}

我们前面说了,用了spring默认的redisTemplate操作redis的话,存到redis里的数据对我们的阅读不友好,我们看不懂,那是因为redisTemplate中默认用了jdk自带的序列化器

要想让数据变成我们能看得懂的样子,我们需要替换掉redisTempalte默认的序列化器,现在我就来实操一下,写一个配置类

@Configuration
public class RedisConfig {
    //这里的上下文已经有了自定义的redisTemplate,所以默认的redisTemplate不会生效
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)  {
        RedisTemplate<Object,Object> redisTemplate=new RedisTemplate<>();
        //设置自定义序列化器
        redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

然后我改写一下测试方法,一起来看结果

public String test(){
        redisTemplate.opsForSet().add("ffffff","55555555");
        return "OK";
    }

我们看到,序列化器已经生效了,键值对已经是我们能看得懂的了。

总结

通过springboot整合redis的过程,我带大家分析了一下springboot的自动配置原理,基本上市面上流行的组件可以和spring整合的spring官方都有starter,引入starter,配合springboot的自动配置,基本上可以做到只需要几行属性的配置加上类的注入,就可以使用了,spring框架博大精深,还有很多很多东西需要学习,有时间我再给大家分享,望大家多多支持,谢谢。

以上就是springboot自动配置原理解析的详细内容,更多关于springboot自动配置原理的资料请关注脚本之家其它相关文章!

相关文章

  • Java 读取excel 文件流代码实例

    Java 读取excel 文件流代码实例

    这篇文章主要介绍了Java 读取excel 文件流代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • MyBatisPlus如何优化千万级数据的CRUD

    MyBatisPlus如何优化千万级数据的CRUD

    最近负责的一个项目,数据库表量级破千万,每次执行 CRUD 都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊 MyBatis Plus在千万级数据场景下如何优化 CRUD
    2025-06-06
  • Apache Arrow Parquet存储与使用

    Apache Arrow Parquet存储与使用

    这篇文章主要为大家介绍了Apache Arrow Parquet存储与使用原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • java GUI编程之paint绘制操作示例

    java GUI编程之paint绘制操作示例

    这篇文章主要介绍了java GUI编程之paint绘制操作,结合实例形式详细分析了java GUI编程paint绘制相关操作技巧与使用注意事项,需要的朋友可以参考下
    2020-01-01
  • Java常见启动命令-jar、-server和-cp详细比较

    Java常见启动命令-jar、-server和-cp详细比较

    这篇文章主要给大家介绍了关于Java常见启动命令-jar、-server和-cp详细比较的相关资料,该文总结了常归的jar包的启动方式,并分析各种启动方式的区别,需要的朋友可以参考下
    2023-07-07
  • Java中Reactor的反应器模式详解

    Java中Reactor的反应器模式详解

    这篇文章主要介绍了Java中Reactor的反应器模式详解,Reactor反应器模式有点儿类似事件驱动模式,当有事件触发时,事件源会将事件dispatch分发到handler处理器进行事件处理,反应器模式中的反应器角色类似于事件驱动模式中的dispatcher事件分发器角色,需要的朋友可以参考下
    2023-12-12
  • java 对象数组排序

    java 对象数组排序

    当遇到数组排序时,我们经常会使用学过的几种排序方法,而java 本身提供了Arrays.sort,在数据元素较少或者对效率要求不是抬高时,直接使用Arrays.sort来的更容易。查看一下源码后Arrays.sort 本身采用的是快速排序。
    2015-04-04
  • Spring Boot支持Crontab任务改造的方法

    Spring Boot支持Crontab任务改造的方法

    这篇文章主要介绍了Spring Boot支持Crontab任务改造的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • SpringBoot+Druid开启监控页面的实现示例

    SpringBoot+Druid开启监控页面的实现示例

    本文主要介绍了SpringBoot+Druid开启监控页面的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06
  • SpringBoot3.x嵌入MongoDB进行测试的步骤详解

    SpringBoot3.x嵌入MongoDB进行测试的步骤详解

    本文介绍了在 Spring Boot 应用中使用Flapdoodle Embed Mongo进行 MongoDB 测试的方法,包括Embed Mongo 的概念,添加依赖、配置、运行测试的步骤,还列举了其优势如快速启动关闭、环境一致、无需外部依赖等,以及注意事项和结论,需要的朋友可以参考下
    2024-12-12

最新评论