springboot自動(dòng)配置原理解析
前言
小伙伴們都知道,現(xiàn)在市面上最流行的web開(kāi)發(fā)框架就是springboot了,在springboot開(kāi)始流行之前,我們都用的是strust2或者是springmvc框架來(lái)開(kāi)發(fā)web應(yīng)用,但是這兩個(gè)框架都有一個(gè)特點(diǎn)就是配置非常的繁瑣,要寫(xiě)一大堆的配置文件,spring在支持了注解開(kāi)發(fā)之后稍微有些改觀但有的時(shí)候還是會(huì)覺(jué)得比較麻煩,這個(gè)時(shí)候springboot就體現(xiàn)出了它的優(yōu)勢(shì),springboot只需要一個(gè)properties或者yml文件就可以簡(jiǎn)化springmvc中在xml中需要配置的一大堆的bean,這就是因?yàn)閟pringboot有自動(dòng)配置,那么springboot自動(dòng)配置的原理是什么呢,今天我們就來(lái)通過(guò)源碼分析一下springboot的自動(dòng)配置原理
開(kāi)始
我以springboot整合redis為例,來(lái)向大家分析springboot的自動(dòng)配置原理
首先創(chuàng)建一個(gè)springboot工程用來(lái)測(cè)試,然后在pom文件中引入springboot-starter-redis的啟動(dòng)器依賴
<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
然后,在啟動(dòng)類中注入redisTemplate類,redisTemplate為spring官方提供的對(duì)redis底層開(kāi)發(fā)包(例如jedis)進(jìn)行了深度封裝的組件,使用redisTemplate可以優(yōu)雅的操作redis。我在啟動(dòng)類中寫(xiě)了一個(gè)測(cè)試方法,向redis寫(xiě)入一條數(shù)據(jù)
@RequestMapping("/redistest") public String test(){ redisTemplate.opsForSet().add("aaaaa","123456"); return "OK"; }
運(yùn)行這個(gè)方法,打開(kāi)redis客戶端可以看到值已經(jīng)寫(xiě)入了
先拋開(kāi)這里的鍵和值讓人看不懂的問(wèn)題,大家是不是覺(jué)得springboot整合redis要比普通的springmvc整合redis簡(jiǎn)單多了?我只配置了redis的連接地址,端口號(hào),注入了redisTemplate,就能開(kāi)始操作redis了,那么springboot底層到底做了些什么使得整合變得如此的簡(jiǎn)單了呢。
首先我們來(lái)看,springboot啟動(dòng)類上都有一個(gè)@SpringbootApplication注解,那么這個(gè)注解是起什么作用的呢,讓我們點(diǎn)進(jìn)去看一下
@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這個(gè)注解是由一系列的注解組合而成,這其中最重要的是@EnableAutoConfiguration和@ComponentScan,@ComponentScan的意思就是組件掃描注解,這個(gè)注解會(huì)自動(dòng)注入所有在主程序所在包下的組件。比@ComponentScan注解更重要的就是@EnableAutoConfiguration注解了,這個(gè)注解的含義就是開(kāi)啟自動(dòng)裝配,直接把bean裝配到ioc容器中,@EnableAutoConfiguration也是一個(gè)組合注解,我們點(diǎn)進(jìn)去看一下
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration
這個(gè)地方我們主要看@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)兩個(gè)注解,首先來(lái)看@AutoConfigurationPackage注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
這個(gè)注解主要是獲取我們注解所在包下的組件去進(jìn)行注冊(cè),大家看到這個(gè)@Import注解,那么這個(gè)注解是什么含義呢,
@Import注解用來(lái)導(dǎo)入@Configuration注解的配置類、聲明@Bean注解的bean方法、導(dǎo)入ImportSelector的實(shí)現(xiàn)類或?qū)隝mportBeanDefinitionRegistrar的實(shí)現(xiàn)類,這里這個(gè)AutoConfigurationPackages.Registrar.class就是ImportBeanDefinitionRegistrar的實(shí)現(xiàn)類,來(lái)看下源碼
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { //metadata是注解的元信息 registry是bean定義的注冊(cè)器 @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //把注解所在的包下所有的組件都進(jìn)行注冊(cè) 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) { //首先判斷這個(gè)bean有沒(méi)有被注冊(cè) if (registry.containsBeanDefinition(BEAN)) { //獲取bean定義 BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); //通過(guò)bean定義獲取構(gòu)造函數(shù)值 ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues(); //給構(gòu)造函數(shù)添加參數(shù)值 constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { //一個(gè)新的bean定義 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); //設(shè)置beanClass為beanPackages類型 beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); //bean注冊(cè) registry.registerBeanDefinition(BEAN, beanDefinition); } }
接下來(lái)就是@Import(AutoConfigurationImportSelector.class)這個(gè)注解,我們來(lái)看看AutoConfigurationImportSelector這個(gè)類,這個(gè)類是我們自動(dòng)裝配的導(dǎo)入選擇器,首先看這個(gè)類的第一個(gè)方法,其實(shí)也就是這個(gè)類的核心方法
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //加載元數(shù)據(jù) AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); //獲得自動(dòng)裝配的實(shí)體 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); //去除重復(fù) 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,我們來(lái)看下這個(gè)方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //從工廠中獲取自動(dòng)配置類 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); //這句斷言很重要,告訴了我們工廠是去哪里找自動(dòng)配置類的,這里顯然META-INF/spring.factories是一個(gè)路徑 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; }
那我們就找一下這個(gè)路徑,去哪里找呢,我們看到這個(gè)類的包是org.springframework.boot.autoconfigure;那我們就到這個(gè)包的位置去找這個(gè)spring.factories,果不其然,我們點(diǎn)開(kāi)這個(gè)文件
我們看到文件中有一行注釋這Auto configure,表示這些都是自動(dòng)配置相關(guān)的類,這里我們不得不說(shuō)spring框架真的是強(qiáng)大,這里面居然有100多個(gè)自動(dòng)配置類,我們找到redis有關(guān)的自動(dòng)配置類
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
這里我們需要的肯定是第一個(gè)自動(dòng)配置類,我們點(diǎn)進(jìn)去看看
@Configuration //條件注解,某個(gè)class位于類路徑上,才會(huì)實(shí)例化一個(gè)Bean,這個(gè)類是redis操作的類 @ConditionalOnClass(RedisOperations.class) //使得@ConfigurationProperties 注解的類生效,這個(gè)類是配置redis屬性的類 @EnableConfigurationProperties(RedisProperties.class) //導(dǎo)入一些配置 @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean //僅僅在當(dāng)前上下文中不存在某個(gè)對(duì)象時(shí),才會(huì)實(shí)例化一個(gè)Bean,這個(gè)就是spring默認(rèn)的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; } }
我們?cè)赼pplication.properties中配置的redis屬性,其實(shí)就是設(shè)置到了這個(gè)類中
//前綴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(); }
我們前面說(shuō)了,用了spring默認(rèn)的redisTemplate操作redis的話,存到redis里的數(shù)據(jù)對(duì)我們的閱讀不友好,我們看不懂,那是因?yàn)閞edisTemplate中默認(rèn)用了jdk自帶的序列化器
要想讓數(shù)據(jù)變成我們能看得懂的樣子,我們需要替換掉redisTempalte默認(rèn)的序列化器,現(xiàn)在我就來(lái)實(shí)操一下,寫(xiě)一個(gè)配置類
@Configuration public class RedisConfig { //這里的上下文已經(jīng)有了自定義的redisTemplate,所以默認(rèn)的redisTemplate不會(huì)生效 @Bean public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object,Object> redisTemplate=new RedisTemplate<>(); //設(shè)置自定義序列化器 redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } }
然后我改寫(xiě)一下測(cè)試方法,一起來(lái)看結(jié)果
public String test(){ redisTemplate.opsForSet().add("ffffff","55555555"); return "OK"; }
我們看到,序列化器已經(jīng)生效了,鍵值對(duì)已經(jīng)是我們能看得懂的了。
總結(jié)
通過(guò)springboot整合redis的過(guò)程,我?guī)Т蠹曳治隽艘幌聅pringboot的自動(dòng)配置原理,基本上市面上流行的組件可以和spring整合的spring官方都有starter,引入starter,配合springboot的自動(dòng)配置,基本上可以做到只需要幾行屬性的配置加上類的注入,就可以使用了,spring框架博大精深,還有很多很多東西需要學(xué)習(xí),有時(shí)間我再給大家分享,望大家多多支持,謝謝。
以上就是springboot自動(dòng)配置原理解析的詳細(xì)內(nèi)容,更多關(guān)于springboot自動(dòng)配置原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java SpringBoot自動(dòng)配置原理詳情
- Springboot自動(dòng)配置與@Configuration配置類詳解
- SpringBoot自動(dòng)配置實(shí)現(xiàn)的詳細(xì)步驟
- SpringBoot自動(dòng)配置原理詳解
- SpringBoot自動(dòng)配置Quartz的實(shí)現(xiàn)步驟
- springboot自動(dòng)配置原理以及spring.factories文件的作用詳解
- 淺談springboot自動(dòng)配置原理
- SpringBoot自動(dòng)配置的實(shí)現(xiàn)原理
- Springboot的自動(dòng)配置是什么及注意事項(xiàng)
相關(guān)文章
java設(shè)計(jì)模式之代理模式(Porxy)詳解
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式之代理模式Porxy的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Java使用JavaMail API發(fā)送和接收郵件的代碼示例
JavaMail是Oracle甲骨文開(kāi)發(fā)的Java郵件類API,支持多種郵件協(xié)議,這里我們就來(lái)看一下Java使用JavaMail API發(fā)送和接收郵件的代碼示例2016-06-06java利用java.net.URLConnection發(fā)送HTTP請(qǐng)求的方法詳解
如何通過(guò)Java(模擬瀏覽器)發(fā)送HTTP請(qǐng)求是我們?cè)谌粘=?jīng)常會(huì)遇到的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于java利用java.net.URLConnection發(fā)送HTTP請(qǐng)求的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-05-05Spring?Boot?Admin?監(jiān)控指標(biāo)接入Grafana可視化的實(shí)例詳解
Spring Boot Admin2 自帶有部分監(jiān)控圖表,如圖,有線程、內(nèi)存Heap和內(nèi)存Non Heap,這篇文章主要介紹了Spring?Boot?Admin?監(jiān)控指標(biāo)接入Grafana可視化,需要的朋友可以參考下2022-11-11關(guān)于Cannot?resolve?com.microsoft.sqlserver:sqljdbc4:4.0報(bào)錯(cuò)問(wèn)題解
這篇文章主要給大家介紹了關(guān)于Cannot?resolve?com.microsoft.sqlserver:sqljdbc4:4.0報(bào)錯(cuò)問(wèn)題的解決辦法,這個(gè)是在pom文件中添加依賴出現(xiàn)報(bào)錯(cuò)問(wèn)題,需要的朋友可以參考下2024-02-02RabbitMQ中的Connection和Channel信道詳解
這篇文章主要介紹了RabbitMQ中的Connection和Channel信道詳解,信道是建立在 Connection 之上的虛擬連接,RabbitMQ 處理的每條 AMQP 指令都是通過(guò)信道完成的,需要的朋友可以參考下2023-08-08java獲取兩個(gè)數(shù)組中不同數(shù)據(jù)的方法
這篇文章主要介紹了java獲取兩個(gè)數(shù)組中不同數(shù)據(jù)的方法,實(shí)例分析了java操作數(shù)組的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-031秒鐘實(shí)現(xiàn)Springboot?替換/寫(xiě)入?word文檔里面的文字、圖片功能
這篇文章主要介紹了Springboot?替換/寫(xiě)入?word文檔里面的文字、圖片,1秒鐘實(shí)現(xiàn),本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12Java線程池中的Future實(shí)現(xiàn)詳解
這篇文章主要介紹了Java線程池中的Future實(shí)現(xiàn)詳解, FutureTask是一個(gè)任務(wù),FutureTask繼承了Runnable、Callable, 通過(guò)FutureTask可以獲取到任務(wù)執(zhí)行的狀態(tài),任務(wù)執(zhí)行完成完成后,將結(jié)構(gòu)通過(guò)Future接口返回,調(diào)用者可以調(diào)用Future#get()方法獲取到數(shù)據(jù),需要的朋友可以參考下2023-10-10