日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringBoot自动装配源码解析

發布時間:2023/12/4 javascript 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringBoot自动装配源码解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Spring Boot 自動裝配原理

  • 使用Spring Boot最方便的一點體驗在于我們可以幾零配置的搭建一個Spring Web項目,那么他是怎么做到不通過配置來對Bean完成注入的呢。這就要歸功于Spring Boot的自動裝配實現,他也是Spring Boot中各個Starter的實現基礎,Spring Boot的核心。
  • 自動裝配,就是Spring Boot會自動的尋找Bean并且裝配到IOC容器中,如下,我們通過一個Spring Boot項目說明,案例如下:
  • 添加pom.xml文件依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.ljm</groupId><artifactId>spring-cloud-alibaba-learn</artifactId><version>1.0-SNAPSHOT</version><properties><java.version>8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--Spring Boot autoConfig start--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.2.2.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.2.2.RELEASE</version></dependency></dependencies><build><plugins><plugin><!-- 指定 JDK 版本 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><skip>true</skip><source>${java.version}</source><target>${java.version}</target><compilerVersion>${java.version}</compilerVersion><encoding>${project.build.sourceEncoding}</encoding></configuration></plugin></plugins></build> </project>
  • application.properties
spring.redis.host=localhost spring.redis.port=6379
  • HelloController.java
package com.springcloud.mystart;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;/*** @author liaojiamin* @Date:Created in 10:50 2022/3/7*/ @RestController public class HelloController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@GetMapping("helloRedis")public String helloRedis(){redisTemplate.opsForValue().set("helloRedis", "RedisTemplateAutoConfig");return "helloRedis";} }
  • SpringBoot啟動類 Application.java
package com.springcloud;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author liaojiamin* @Date:Created in 11:08 2022/3/7*/ @SpringBootApplication public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);} }
  • Redis操作界面結果

  • 如上案例中,我們并沒有通過XML文件的形式注入Redis Template到IOC容器中,但是HelloController中卻可以直接用@Autowired注入Redis Template實例,說明,IOC容器中已經存在了Redis Template,這個是Spring Boot自動裝配實現的自動加載機制。
  • 在針對Redis的配置以及jar來說,我們只添加了一個Start依賴,就完成了依賴組件相關的Bean自動注入,

自實現自動裝配標簽

  • 自動裝配在Spring Boot中通過@EnableAutoConfiguration 注解來開啟的,這個注解我們沒有在項目中顯示的聲明,他是包含在@SpringBootApplication注解中
  • 如下我們可以看到@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 {......}
  • 在理解EnableAutoConfiguration之前,我們能想到之前在用SpringMvc的時候用到過一個@Enable注解,他的主要作用就是吧相關的組件Bean裝配到IOC容器中。@Enable注解對JavaConfig的進一步的優化,目的是為了減少配置,其實Spring從3.X開始就一直在做優化,減少配置,降低上手的難度,比如常見的有@EnableWebMvc,@EnableScheduling,如下代碼
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({DelegatingWebMvcConfiguration.class}) public @interface EnableWebMvc { }
  • 如果我們通過JavaConfig的方式來注入Bean的時候,離不來@Configuration和@Bean,@Enable就是對這兩個注解的封裝,比如在上面的@EnableWebMvc注解代碼中我們能看到@Import,Spring會解析@Import倒入的配置類,通過對這個配置類的實現來找到需要裝配的Bean。
EnableAutoConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";/*** Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/Class<?>[] exclude() default {};/*** Exclude specific auto-configuration class names such that they will never be* applied.* @return the class names to exclude* @since 1.3.0*/String[] excludeName() default {};}
  • 以上是@EnableAutoConfiguration的注解,在除了@Import之外還有一個@AutoConfigurationPackage

注解,作用在于用了該注解的類所在的包以及子包下所有的組件掃描到Spring IOC容器中

  • @Import注解倒入類一個Configuration結尾的配置類,和上面不同,AutoConfigurationImportSelector類就是本注解的特殊地方
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {......}
  • AutoConfigurationImportSelect 實現了DeferredImportSelector,他是ImportSelector的子類,其中有一個selectImports方法返回類一個String數組,這個數組中就是我們需要制定裝配到IOC容器中的所有類
  • 也就是說他在ImportSelector 的實現類之后,將實現類中返回的class名稱都裝配進IOC容器里
  • 與@Configuration不同的是,ImportSelector是批量的,并且還可以通過邏輯處理來選擇對于的Bean,那么我們用一個Demo來驗證
  • 創建兩個類:
/*** @author liaojiamin* @Date:Created in 11:49 2022/3/7*/ public class FilterFirstObj { } /*** @author liaojiamin* @Date:Created in 11:49 2022/3/7*/ public class FilterSecondObj { }
  • 創建一個ImportSelect的實現類,直接返回我們新建的類的名字,如下:
/*** @author liaojiamin* @Date:Created in 11:49 2022/3/7*/ public class GpImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {return new String[]{FilterFirstObj.class.getName(), FilterSecondObj.class.getName()};} }
  • 定義我們自己的注解,這個可以直接抄@EnableAutoConfiguration
