Springboot自動加載配置的原理解析
1、springboot自動配置的原理初探
以下註解都在springboot的自動化配置包中:spring-boot-autoconfigure。讀者朋友可以跟著一下步驟走一遍,應該對自動配置就有一定的認知瞭。
1.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 { //略…… }
2.打開SpringBootApplication註解,上面有個關鍵註解EnableAutoConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { //…… }
3.EnableAutoConfiguration上有個@Import(AutoConfigurationImportSelector.class),註意AutoConfigurationImportSelector,
@Import作用創建一個AutoConfigurationImportSelector的bean對象,並且加入IoC容器
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector //此處隻貼瞭關鍵方法 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方法代碼如上,其調用瞭SpringFactoriesLoader的loadFactoryNames方法,來獲取
configurations,此configurations列表其實就是要被自動花配置的類。SpringFactoriesLoader的兩個重要方法如下:
//org.springframework.core.io.support.SpringFactoriesLoader //隻貼瞭兩個關鍵方法 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源碼查看。此處我故意沒復制源碼,用的截圖,可以看到截圖直接有報錯,編譯錯誤,錯誤的原因是我們還沒添加spring-boot-starter-data-redis的依賴。**這裡有個問題,為什麼明明代碼都報錯,Cannot resolve symbol xxx(未找到類),但是我們的項目依然可以啟動?不信你建立一個簡單的springboot項目,隻添加web依賴,手動打開RedisAutoConfiguration,發現是報紅錯的,但是你啟動項目,發現沒任何問題,why??**這個問題後面再解答,先接著看自動配置的問題。
6.先把RedisAutoConfiguration源碼復制出來方便我寫註釋,上面用截圖主要是讓大傢看到報錯
@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)註解,這個註解的作用是當RedisOperations.class這個類被找到後才會生效,如果沒找到此類,那麼整個RedisAutoConfiguration就不會生效。**所以當我們引入瞭redis的依賴,springboot首先會通過RedisAutoConfiguration的方法redisTemplate給我們設置一個默認的redis配置,當然這個方法上也有個註解@ConditionalOnMissingBean(name = “redisTemplate”),就是當我們沒有手動配redisTemplate這個bean它才會調用這個默認的方法,註入一個redisTemplate到IoC容器,所以一般情況我們都是手動配置這個redisTemplate,方便我們設置序列化器,如下:
@Configuration public class RedisConfig { /** * 設置 redisTemplate 的序列化設置 * * @param redisConnectionFactory * @return */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { // 1.創建 redisTemplate 模版 RedisTemplate<String, Object> template = new RedisTemplate<>(); // 2.關聯 redisConnectionFactory template.setConnectionFactory(redisConnectionFactory); // 3.創建 序列化類 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); // 4.設置可見度 om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 5.啟動默認的類型 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 6.序列化類,對象映射設置 jackson2JsonRedisSerializer.setObjectMapper(om); // 7.設置 value 的轉化格式和 key 的轉化格式 template.setValueSerializer(jackson2JsonRedisSerializer); template.setKeySerializer(new StringRedisSerializer()); template.afterPropertiesSet(); return template; } }
RedisAutoConfiguration上還有一下兩個註解,作用是從配置文件讀取redis相關的信息,ip、端口、密碼等
@EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
2. 補充擴展(解釋為什麼引用的包都報紅錯瞭,項目還能啟動)
所有的@Condition註解(包括衍生的)其實都對應一個具體的實現,這個實現類裡面有個判斷方法叫做matches,返回的是個佈爾類型判斷值。
打開ConditionalOnClass源碼如下,其Conditional註解傳遞的是個OnClassCondition.class,這就其對應的判斷類,也就是說,當我們使用ConditionalOnClass註解時,其實際上調用的是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源碼的同學應該知道,spring創建一個對象的過程是當服務啟動後,先讀取xml配置文件(或者通過註解),根據配置文件先定義一個BeanDefinition,然後把這個bean給放到容器(在spring中實際就是一個Map),然後在根據bean定義,通過反射創建真正的對象。反射會觸發類加載,當condition條件不滿足時,根據如下註釋可知,bean定義後續都被攔截瞭,連註冊都不行,所以自然就不可能通過反射創建對象,不反射自然不會觸發類加載,不觸發類加載那麼RedisAutoConfiguration當然啊不會加載,它不加載,那麼即使它裡面引用瞭一個不存在的類也不會有啥問題。
上面說的很繞,表達的不是很好,要想看懂以上部分需要掌握兩方面的知識:
- 類加載原理,推薦看周志明老師的《深入理解JVM虛擬機》
- spring IoC容器創建bean的原理,推薦《spring揭秘》,詳細看看IoC部分
3、又一個問題
spring-boot-autoconfigure.jar這個包中的RedisAutoConfiguration都報紅色錯誤瞭,那麼spring官方是怎麼打包出來spring-boot-autoconfigure.jar的??怎麼給我們提供瞭一個報錯的包呢
//TODO
總結
到此這篇關於Springboot自動加載配置原理的文章就介紹到這瞭,更多相關Springboot自動加載配置原理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- SpringBoot開發實戰之自動配置
- 使用@Autowired 註入RedisTemplate報錯的問題及解決
- SpringBoot自動配置特點與原理詳細分析
- SpringBoot自動配置深入探究實現原理
- Spring中的spring.factories文件用法(Spring如何加載第三方Bean)