Springboot自動(dòng)加載配置的原理解析
1、springboot自動(dòng)配置的原理初探
以下注解都在springboot的自動(dòng)化配置包中:spring-boot-autoconfigure。讀者朋友可以跟著一下步驟走一遍,應(yīng)該對(duì)自動(dòng)配置就有一定的認(rèn)知了。
1.springboot程序的入口是在啟動(dòng)類,該類有個(gè)關(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注解,上面有個(gè)關(guān)鍵注解EnableAutoConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { //…… }
3.EnableAutoConfiguration上有個(gè)@Import(AutoConfigurationImportSelector.class),注意AutoConfigurationImportSelector,
@Import作用創(chuàng)建一個(gè)AutoConfigurationImportSelector的bean對(duì)象,并且加入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列表其實(shí)就是要被自動(dòng)花配置的類。SpringFactoriesLoader的兩個(gè)重要方法如下:
//org.springframework.core.io.support.SpringFactoriesLoader //只貼了兩個(gè)關(guān)鍵方法 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; //此方法返回的是即將要被自動(dòng)化配置的類的全限定類名,是從META-INF/spring.factories配置的,配置文件中有個(gè)org.springframework.boot.autoconfigure.EnableAutoConfiguration 其后面可配置多個(gè)想被自動(dòng)花配置的類 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.舉例分析,我們?cè)趕pring.factories中可以看到org.springframework.boot.autoconfigure.EnableAutoConfiguration后有一個(gè)org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,說明springboot希望redis能夠自動(dòng)化配置。接著我們打開RedisAutoConfiguration源碼查看。此處我故意沒復(fù)制源碼,用的截圖,可以看到截圖直接有報(bào)錯(cuò),編譯錯(cuò)誤,錯(cuò)誤的原因是我們還沒添加spring-boot-starter-data-redis的依賴。**這里有個(gè)問題,為什么明明代碼都報(bào)錯(cuò),Cannot resolve symbol xxx(未找到類),但是我們的項(xiàng)目依然可以啟動(dòng)?不信你建立一個(gè)簡(jiǎn)單的springboot項(xiàng)目,只添加web依賴,手動(dòng)打開RedisAutoConfiguration,發(fā)現(xiàn)是報(bào)紅錯(cuò)的,但是你啟動(dòng)項(xiàng)目,發(fā)現(xiàn)沒任何問題,why??**這個(gè)問題后面再解答,先接著看自動(dòng)配置的問題。
6.先把RedisAutoConfiguration源碼復(fù)制出來方便我寫注釋,上面用截圖主要是讓大家看到報(bào)錯(cuò)
@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上有一個(gè)Configuration和ConditionalOnClass注解,先分析這兩個(gè)。首先Configuration注解,代表這是個(gè)Java config配置類,和spring配置bean的xml文件是一個(gè)作用,都是用來實(shí)例化bean的,**但是注意還有個(gè)@ConditionalOnClass(RedisOperations.class)注解,這個(gè)注解的作用是當(dāng)RedisOperations.class這個(gè)類被找到后才會(huì)生效,如果沒找到此類,那么整個(gè)RedisAutoConfiguration就不會(huì)生效。**所以當(dāng)我們引入了redis的依賴,springboot首先會(huì)通過RedisAutoConfiguration的方法redisTemplate給我們?cè)O(shè)置一個(gè)默認(rèn)的redis配置,當(dāng)然這個(gè)方法上也有個(gè)注解@ConditionalOnMissingBean(name = "redisTemplate"),就是當(dāng)我們沒有手動(dòng)配redisTemplate這個(gè)bean它才會(huì)調(diào)用這個(gè)默認(rèn)的方法,注入一個(gè)redisTemplate到IoC容器,所以一般情況我們都是手動(dòng)配置這個(gè)redisTemplate,方便我們?cè)O(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.啟動(dòng)默認(rèn)的類型 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 6.序列化類,對(duì)象映射設(shè)置 jackson2JsonRedisSerializer.setObjectMapper(om); // 7.設(shè)置 value 的轉(zhuǎn)化格式和 key 的轉(zhuǎn)化格式 template.setValueSerializer(jackson2JsonRedisSerializer); template.setKeySerializer(new StringRedisSerializer()); template.afterPropertiesSet(); return template; } }
RedisAutoConfiguration上還有一下兩個(gè)注解,作用是從配置文件讀取redis相關(guān)的信息,ip、端口、密碼等
@EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
2. 補(bǔ)充擴(kuò)展(解釋為什么引用的包都報(bào)紅錯(cuò)了,項(xiàng)目還能啟動(dòng))
所有的@Condition注解(包括衍生的)其實(shí)都對(duì)應(yīng)一個(gè)具體的實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)類里面有個(gè)判斷方法叫做matches,返回的是個(gè)布爾類型判斷值。
打開ConditionalOnClass源碼如下,其Conditional注解傳遞的是個(gè)OnClassCondition.class,這就其對(duì)應(yīng)的判斷類,也就是說,當(dāng)我們使用ConditionalOnClass注解時(shí),其實(shí)際上調(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定義即將注冊(cè)到容器之前進(jìn)行的,**看過springIoC源碼的同學(xué)應(yīng)該知道,spring創(chuàng)建一個(gè)對(duì)象的過程是當(dāng)服務(wù)啟動(dòng)后,先讀取xml配置文件(或者通過注解),根據(jù)配置文件先定義一個(gè)BeanDefinition,然后把這個(gè)bean給放到容器(在spring中實(shí)際就是一個(gè)Map),然后在根據(jù)bean定義,通過反射創(chuàng)建真正的對(duì)象。反射會(huì)觸發(fā)類加載,當(dāng)condition條件不滿足時(shí),根據(jù)如下注釋可知,bean定義后續(xù)都被攔截了,連注冊(cè)都不行,所以自然就不可能通過反射創(chuàng)建對(duì)象,不反射自然不會(huì)觸發(fā)類加載,不觸發(fā)類加載那么RedisAutoConfiguration當(dāng)然啊不會(huì)加載,它不加載,那么即使它里面引用了一個(gè)不存在的類也不會(huì)有啥問題。
上面說的很繞,表達(dá)的不是很好,要想看懂以上部分需要掌握兩方面的知識(shí):
- 類加載原理,推薦看周志明老師的《深入理解JVM虛擬機(jī)》
- spring IoC容器創(chuàng)建bean的原理,推薦《spring揭秘》,詳細(xì)看看IoC部分
3、又一個(gè)問題
spring-boot-autoconfigure.jar這個(gè)包中的RedisAutoConfiguration都報(bào)紅色錯(cuò)誤了,那么spring官方是怎么打包出來spring-boot-autoconfigure.jar的??怎么給我們提供了一個(gè)報(bào)錯(cuò)的包呢
//TODO
總結(jié)
到此這篇關(guān)于Springboot自動(dòng)加載配置原理的文章就介紹到這了,更多相關(guān)Springboot自動(dòng)加載配置原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Java中攔截mybatis并輸出完整sql語句的方法
這篇文章主要介紹了關(guān)于Java中攔截mybatis并輸出完整sql語句的方法,假如項(xiàng)目中有很多很多的SQL我們不可能一一的去修改解決。這個(gè)時(shí)候我們就需要通過mybatis攔截SQL并且最終修改SQL,需要的朋友可以參考下2023-08-08Mybatis之映射實(shí)體類中不區(qū)分大小寫的解決
這篇文章主要介紹了Mybatis之映射實(shí)體類中不區(qū)分大小寫的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot工程打包與運(yùn)行的實(shí)現(xiàn)詳解
本文主要介紹了SpringBoot工程的打包與運(yùn)行的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Java圖片與二進(jìn)制相互轉(zhuǎn)換實(shí)現(xiàn)示例講解
這篇文章主要介紹了Java圖片與二進(jìn)制相互轉(zhuǎn)換實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-03-03詳解Mybatis攔截器安全加解密MySQL數(shù)據(jù)實(shí)戰(zhàn)
本文主要介紹了Mybatis攔截器安全加解密MySQL數(shù)據(jù)實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01詳解SpringBoot構(gòu)建的Web項(xiàng)目如何在服務(wù)端校驗(yàn)表單輸入
這篇文章主要介紹了詳解SpringBoot構(gòu)建的Web項(xiàng)目如何在服務(wù)端校驗(yàn)表單輸入,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10java創(chuàng)建excel示例(jxl使用方法)
Java Excel是一開放源碼項(xiàng)目,通過它Java開發(fā)人員可以讀取Excel文件的內(nèi)容、創(chuàng)建新的Excel文件、更新 已經(jīng)存在的Excel文件。下面是使用方法,包括去掉網(wǎng)格線、字體設(shè)置、單元格設(shè)置、對(duì)齊方式等設(shè)置2014-03-03Kafka?日志存儲(chǔ)實(shí)現(xiàn)過程
這篇文章主要為大家介紹了Kafka?日志存儲(chǔ)的實(shí)現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05