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自动装配源码解析 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。