本文基於 Spring Cloud 2020.0 發布版的依賴
本系列會深入分析 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: 包括如下幾個基本組件以及其加載配置:
- 服務注冊接口:
org.springframework.cloud.serviceregistry
包 - 服務發現接口:
org.springframework.cloud.discovery
包 - 負載均衡接口:
org.springframework.cloud.loadbalancer
包 - 斷路器接口:
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 中:
EnvironmentPostProcessorApplicationListener
//這個類也是通過spring.factories中指定ApplicationListener的實現而實現加載的,這里省略
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
//創建這個Bean的時候,會調用
public EnvironmentPostProcessorApplicationListener() {
this(EnvironmentPostProcessorsFactory
.fromSpringFactories(EnvironmentPostProcessorApplicationListener.class.getClassLoader()));
}
}
EnvironmentPostProcessorsFactory
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
的源碼
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
實現是:
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();
}
}
啟動后,訪問http://127.0.0.1:8080/test
返回:
{"fatherBean":{"name":"root"},"name":"child1"}
訪問http://127.0.0.1:8081/test
返回:
{"fatherBean":{"name":"root"},"name":"child2"}
訪問http://127.0.0.1:8082/test
返回:
{"fatherBean":{"name":"root"},"name":"child3"}
訪問http://127.0.0.1:8080/actuator/beans
會有類似於下面的返回(省略了不關心的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;
}
值越小,越靠前。