Springboot自動加載配置的原理解析
1、springboot自動配置的原理初探
以下注解都在springboot的自動化配置包中:spring-boot-autoconfigure。讀者朋友可以跟著一下步驟走一遍,應(yīng)該對自動配置就有一定的認知了。
1.springboot程序的入口是在啟動類,該類有個關(guān)鍵注解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 {
//略……
}
2.打開SpringBootApplication注解,上面有個關(guān)鍵注解EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//……
}
3.EnableAutoConfiguration上有個@Import(AutoConfigurationImportSelector.class),注意AutoConfigurationImportSelector,
@Import作用創(chuàng)建一個AutoConfigurationImportSelector的bean對象,并且加入IoC容器
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
//此處只貼了關(guān)鍵方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = 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;
}
4.AutoConfigurationImportSelector類中的getCandidateConfigurations方法代碼如上,其調(diào)用了SpringFactoriesLoader的loadFactoryNames方法,來獲取
configurations,此configurations列表其實就是要被自動花配置的類。SpringFactoriesLoader的兩個重要方法如下:
//org.springframework.core.io.support.SpringFactoriesLoader
//只貼了兩個關(guān)鍵方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//此方法返回的是即將要被自動化配置的類的全限定類名,是從META-INF/spring.factories配置的,配置文件中有個org.springframework.boot.autoconfigure.EnableAutoConfiguration 其后面可配置多個想被自動花配置的類
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullab等le ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));//META-INF/spring.factories
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
5.舉例分析,我們在spring.factories中可以看到org.springframework.boot.autoconfigure.EnableAutoConfiguration后有一個org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,說明springboot希望redis能夠自動化配置。接著我們打開RedisAutoConfiguration源碼查看。此處我故意沒復(fù)制源碼,用的截圖,可以看到截圖直接有報錯,編譯錯誤,錯誤的原因是我們還沒添加spring-boot-starter-data-redis的依賴。**這里有個問題,為什么明明代碼都報錯,Cannot resolve symbol xxx(未找到類),但是我們的項目依然可以啟動?不信你建立一個簡單的springboot項目,只添加web依賴,手動打開RedisAutoConfiguration,發(fā)現(xiàn)是報紅錯的,但是你啟動項目,發(fā)現(xiàn)沒任何問題,why??**這個問題后面再解答,先接著看自動配置的問題。

6.先把RedisAutoConfiguration源碼復(fù)制出來方便我寫注釋,上面用截圖主要是讓大家看到報錯
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@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;
}
}
看源碼可知RedisAutoConfiguration上有一個Configuration和ConditionalOnClass注解,先分析這兩個。首先Configuration注解,代表這是個Java config配置類,和spring配置bean的xml文件是一個作用,都是用來實例化bean的,**但是注意還有個@ConditionalOnClass(RedisOperations.class)注解,這個注解的作用是當(dāng)RedisOperations.class這個類被找到后才會生效,如果沒找到此類,那么整個RedisAutoConfiguration就不會生效。**所以當(dāng)我們引入了redis的依賴,springboot首先會通過RedisAutoConfiguration的方法redisTemplate給我們設(shè)置一個默認的redis配置,當(dāng)然這個方法上也有個注解@ConditionalOnMissingBean(name = "redisTemplate"),就是當(dāng)我們沒有手動配redisTemplate這個bean它才會調(diào)用這個默認的方法,注入一個redisTemplate到IoC容器,所以一般情況我們都是手動配置這個redisTemplate,方便我們設(shè)置序列化器,如下:
@Configuration
public class RedisConfig {
/**
* 設(shè)置 redisTemplate 的序列化設(shè)置
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 1.創(chuàng)建 redisTemplate 模版
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 2.關(guān)聯(lián) redisConnectionFactory
template.setConnectionFactory(redisConnectionFactory);
// 3.創(chuàng)建 序列化類
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 4.設(shè)置可見度
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 5.啟動默認的類型
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 6.序列化類,對象映射設(shè)置
jackson2JsonRedisSerializer.setObjectMapper(om);
// 7.設(shè)置 value 的轉(zhuǎn)化格式和 key 的轉(zhuǎn)化格式
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
RedisAutoConfiguration上還有一下兩個注解,作用是從配置文件讀取redis相關(guān)的信息,ip、端口、密碼等
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
2. 補充擴展(解釋為什么引用的包都報紅錯了,項目還能啟動)
所有的@Condition注解(包括衍生的)其實都對應(yīng)一個具體的實現(xiàn),這個實現(xiàn)類里面有個判斷方法叫做matches,返回的是個布爾類型判斷值。
打開ConditionalOnClass源碼如下,其Conditional注解傳遞的是個OnClassCondition.class,這就其對應(yīng)的判斷類,也就是說,當(dāng)我們使用ConditionalOnClass注解時,其實際上調(diào)用的是OnClassCondition來判斷的
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* The classes that must be present. Since this annotation is parsed by loading class
* bytecode, it is safe to specify classes here that may ultimately not be on the
* classpath, only if this annotation is directly on the affected component and
* <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
* use this annotation as a meta-annotation, only use the {@link #name} attribute.
* @return the classes that must be present
*/
Class<?>[] value() default {};
/**
* The classes names that must be present.
* @return the class names that must be present.
*/
String[] name() default {};
}
ConditionalOnClass類圖如下,它繼承了condition接口