/*** @author liaojiamin* @Date:Created in 11:52 2022/3/7*/ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({GpImportSelector.class}) public @interface EnableAutoImport { }
  • 在之前Demo的啟動類中從IOC容器中獲取我們定義的類:
/*** @author liaojiamin* @Date:Created in 11:08 2022/3/7*/ @SpringBootApplication @EnableAutoImport public class Application {public static void main(String[] args) {ConfigurableApplicationContext ca = SpringApplication.run(Application.class, args);System.out.println(ca.getBean(FilterFirstObj.class));} }

  • 以上的實現方式相比@Import(*Configuration.class)來說,好處在于靈活性更高,還可以實現批量的注入,我們還有在以上的Demo中GpImportSelector添加N個,由于一個Configuration表示某一個技術組件中的一批Bean,所以自動裝配的過程只需要掃描置頂路徑對于的配置類即可。

自動裝配源碼分析

  • 基于以上Demo的實現,我們找到AutoConfigurationImportSelector的實現,找到其中的selectImports方法,他是ImportSelector接口的實現,如下:
@Overridepublic 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());}
  • 如上源碼中有兩個功能:

    • 從META-INF/spring-autoconfigure-metadata.properties中加載自動裝配的條件元數據
    • 手機所有符合條件的配置類,autoConfigurationEntry.getConfigurations(),完成自動裝配
  • 在AutoConfigurationImportSelector 類中并不直接執行對呀selectImport方法,其中有一個process方法,這個方法最中還是會調用getAutoConfigurationEntry方法獲取自動裝配的所有配置類,我們可以看如下源碼

  • process方法

@Overridepublic void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() -> String.format("Only %s implementations are supported, got %s",AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);}}
  • getAutoConfigurationEntry方法
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);}
  • 通過Debug代碼可以看每個步驟得到的返回值,簡單分析代碼的作用

    • getAttributes 獲取@EnableAutoCOnfiguration注解中的屬性exclude, excludeName
    • getCandidateConfigurations獲得所有自動裝配的配置類。
    • removeDuplicates 用LinkedHashSet實現去重
    • getExclusions更具@EnableAutoConfiguration注解中配置的exclude等屬性吧不需要自動裝配的配置類移除
    • fireAutoConfigurationImportEvents廣播事件
    • 最后返回經過過濾之后的配置類集合
  • 以上步驟中,核心在于獲取自動裝配的配置類getCandidateConfigurations,其他只是在最篩選等其他步驟,我們看該方法實現

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;}
  • 如上代碼用到了SpringFactoriesLoader,他是Spring內部提供的一種類加載方式,類似java的SPI,他會掃描classpath目錄下的META-INF/spring.factories文件,spring.factories文件中的數據以 key=value 的形式存儲,也就是getCandidateConfigurations 方法的返回值

  • 如下圖是spring-boot-autoconfiguration對應的jar包中文件,我們可以在jar中找到對應的spring.factories

  • 內容如下

...... org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ ......
  • 里面包含了多種Spring支持的組件的加載,我們這以Redis為案例,通過Debug,我們查看getCandidateConfigurations所掃描到的所有類,如下圖所示,其中就包括我們上圖中找到的Redis的支持:

  • 我們打開RedisAutoConfiguration可以看到,他是一個基于JavaConfig形式的配置類,如下:
@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@ConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}}
  • 除了基本的Configuration 和Bean兩個注解,還有一個COnditionalOnClass,這個條件控制機制在這里用途是判斷classpath下是否存在RedisOperations這個類,我們找一下這個類屬于那個jar中,如下圖

  • 如上圖,所示,他在spring-data-redis中,而我們只引入了一個有關redis的jar就是那個redis-start,那么結論顯而易見了,在Spring Boot自動裝配的時候,他能掃描到所有支持的組件,但是他實際加載到IOC中的會依據每個組件的condition進行第一次篩選,只有找到對應的資源文件他才會去加載
  • @EnableConfigurationProperties注解也是我們需要關注的,他說有關屬性配置的,也就是我們按照約定在application.properties中配置的Redis的參數,如下Redis.properties
@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;/*** Client name to be set on connections with CLIENT SETNAME.*/private String clientName;private Sentinel sentinel;private Cluster cluster;......}
  • 我們的properties中的配置樣式的由來就是由此得出的
spring.redis.host=127.0.0.1 spring.redis.port=6379
  • 由此自動裝配的基本原理就完結了,總結過程如下:
    • 通過@Import(AutoConfigurationImportSelector)實現配置類的導入,但是這里并不是傳統意義上的單個配置類裝配
    • AutoConfigurationImportSelector實現了ImportSelector接口,重寫了selectImports,他用來實現選擇性批量配置類的裝配
    • 通過Spring提供的SpringFactoriesLoader機制,掃描classpath路徑下的META-INF/spring.factories讀取自動裝配的配置類
    • 通過篩選條件,吧不符合的配置類移除,最中完成自動裝配

下一篇:SpringBoot中Bean按條件裝配

總結

以上是生活随笔為你收集整理的SpringBoot自动装配源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。