Spring Cloud系列之Commons - 1. 背景與基礎知識準備
本系列會深入分析 Spring Cloud 的每一個元件,從 Spring Cloud Commons 這個 Spring Cloud 所有元素的抽象說起,深入設計思路與原始碼,並結合實際使用例子深入理解。本系列適合有一定 Spring 或者 Spring Boot 使用經驗的人閱讀。
什麼是 Spring Cloud Commons
Spring Cloud框架包括如下功能:
· 分散式多版本配置管理
· 服務註冊與發現
· 路由
· 微服務呼叫
· 負載均衡
· 斷路器
· 分散式訊息
Spring Cloud Commons包含實現這一切要載入的基礎元件的介面,以及Spring Cloud啟動如何載入,載入哪些東西。其中:
· spring cloud context:包括Spring Cloud應用需要載入的 ApplicationContext 的內容
· spring cloud common: 包括如下幾個基本元件以及其載入配置:
o 服務註冊介面: org.springframework.cloud.serviceregistry 包
o 服務發現介面: org.springframework.cloud.discovery 包
o 負載均衡介面: org.springframework.cloud.loadbalancer 包
o 斷路器介面: org.springframework.cloud.circuitbreaker 包
· spring cloud loadbalancer:類似於ribbon,並且是ribbon的替代品。實現了上述負載均衡介面的元件
這個系列我們要講述的是 spring cloud common 這個模組, spring cloud loadbalancer 還有 spring cloud context 將會在另一個單獨的系列。
Spring 與 Spring Boot 背景知識補充
我們在看一個 Spring Cloud 模組原始碼時,需要記住任何一個 Spring Cloud 模組都是基於 Spring Boot 擴充套件而來的,這個擴充套件一般是透過 spring.factories SPI 機制。 任何一個 Spring Cloud 模組原始碼都可以以這個為切入點進行理解
spring.factories SPI 機制
spring-core 專案中提供了 Spring 框架多種 SPI 機制,其中一種非常常用並靈活運用在了 Spring-boot 的機制就是基於 spring.factories 的 SPI 機制。
那麼什麼是 SPI(Service Provider)呢? 在系統設計中,為了模組間的協作,往往會設計統一的介面供模組之間的呼叫。面向的物件的設計裡,我們一般推薦模組之間基於介面程式設計, 模組之間不對實現類進行硬編碼 ,而是將指定哪個實現置於程式之外指定。Java 中預設的 SPI 機制就是透過 ServiceLoader 來實現,簡單來說就是透過在 META-INF/services 目錄下新建一個名稱為介面全限定名的檔案,內容為介面實現類的全限定名,之後程式透過程式碼:
// 指定載入的介面類,以及用來載入類的類載入器,如果類載入器為 null 則用根類載入器載入
ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class, someClassLoader);
Iterator<SpiService> iterator = serviceLoader.iterator(); while (iterator.hasNext()){
SpiService spiService = iterator.next();
}
獲取指定的實現類。
在 Spring 框架中,這個類是 SpringFactoriesLoader ,需要在 META-INF/spring.factories 檔案中指定介面以及對應的實現類,例如 Spring Cloud Commons 中的:
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor
其中指定了 EnvironmentPostProcessor 的實現 HostInfoEnvironmentPostProcessor 。
同時,Spring Boot 中會透過 SpringFactoriesLoader.loadXXX 類似的方法讀取所有的 EnvironmentPostProcessor 的實現類並生成 Bean 到 ApplicationContext 中:
//這個類也是透過spring.factories中指定ApplicationListener的實現而實現載入的,這裡省略 public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener , Ordered {
//建立這個Bean的時候,會呼叫
public EnvironmentPostProcessorApplicationListener () {
this (EnvironmentPostProcessorsFactory
.fromSpringFactories(EnvironmentPostProcessorApplicationListener.class.getClassLoader()));
}
}
static EnvironmentPostProcessorsFactory fromSpringFactories (ClassLoader classLoader) {
return new ReflectionEnvironmentPostProcessorsFactory(
//透過 SpringFactoriesLoader.loadFactoryNames 獲取檔案中指定的實現類並初始化
SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
}
spring.factories 的特殊使用 - EnableAutoConfiguration
META-INF/spring.factories 檔案中不一定指定的是介面以及對應的實現類,例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\
其中 EnableAutoConfiguration 是一個註解, LoadBalancerAutoConfiguration 與 BlockingLoadBalancerClientAutoConfiguration 都是配置類並不是 EnableAutoConfiguration 的實現。那麼這個是什麼意思呢? EnableAutoConfiguration 是一個註解, LoadBalancerAutoConfiguration 與 BlockingLoadBalancerClientAutoConfiguration 都是配置類。 spring.factories 這裡是另一種特殊使用,記錄要載入的 Bean 類。 EnableAutoConfiguration 在註解被使用的時候,這些 Bean 會被載入。這就是 spring.factories 的另外一種用法。
EnableAutoConfiguration 是 Spring-boot 自動裝載的核心註解。有了這個註解,Spring-boot 就可以自動載入各種 @Configuration 註解的類。那麼這個機制是如何實現的呢?
來看下
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 {};
//排除的Bean名稱
String[] excludeName() default {};
}
我們看到了有 @Import 這個註解。這個註解是 Spring 框架的一個很常用的註解,是 Spring 基於 Java 註解配置的主要組成部分。
@Import 註解的作用
@Import 註解提供了 @Bean 註解的功能,同時還有原來 Spring 基於 xml 配置檔案裡的 <import> 標籤組織多個分散的xml檔案的功能,當然在這裡是組織多個分散的 @Configuration 的類。這個註解的功能與用法包括
1. 引入其他的 @Configuration
假設有如下介面和兩個實現類:
package com.test interface ServiceInterface {
void test () ;
}
class ServiceA implements ServiceInterface {
@Override
public void test () {
System.out.println( "ServiceA" );
}
}
class ServiceB implements ServiceInterface {
@Override
public void test () {
System.out.println( "ServiceB" );
}
}
兩個 @Configuration ,其中 ConfigA``@Import``ConfigB :
package com.test @Import (ConfigB.class) @Configuration class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA () {
return new ServiceA();
}
}
@Configuration class ConfigB {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceB () {
return new ServiceB();
}
}
透過 ConfigA 建立 AnnotationConfigApplicationContext ,獲取 ServiceInterface ,看是哪種實現:
public static void main (String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
ServiceInterface bean = ctx.getBean(ServiceInterface.class);
bean.test();
}
輸出為: ServiceB .證明 @Import 的優先於本身的的類定義載入。
2. 直接初始化其他類的 Bean
在
Spring 4.2
之後,
@Import
可以直接指定實體類,載入這個類定義到
context
中。
例如把上面程式碼中的
ConfigA
的
@Import
修改為
@Import(ServiceB.class)
,就會生成
ServiceB
的
Bean
到容器上下文中,之後執行
main
方法,輸出為:
ServiceB
.證明
@Import
的優先於本身的的類定義載入.
3. 指定實現 ImportSelector (以及 DefferredServiceImportSelector )的類,用於個性化載入
指定實現 ImportSelector 的類,透過 AnnotationMetadata 裡面的屬性,動態載入類。 AnnotationMetadata 是 Import 註解所在的類屬性(如果所在類是註解類,則延伸至應用這個註解類的非註解類為止)。
需要實現 selectImports 方法,返回要載入的 @Configuation 或者具體 Bean 類的全限定名的 String 陣列。
package com.test; class ServiceImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//可以是@Configuration註解修飾的類,也可以是具體的Bean類的全限定名稱
return new String[]{ "com.test.ConfigB" };
}
}
@Import (ServiceImportSelector.class) @Configuration class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA () {
return new ServiceA();
}
}
再次執行
main
方法,輸出:
ServiceB
.證明
@Import
的優先於本身的的類定義載入。
一般的,框架中如果基於
AnnotationMetadata
的引數實現動態載入類,一般會寫一個額外的
Enable
註解,配合使用。例如:
package com.test;
@Retention (RetentionPolicy.RUNTIME) @Documented@Target (ElementType.TYPE) @Import (ServiceImportSelector.class) @interface EnableService {
String name () ;
}
class ServiceImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//這裡的importingClassMetadata針對的是使用@EnableService的非註解類
//因為`AnnotationMetadata`是`Import`註解所在的類屬性,如果所在類是註解類,則延伸至應用這個註解類的非註解類為止
Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true );
String name = (String) map.get( "name" );
if (Objects.equals(name, "B" )) {
return new String[]{ "com.test.ConfigB" };
}
return new String[ 0 ];
}
}
之後,在 ConfigA 中增加註解 @EnableService(name = "B")
package com.test; @EnableService (name = "B" ) @Configuration class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA () {
return new ServiceA();
}
}
再次執行 main 方法,輸出: ServiceB .
還可以實現
DeferredImportSelector
介面,這樣
selectImports
返回的類就都是最後載入的,而不是像
@Import
註解那樣,先載入。
例如:
package com.test; class DefferredServiceImportSelector implements DeferredImportSelector {
@Override
public String [] selectImports(AnnotationMetadata importingClassMetadata) {
Map < String , Object > map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true );
String name = ( String ) map.get( "name" );
if (Objects.equals(name, "B" )) {
return new String []{ "com.test.ConfigB" };
}
return new String [ 0 ];
}
}
修改 EnableService 註解:
@Retention (RetentionPolicy.RUNTIME) @Documented@Target (ElementType.TYPE) @Import (DefferredServiceImportSelector.class) @interface EnableService {
String name () ;
}
這樣 ConfigA 就優先於 DefferredServiceImportSelector 返回的 ConfigB 載入,執行 main 方法,輸出: ServiceA
4. 指定實現 ImportBeanDefinitionRegistrar 的類,用於個性化載入
與 ImportSelector 用法與用途類似,但是如果我們想重定義 Bean ,例如動態注入屬性,改變 Bean 的型別和 Scope 等等,就需要透過指定實現 ImportBeanDefinitionRegistrar 的類實現。例如:
定義 ServiceC
package com.test; class ServiceC implements ServiceInterface {
private final String name;
ServiceC(String name) {
this .name = name;
}
@Override
public void test () {
System.out.println(name);
}
}
定義 ServiceImportBeanDefinitionRegistrar 動態註冊 ServiceC ,修改 EnableService
package com.test;
@Retention (RetentionPolicy.RUNTIME) @Documented@Target (ElementType.TYPE) @Import (ServiceImportBeanDefinitionRegistrar.class) @interface EnableService {
String name () ;
}
class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true );
String name = (String) map.get( "name" );
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
//增加構造引數
.addConstructorArgValue(name);
//註冊Bean
registry.registerBeanDefinition( "serviceC" , beanDefinitionBuilder.getBeanDefinition());
}
}
ImportBeanDefinitionRegistrar 在 @Bean 註解之後載入,所以要修改 ConfigA 去掉其中被 @ConditionalOnMissingBean 註解的 Bean ,否則一定會生成 ConfigA 的 ServiceInterface
package com.test; @EnableService (name = "TestServiceC" ) @Configuration class ConfigA { // @Bean// @ConditionalOnMissingBean// public ServiceInterface getServiceA() {// return new ServiceA();// }
}
之後執行 main ,輸出: TestServiceC
Spring Boot 核心自動裝載的實現原理
上面我們提到了 @EnableAutoConfiguration 註解裡面的:
@ Import ( AutoConfigurationImportSelector . class )
屬於
@Import
註解的第三種用法,也就是透過具體的
ImportSelector
進行裝載,實現其中的
selectImports
介面返回需要自動裝載的類的全限定名稱。這裡的
AutoConfigurationImportSelector
實現是:
@ Override public String[] selectImports (AnnotationMetadata annotationMetadata) {
//`spring.boot.enableautoconfiguration`這個屬性沒有指定為false那就是啟用了Spring Boot自動裝載,否則就是沒啟用。沒啟用的話,返回空陣列
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//獲取要載入的類,詳情見下面原始碼
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//獲取要載入的類 protected AutoConfigurationEntry getAutoConfigurationEntry (AnnotationMetadata annotationMetadata) {
//`spring.boot.enableautoconfiguration`這個屬性沒有指定為false那就是啟用了Spring Boot自動裝載,否則就是沒啟用。沒啟用的話,返回空陣列
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//獲取註解
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//從spring.factories讀取所有key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的類
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = removeDuplicates(configurations);
//根據EnableAutoConfiguration註解的屬性去掉要排除的類
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
//釋出AutoConfigurationImportEvent事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
Spring Boot中的 ApplicationContext 的層級是什麼
ApplicationContext 是 spring 用來容納管理 beans 以及其生命週期的容器。ApplicationContext 的分層規定了bean的界限以及可以複用的 bean。關於 ApplicationContext 層級可以參考 ,這裡我們透過一個簡單的例子來說明下 ApplicationContext 層級以及其中的bean界限,例如某些 bean 可以被多個 ApplicationContext 共享,同時某些 bean 只在某個 ApplicationContext 生效,不同 ApplicationContext 可以宣告同名或者同型別的bean這樣。我們將實現一個下圖所示的 ApplicationContext 結構:
我們會實現,一個 parent context 與三個對應 child context 的結構。
首先定義Parent context:
Bean類:
package com.test.spring.context.bean;
import lombok.Data; import lombok.NoArgsConstructor;
@Data@NoArgsConstructor public class RootBean {
private Stirng name;
}
Context類:
import com.hopegaming.scaffold.spring.context.bean.RootBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration@PropertySource (value = "classpath:/root.yaml" , factory = YamlPropertyLoaderFactory.class) public class RootContext {
@Bean
public RootBean getFatherBean () {
RootBean rootBean = new RootBean();
rootBean.setName( "root" );
return rootBean;
}
}
root.yml:
# 配置這些主要是將actuator相關介面暴露出來。
management:
endpoint:
health:
show-details: always
endpoints:
jmx:
exposure:
exclude: '*'
web:
exposure:
include : '*'
由於我們使用了yml,這裡需要我們自定義一個 YamlPropertyLoaderFactory 用於載入yml配置:
package com.test.spring.context.config;
import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.DefaultPropertySourceFactory; import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null ){
return super .createPropertySource(name, resource);
}
return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).get( 0 );
}
}
定義child context的公共Bean類:
package com.test.spring.context.bean;
import lombok.Data; import lombok.NoArgsConstructor;
@Data@NoArgsConstructor public class ChildBean {
private RootBean fatherBean;
private String name;
}
定義ChildContext1:
package com.test.spring.context.config.child1;
import com.hopegaming.scaffold.spring.context.bean.ChildBean; import com.hopegaming.scaffold.spring.context.bean.RootBean; import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource;
@SpringBootApplication (scanBasePackages = { "com.test.spring.context.controller" }) @PropertySource (value = "classpath:/bean-config-1.yaml" , factory = YamlPropertyLoaderFactory.class) public class ChildContext1 {
@Bean
public ChildBean getChildBean (@Value( "${spring.application.name}" ) String name, RootBean fatherBean) {
ChildBean childBean = new ChildBean();
childBean.setFatherBean(fatherBean);
childBean.setName(name);
return childBean;
}
}
bean-config-1.yaml :
server:
port: 8080 spring:
application:
name: child1
接下來分別是ChildContext2,ChildContext3的:
package com.test.spring.context.config.child2;
import com.hopegaming.scaffold.spring.context.bean.ChildBean; import com.hopegaming.scaffold.spring.context.bean.RootBean; import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource;
@SpringBootApplication (scanBasePackages = { "com.test.spring.context.controller" }) @PropertySource (value = "classpath:/bean-config-2.yaml" , factory = YamlPropertyLoaderFactory.class) public class ChildContext2 {
@Bean
public ChildBean getChildBean (@Value( "${spring.application.name}" ) String name, RootBean fatherBean) {
ChildBean childBean = new ChildBean();
childBean.setFatherBean(fatherBean);
childBean.setName(name);
return childBean;
}
}
server:
port: 8081
spring:
application:
name: child2
management:
endpoint:
health:
show -details: always
endpoints:
jmx:
exposure:
exclude : '*'
web:
exposure:
include : '*'
package com.test.spring.context.config.child3;
import com.hopegaming.scaffold.spring.context.bean.ChildBean; import com.hopegaming.scaffold.spring.context.bean.RootBean; import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource;
@SpringBootApplication (scanBasePackages = { "com.test.spring.context.controller" }) @PropertySource (value = "classpath:/bean-config-3.yaml" , factory = YamlPropertyLoaderFactory.class) public class ChildContext3 {
@Bean
public ChildBean getChildBean (@Value( "${spring.application.name}" ) String name, RootBean fatherBean) {
ChildBean childBean = new ChildBean();
childBean.setFatherBean(fatherBean);
childBean.setName(name);
return childBean;
}
}
server:
port: 8082
spring:
application:
name: child3
management:
endpoint:
health:
show -details: always
endpoints:
jmx:
exposure:
exclude : '*'
web:
exposure:
include : '*'
測試介面 TestController :
package com .test.spring.context.controller ;
import org .springframework.beans.factory.annotation.Autowired ; import org .springframework.beans.factory.annotation.Value ; import org .springframework.web.bind.annotation.RequestMapping ; import org .springframework.web.bind.annotation.RestController ;
import java .util.Locale ;
@ RestController
public class TestController {
@ Autowired
private ChildBean childBean;
@ RequestMapping ("/ test ")
public ChildBean getChildBean() {
return childBean ;
}
}
啟動類:
package com.test.spring.context;
import com.hopegaming.scaffold.spring.context.config.child1.ChildContext1; import com.hopegaming.scaffold.spring.context.config.child2.ChildContext2; import com.hopegaming.scaffold.spring.context.config.child3.ChildContext3; import com.hopegaming.scaffold.spring.context.config.root.RootContext; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext;
public class ContextMain {
public static void main (String[] args) {
SpringApplicationBuilder appBuilder =
new SpringApplicationBuilder()
.sources(RootContext.class)
//第一個子context用child,剩下的都用sibling
.child(ChildContext1.class)
.sibling(ChildContext2.class)
.sibling(ChildContext3.class);
ConfigurableApplicationContext applicationContext = appBuilder.run();
}
}
啟動後,訪問 返回:
{ "fatherBean" :{ "name" : "root" }, "name" : "child1" }
訪問 返回:
{ "fatherBean" :{ "name" : "root" }, "name" : "child2" }
訪問 返回:
{ "fatherBean" :{ "name" : "root" }, "name" : "child3" }
訪問 會有類似於下面的返回(省略了不關心的bean):
{
"contexts" : {
"application-1" : {
"beans" : {
"getChildBean" : {
"aliases" : [],
"scope" : "singleton" ,
"type" : "com.hopegaming.scaffold.spring.context.bean.ChildBean" ,
"resource" : "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2" ,
"dependencies" : [
"getFatherBean"
]
},
"childContext2" : {
"aliases" : [],
"scope" : "singleton" ,
"type" : "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2$$EnhancerBySpringCGLIB$$26f80b15" ,
"resource" : null ,
"dependencies" : []
}
.......
},
"parentId" : "application"
},
"application" : {
"beans" : {
"getFatherBean" : {
"aliases" : [],
"scope" : "singleton" ,
"type" : "com.hopegaming.scaffold.spring.context.bean.RootBean" ,
"resource" : "com.hopegaming.scaffold.spring.context.config.root.RootContext" ,
"dependencies" : []
},
"rootContext" : {
"aliases" : [],
"scope" : "singleton" ,
"type" : "com.hopegaming.scaffold.spring.context.config.root.RootContext$$EnhancerBySpringCGLIB$$18d9c26f" ,
"resource" : null ,
"dependencies" : []
}
.......
},
"parentId" : null
}
}
}
透過這個例子,想必大家對於 ApplicationContext 層級有了一定的理解
Bean 載入條件
我們會經常看到 @Conditional 相關的註解,例如 @ConditionalOnBean 還有 @ConditionalOnClass 等等,這些註解提供了自動裝載時候根據某些條件載入不同類的靈活性。 @Conditional 註解是 spring-context 提供的特性,Spring Boot 在這個註解的基礎上,提供了更多具體的條件配置註解,包括:
· @ConditionalOnBean ,如果當前 ApplicationContext 的 BeanFactory 已經包含這些 Bean,則滿足條件。與之相反的是 @ConditionalOnMissingBean ,如果當前 ApplicationContext 的 BeanFactory 不包含這些 Bean,則滿足條件。
· @ConditionalOnClass ,如果當前 classpath 中有這些類,則滿足條件。與之相反的是 @ConditionalOnMissingClass ,如果當前 classpath 中沒有這些類,則滿足條件
· @ConditionalOnProperty ,指定屬性是否存在,並且值滿足 havingValue 指定的值(沒設定就是不為 false 就行), matchIfMissing 代表如果屬性不存在代表條件滿足還是不滿足。
以上幾個註解是比較常用的,剩下的例如 ConditionalOnCloudPlatform 這些不太常用,這裡先不提了。
如果有多個類似的 @Conditional 註解作用於同一個方法或者類, 這些載入條件是“And”的關係 。
Configuration 載入順序
由於 Bean 載入條件的複雜性,有時候我們想某些 Configuration 類先載入,某些在特定的 Configuration 載入完之後再載入。例如:
@Configuration public class FirstConfiguration {
@Bean
@ConditionalOnMissingBean
public Service service1 () {
......
}
}
@Configuration public class SecondConfiguration {
@Bean
@ConditionalOnMissingBean
public Service service1 () {
......
}
}
假設這兩個類在不同 jar 包,我們沒有辦法確定最後建立的是哪一個類的 Service ,這時候我們就需要用到一些決定 Configuration 載入順序的註解。注意這裡的 Configuration 載入順序僅僅是 Bean 定義載入順序,主要是為了限制上面提到的 Bean 載入條件的判斷順序,而不是建立 Bean 的順序。Bean 建立的順序主要由 Bean 依賴決定以及 @DependsOn 註解限制。
相關的註解如下:
· @AutoConfigureAfter 指定當前 Configuration 在 某個 Configuration 之後載入。
· @AutoConfigureBefore 指定當前 Configuration 在 某個 Configuration 之前載入。
· @AutoConfigureOrder 類似於 @Order 註解,指定當前 Configuration 的載入序號,預設是 0 ,越小越先載入。
Bean 排序
對於同一型別的 Bean( 實現了同一介面的 Bean),我們可以用一個 List 進行自動裝載,例如:
public interface Service {
void test () ;
} @Componenet public class ServiceA implements Service {
@Override
public void test () {
System.out.println( "ServiceA" );
}
} @Componenet public class ServiceB implements Service {
@Override
public void test () {
System.out.println( "ServiceB" );
}
}
@Componenet public class Test {
@Autowired
private List<Service> services;
}
private List<Service> services 中就會有 serviceA 和 serviceB 這兩個 Bean,但是誰在前誰在後呢?可以透過 @Order 註解指定。
@ Retention ( RetentionPolicy . RUNTIME )
@Target({ ElementType .TYPE , ElementType .METHOD , ElementType .FIELD })
@ Documented
public @interface Order {
/**
* The order value.
* <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
* @see Ordered#getOrder()
*/
int value () default Ordered .LOWEST_PRECEDENCE ;
}
值越小,越靠前。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990087/viewspace-2755278/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Cloud:Consul基礎知識SpringCloud
- Spring Cloud 升級之路 - 2020.0.x - 1. 背景知識、需求描述與公共依賴SpringCloud
- 1.基礎知識
- 1. 初始認識 Spring CloudSpringCloud
- 小豬的Python學習之旅 —— 1.基礎知識儲備Python
- 音視訊學習(一)-- 基礎知識準備
- 抱歉,Xposed真的可以為所欲為——1.基礎知識儲備
- WebSocket系列之基礎知識入門篇Web
- 【譯】給小白準備的Web架構基礎知識Web架構
- OpenStack必備基礎知識
- GO 學習筆記 《1. 基礎知識》Go筆記
- 【Git 系列】基礎知識全集Git
- linux基礎知識整理(備忘)Linux
- 【Xtrabackup】Xtrabackup備份基礎知識
- Spring Cloud基礎SpringCloud
- Python基礎知識之字典Python
- Python基礎知識之集合Python
- MySQL指南之基礎知識MySql
- day04 必備基礎知識
- IO基礎知識與概念
- Spring Cloud基礎教程SpringCloud
- spring cloud Alibaba 之 spring boot 基礎學習筆記CloudSpring Boot筆記
- Java基礎知識系列—序列化Java
- 計算機網路基礎知識(面試準備)計算機網路面試
- IdentityServer4系列 | 初識基礎知識點IDEServer
- SAP SD 基礎知識之物料列表與物料排除
- Flink系列(0)——準備篇(流處理基礎)
- 程式猿必備的Linux基礎知識Linux
- 效能測試必備基礎知識(二)
- Python 基礎(一):入門必備知識Python
- Python 必備面試基礎知識-3Python面試
- Python 面試必備基礎知識-1Python面試
- 程式必備區塊鏈基礎知識區塊鏈
- Java基礎知識整理之this用法Java
- 介面測試之基礎知識
- Java基礎知識之概述(一)Java
- C#基礎知識回顧:1.由WeakReference想到物件的建立與銷燬C#物件
- MyBatis 框架系列之基礎初識MyBatis框架