打開Condition接口如下,查看注釋,注釋中有說明 **條件判斷是在bean定義即將注冊到容器之前進行的,**看過springIoC源碼的同學(xué)應(yīng)該知道,spring創(chuàng)建一個對象的過程是當(dāng)服務(wù)啟動后,先讀取xml配置文件(或者通過注解),根據(jù)配置文件先定義一個BeanDefinition,然后把這個bean給放到容器(在spring中實際就是一個Map),然后在根據(jù)bean定義,通過反射創(chuàng)建真正的對象。反射會觸發(fā)類加載,當(dāng)condition條件不滿足時,根據(jù)如下注釋可知,bean定義后續(xù)都被攔截了,連注冊都不行,所以自然就不可能通過反射創(chuàng)建對象,不反射自然不會觸發(fā)類加載,不觸發(fā)類加載那么RedisAutoConfiguration當(dāng)然啊不會加載,它不加載,那么即使它里面引用了一個不存在的類也不會有啥問題。

上面說的很繞,表達的不是很好,要想看懂以上部分需要掌握兩方面的知識:
- 類加載原理,推薦看周志明老師的《深入理解JVM虛擬機》
- spring IoC容器創(chuàng)建bean的原理,推薦《spring揭秘》,詳細看看IoC部分
3、又一個問題
spring-boot-autoconfigure.jar這個包中的RedisAutoConfiguration都報紅色錯誤了,那么spring官方是怎么打包出來spring-boot-autoconfigure.jar的??怎么給我們提供了一個報錯的包呢
//TODO
總結(jié)
到此這篇關(guān)于Springboot自動加載配置原理的文章就介紹到這了,更多相關(guān)Springboot自動加載配置原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot 實現(xiàn)mqtt物聯(lián)網(wǎng)的示例代碼
這篇文章主要介紹了springboot 實現(xiàn)mqtt物聯(lián)網(wǎng),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
Java通過動態(tài)規(guī)劃設(shè)計股票買賣最佳時機
動態(tài)規(guī)劃可謂是大名鼎鼎,筆試面試中的高頻考點,也是重點難點,動態(tài)規(guī)劃類型題目靈活多變,難度系數(shù)也相對較高,往往我們做不好動態(tài)規(guī)劃的題目就會與心儀的offer失之交臂,本篇文章我們就一起來研究一下動態(tài)規(guī)劃設(shè)計股票買賣最佳時機2022-10-10
Java?多線程并發(fā)?ReentrantReadWriteLock詳情
這篇文章主要介紹了Java多線程并發(fā)ReentrantReadWriteLock詳情,ReentrantReadWriteLock可重入讀寫鎖。實際使用場景中,我們需要處理的操作本質(zhì)上是讀與寫,更多相關(guān)資料,感興趣的小伙伴可以參考一下下面文章內(nèi)容2022-06-06
FeignClient設(shè)置動態(tài)url方式
文章介紹了如何在Spring Cloud環(huán)境下使用FeignClient實現(xiàn)負載均衡,通過配置Nacos和FeignClient屬性,可以實現(xiàn)服務(wù)間的負載均衡調(diào)用2024-11-11

