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>
 
 
spring.redis.host=localhost
spring.redis.port=6379
 
 
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
; 
@RestController 
public  class  HelloController  { @Autowired private  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
; 
@SpringBootApplication 
public  class  Application  { public  static  void  main ( String
[ ]  args
)  { SpringApplication
. run ( Application
. class ,  args
) ; } 
} 
 
 
 
如上案例中,我們并沒有通過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" ; Class
< ? > [ ]  exclude ( )  default  { } ; 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來驗證 創建兩個類: public  class  FilterFirstObj  { 
} 
public  class  FilterSecondObj  { 
}  
創建一個ImportSelect的實現類,直接返回我們新建的類的名字,如下: public  class  GpImportSelector  implements  ImportSelector  { @Override public  String
[ ]  selectImports ( AnnotationMetadata annotationMetadata
)  { return  new  String [ ] { FilterFirstObj
. class . getName ( ) ,  FilterSecondObj
. class . getName ( ) } ; } 
} 
 
定義我們自己的注解,這個可以直接抄@EnableAutoConfiguration @Target ( { ElementType
. TYPE
} ) 
@Retention ( RetentionPolicy
. RUNTIME
) 
@Documented 
@Inherited 
@AutoConfigurationPackage 
@Import ( { GpImportSelector
. class } ) 
public  @
interface  EnableAutoImport  { 
}  
在之前Demo的啟動類中從IOC容器中獲取我們定義的類: @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接口的實現,如下: @Override public  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 ( ) ) ; } 
 
 
@Override public  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
) ; } 
 
 
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 @ConditionalOnMissingBean public  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  { private  int  database 
=  0 ; private  String url
; private  String host 
=  "localhost" ; private  String password
; private  int  port 
=  6379 ; private  boolean  ssl
; private  Duration timeout
; 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自动装配源码解析 的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                            
                                如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。