文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

springboot自动配置原理解析

2024-04-02 19:55

关注

前言

小伙伴们都知道,现在市面上最流行的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 {

	
	private int database = 0;

	
	private String url;

	
	private String host = "localhost";

	
	private String password;

	
	private int port = 6379;

	
	private boolean ssl;

	
	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自动配置原理的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