面試阿里p7,被按在地上摩擦,鬼知道我經歷了什麼?
面試阿里p7被問到的問題(當時我只知道第一個):
@Conditional是做什麼的?
@Conditional多個條件是什麼邏輯關係?
條件判斷在什麼時候執行?
ConfigurationCondition和Condition有什麼區別?什麼時候使用ConfigurationCondition?
多個Condition執行的順序是什麼樣的?可以配置優先順序麼?
可以介紹一下@Conditional常見的一些用法麼?
@Conditional註解
@Conditional註解是從spring4.0才有的,可以用在任何型別或者方法上面,通過@Conditional註解可以配置一些條件判斷,當所有條件都滿足的時候,被@Conditional標註的目標才會被spring容器處理。
比如可以通過@Conditional來控制bean是否需要註冊,控制被@Configuration標註的配置類是需要需要被解析等。
效果就像這段程式碼,相當於在spring容器解析目標前面加了一個條件判斷:
if(@Conditional中配置的多個條件是否都匹配){
//spring繼續處理被@Conditional註解標註的物件
}
@Conditional原始碼:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
這個註解只有一個value引數,Condition型別的陣列,Condition是一個介面,表示一個條件判斷,內部有個方法返回true或false,當所有Condition都成立的時候,@Conditional的結果才成立。
下面我們來看一下Condition介面。
Condition介面
用來表示條件判斷的介面,原始碼如下:
@FunctionalInterface
public interface Condition {
/**
* 判斷條件是否匹配
* context:條件判斷上下文
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
是一個函式式介面,內部只有一個matches方法,用來判斷條件是否成立的,2個引數:
context:條件上下文,ConditionContext介面型別的,可以用來獲取容器中的個人資訊
metadata:用來獲取被@Conditional標註的物件上的所有註解資訊
ConditionContext介面
這個介面中提供了一些常用的方法,可以用來獲取spring容器中的各種資訊,看一下原始碼:
public interface ConditionContext {
/**
* 返回bean定義註冊器,可以通過註冊器獲取bean定義的各種配置資訊
*/
BeanDefinitionRegistry getRegistry();
/**
* 返回ConfigurableListableBeanFactory型別的bean工廠,相當於一個ioc容器物件
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* 返回當前spring容器的環境配置資訊物件
*/
Environment getEnvironment();
/**
* 返回資源載入器
*/
ResourceLoader getResourceLoader();
/**
* 返回類載入器
*/
@Nullable
ClassLoader getClassLoader();
}
比較關鍵性的問題:條件判斷在什麼時候執行?
Spring對配置類的處理主要分為2個階段:
配置類解析階段
會得到一批配置類的資訊,和一些需要註冊的bean
bean註冊階段
將配置類解析階段得到的配置類和需要註冊的bean註冊到spring容器中
看一下什麼是配置類
類中有下面任意註解之一的就屬於配置類:
類上有@Compontent註解
類上有@Configuration註解
類上有@CompontentScan註解
類上有@Import註解
類上有@ImportResource註解
類中有@Bean標註的方法
判斷一個類是不是一個配置類,是否的是下面這個方法,有興趣的可以看一下:
org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate
spring中處理這2個過程會迴圈進行,直到完成所有配置類的解析及所有bean的註冊。
Spring對配置類處理過程
原始碼位置:
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
整個過程大致的過程如下:
通常我們會通過new AnnotationConfigApplicationContext()傳入多個配置類來啟動spring容器
spring對傳入的多個配置類進行解析
配置類解析階段:這個過程就是處理配置類上面6中註解的過程,此過程中又會發現很多新的配置類,比如@Import匯入的一批新的類剛好也符合配置類,而被@CompontentScan掃描到的一些類剛好也是配置類;此時會對這些新產生的配置類進行同樣的過程解析
bean註冊階段:配置類解析後,會得到一批配置類和一批需要註冊的bean,此時spring容器會將這批配置類作為bean註冊到spring容器,同樣也會將這批需要註冊的bean註冊到spring容器
經過上面第3個階段之後,spring容器中會註冊很多新的bean,這些新的bean中可能又有很多新的配置類
Spring從容器中將所有bean拿出來,遍歷一下,會過濾得到一批未處理的新的配置類,繼續交給第3步進行處理
step3到step6,這個過程會經歷很多次,直到完成所有配置類的解析和bean的註冊
從上面過程中可以瞭解到:
可以在配置類上面加上@Conditional註解,來控制是否需要解析這個配置類,配置類如果不被解析,那麼這個配置上面6種註解的解析都會被跳過
可以在被註冊的bean上面加上@Conditional註解,來控制這個bean是否需要註冊到spring容器中
如果配置類不會被註冊到容器,那麼這個配置類解析所產生的所有新的配置類及所產生的所有新的bean都不會被註冊到容器
一個配置類被spring處理有2個階段:配置類解析階段、bean註冊階段(將配置類作為bean被註冊到spring容器)。
如果將Condition介面的實現類作為配置類上@Conditional中,那麼這個條件會對兩個階段都有效,此時通過Condition是無法精細的控制某個階段的,如果想控制某個階段,比如可以讓他解析,但是不能讓他註冊,此時就就需要用到另外一個介面了:ConfigurationCondition
ConfigurationCondition介面
看一下這個介面的原始碼:
public interface ConfigurationCondition extends Condition {
/**
* 條件判斷的階段,是在解析配置類的時候過濾還是在建立bean的時候過濾
*/
ConfigurationPhase getConfigurationPhase();
/**
* 表示階段的列舉:2個值
*/
enum ConfigurationPhase {
/**
* 配置類解析階段,如果條件為false,配置類將不會被解析
*/
PARSE_CONFIGURATION,
/**
* bean註冊階段,如果為false,bean將不會被註冊
*/
REGISTER_BEAN
}
}
ConfigurationCondition介面相對於Condition介面多了一個getConfigurationPhase方法,用來指定條件判斷的階段,是在解析配置類的時候過濾還是在建立bean的時候過濾。
@Conditional使用的3步驟
自定義一個類,實現Condition或ConfigurationCondition介面,實現matches方法
在目標物件上使用@Conditional註解,並指定value的指為自定義的Condition型別
啟動spring容器載入資源,此時@Conditional就會起作用了
案例1:阻止配置類的處理
在配置類上面使用@Conditional,這個註解的value指定的Condition當有一個為false的時候,spring就會跳過處理這個配置類。
自定義一個Condition類:
package com.javacode2018.lesson001.demo25.test3;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MyCondition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
matches方法內部我們可以隨意發揮,此處為了演示效果就直接返回false。
來個配置類,在配置類上面使用上面這個條件,此時會讓配置類失效,如下:
package com.javacode2018.lesson001.demo25.test3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Conditional(MyCondition1.class) //@1
@Configuration
public class MainConfig3 {
@Bean
public String name() { //@1
return "路人甲Java";
}
}
@1:使用了自定義的條件類
@2:通過@Bean標註這name這個方法,如果這個配置類成功解析,會將name方法的返回值作為bean註冊到spring容器
來個測試類,啟動spring容器載入MainConfig3配置類,如下:
package com.javacode2018.lesson001.demo25;
import com.javacode2018.lesson001.demo25.test3.MainConfig3;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Map;
public class ConditionTest {
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
Map<String, String> serviceMap = context.getBeansOfType(String.class);
serviceMap.forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
}
test3中,從容器中獲取String型別的bean,執行test3沒有任何輸出。
我們可以將MainConfig3上面的@Conditional去掉,再次執行輸出:
name->路人甲Java
案例2:阻止bean的註冊
來個配置類,如下:
package com.javacode2018.lesson001.demo25.test4;
import com.javacode2018.lesson001.demo25.test3.MyCondition1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig4 {
@Conditional(MyCondition1.class) //@1
@Bean
public String name() {
return "路人甲Java";
}
@Bean
public String address() {
return "上海市";
}
}
上面2個方法上面使用了@Bean註解來定義了2個bean,name方法上面使用了@Conditional註解,這個條件會在name這個bean註冊到容器之前會進行判斷,當條件為true的時候,name這個bean才會被註冊到容器。
ConditionTest中新增個測試用例來載入上面這個配置類,從容器中獲取String型別所有bean輸出,程式碼如下:
@Test
public void test4() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
Map<String, String> serviceMap = context.getBeansOfType(String.class);
serviceMap.forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
執行輸出:
address->上海市
可以看到容器中只有一個address被註冊了,而name這個bean沒有被註冊。
案例3:bean不存在的時候才註冊
需求
IService介面有兩個實現類Service1和Service1,這兩個類會放在2個配置類中通過@Bean的方式來註冊到容器,此時我們想加個限制,只允許有一個IService型別的bean被註冊到容器。
可以在@Bean標註的2個方法上面加上條件限制,當容器中不存在IService型別的bean時,才將這個方法定義的bean註冊到容器,下面來看程式碼實現。
程式碼實現
條件判斷類:OnMissingBeanCondition
package com.javacode2018.lesson001.demo25.test1;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
public class OnMissingBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//獲取bean工廠
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//從容器中獲取IService型別bean
Map<String, IService> serviceMap = beanFactory.getBeansOfType(IService.class);
//判斷serviceMap是否為空
return serviceMap.isEmpty();
}
}
上面matches方法中會看容器中是否存在IService型別的bean,不存在的時候返回true
IService介面
package com.javacode2018.lesson001.demo25.test1;
public interface IService {
}
介面有2個實現類
Service1
package com.javacode2018.lesson001.demo25.test1;
public class Service1 implements IService {
}
Service2
package com.javacode2018.lesson001.demo25.test1;
public class Service2 implements IService {
}
來一個配置類負責註冊Service1到容器
package com.javacode2018.lesson001.demo25.test1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig1 {
@Conditional(OnMissingBeanCondition.class) //@1
@Bean
public IService service1() {
return new Service1();
}
}
@1:方法之前使用了條件判斷
再來一個配置類負責註冊Service2到容器
package com.javacode2018.lesson001.demo25.test1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig2 {
@Conditional(OnMissingBeanCondition.class)//@1
@Bean
public IService service2() {
return new Service2();
}
}
@1:方法之前使用了條件判斷
來一個總的配置類,匯入另外2個配置類
package com.javacode2018.lesson001.demo25.test1;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({BeanConfig1.class,BeanConfig2.class}) //@1
public class MainConfig1 {
}
@1:通過@Import將其他2個配置類匯入
來個測試用例
ConditionTest新增一個方法,方法中從容器中獲取IService型別的bean,然後輸出:
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
Map<String, IService> serviceMap = context.getBeansOfType(IService.class);
serviceMap.forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
執行輸出:
service1->com.javacode2018.lesson001.demo25.test1.Service1@2cd76f31
可以看出容器中只有一個IService型別的bean。
可以將@Bean標註的2個方法上面的@Conditional去掉,再執行會輸出:
service1->com.javacode2018.lesson001.demo25.test1.Service1@49438269
service2->com.javacode2018.lesson001.demo25.test1.Service2@ba2f4ec
此時沒有條件限制,2個Service都會註冊到容器。
案例4:根據環境選擇配置類
平常我們做專案的時候,有開發環境、測試環境、線上環境,每個環境中有些資訊是不一樣的,比如資料庫的配置資訊,下面我們來模擬不同環境中使用不同的配置類來註冊不同的bean。
自定義一個條件的註解
package com.javacode2018.lesson001.demo25.test2;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Conditional(EnvCondition.class) //@1
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnvConditional {
//環境(測試環境、開發環境、生產環境)
enum Env { //@2
TEST, DEV, PROD
}
//環境
Env value() default Env.DEV; //@3
}
@1:注意這個註解比較特別,這個註解上面使用到了@Conditional註解,這個地方使用到了一個自定義Conditione類:EnvCondition
@2:列舉,表示環境,定義了3個環境
@3:這個引數用指定環境
上面這個註解一會我們會用在不同環境的配置類上面
下面來3個配置類
讓3個配置類分別在不同環境中生效,會在這些配置類上面使用上面自定義的@EnvConditional註解來做條件限定。
每個配置類中通過@Bean來定義一個名稱為name的bean,一會通過輸出這個bean來判斷哪個配置類生效了。
下面來看3個配置類的程式碼
測試環境配置類
package com.javacode2018.lesson001.demo25.test2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnvConditional(EnvConditional.Env.TEST)//@1
public class TestBeanConfig {
@Bean
public String name() {
return "我是測試環境!";
}
}
@1指定的測試環境
開發環境配置類
package com.javacode2018.lesson001.demo25.test2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnvConditional(EnvConditional.Env.DEV) //@1
public class DevBeanConfig {
@Bean
public String name() {
return "我是開發環境!";
}
}
@1:指定的開發環境
生產環境配置類
package com.javacode2018.lesson001.demo25.test2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnvConditional(EnvConditional.Env.PROD) //@1
public class ProdBeanConfig {
@Bean
public String name() {
return "我是生產環境!";
}
}
@1:指定的生產環境
下面來看一下條件類:EnvCondition
條件類會解析配置類上面@EnvConditional註解,得到環境資訊。
然後和目前的環境對比,決定返回true還是false,如下:
package com.javacode2018.lesson001.demo25.test2;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class EnvCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//當前需要使用的環境
EnvConditional.Env curEnv = EnvConditional.Env.DEV; //@1
//獲取使用條件的類上的EnvCondition註解中對應的環境
EnvConditional.Env env = (EnvConditional.Env) metadata.getAllAnnotationAttributes(EnvConditional.class.getName()).get("value").get(0);
return env.equals(curEnv);
}
}
@1:這個用來指定當前使用的環境,此處假定當前使用的是開發環境,這個我們以後可以任意發揮,比如將這些放到配置檔案中,此處方便演示效果。
來個測試用例
@Test
public void test2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println(context.getBean("name"));
}
執行輸出
我是開發環境!
可以看到開發環境生效了。
修改一下EnvCondition的程式碼,切換到生產環境:
EnvConditional.Env curEnv = EnvConditional.Env.PROD;
再次執行test2方法輸出:
我是生產環境!
生產環境配置類生效了。
案例5:Condition指定優先順序
多個Condition按順序執行
@Condtional中value指定多個Condtion的時候,預設情況下會按順序執行,還是通過程式碼來看一下效果。
下面程式碼中定義了3個Condition,每個Condition的matches方法中會輸出當前類名,然後在配置類上面同時使用這3個Condition:
package com.javacode2018.lesson001.demo25.test5;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;
class Condition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(this.getClass().getName());
return true;
}
}
class Condition2 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(this.getClass().getName());
return true;
}
}
class Condition3 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(this.getClass().getName());
return true;
}
}
@Configuration
@Conditional({Condition1.class, Condition2.class, Condition3.class})
public class MainConfig5 {
}
來個測試用例
@Test
public void test5() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
}
執行輸出:
com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3
com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3
com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3
上面有多行輸出,是因為spring解析整個配置類的過程中,有好幾個地方都會執行條件判斷。
我們們只用關注前3行,可以看出輸出的屬性和@Conditional中value值的順序是一樣的。
指定Condition的順序
自定義的Condition可以實現PriorityOrdered介面或者繼承Ordered介面,或者使用@Order註解,通過這些來指定這些Condition的優先順序。
排序規則:先按PriorityOrdered排序,然後按照order的值進行排序;也就是:PriorityOrdered asc,order值 asc
下面這幾個都可以指定order的值
介面:org.springframework.core.Ordered,有個getOrder方法用來返回int型別的值
介面:org.springframework.core.PriorityOrdered,繼承了Ordered介面,所以也有getOrder方法
註解:org.springframework.core.annotation.Order,有個int型別的value引數指定Order的大小
看案例程式碼:
package com.javacode2018.lesson001.demo25.test6;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
@Order(1) //@1
class Condition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(this.getClass().getName());
return true;
}
}
class Condition2 implements Condition, Ordered { //@2
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(this.getClass().getName());
return true;
}
@Override
public int getOrder() { //@3
return 0;
}
}
class Condition3 implements Condition, PriorityOrdered { //@4
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(this.getClass().getName());
return true;
}
@Override
public int getOrder() {
return 1000;
}
}
@Configuration
@Conditional({Condition1.class, Condition2.class, Condition3.class})//@5
public class MainConfig6 {
}
@1:Condition1通過@Order指定順序,值為1
@2:Condition2通過實現了Ordered介面來指定順序,@3:getOrder方法返回1
@4:Condition3實現了PriorityOrdered介面,實現這個介面需要重寫getOrder方法,返回1000
@5:Condtion順序為1、2、3
根據排序的規則,PriorityOrdered的會排在前面,然後會再按照order升序,最後可以順序是:
Condtion3->Condtion2->Condtion1
來個測試用例看看效果是不是我們分析的這樣:
@Test
public void test6() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
}
執行test6,部分輸出如下:
com.javacode2018.lesson001.demo25.test6.Condition3
com.javacode2018.lesson001.demo25.test6.Condition2
com.javacode2018.lesson001.demo25.test6.Condition1
結果和我們分析的一致。
案例6:ConfigurationCondition使用
ConfigurationCondition使用的比較少,很多地方對這個基本上也不會去介紹,Condition介面基本上可以滿足99%的需求了,但是springboot中卻大量用到了ConfigurationCondition這個介面。
ConfigurationCondition通過解釋比較難理解,來個案例感受一下:
來一個普通的類:Service
package com.javacode2018.lesson001.demo25.test7;
public class Service {
}
來一個配置類,通過配置類註冊上面這個Service
package com.javacode2018.lesson001.demo25.test7;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig1 {
@Bean
public Service service() {
return new Service();
}
}
再來一個配置類:BeanConfig2
package com.javacode2018.lesson001.demo25.test7;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig2 {
@Bean
public String name() {
return "路人甲Java";
}
}
來一個總的配置類
package com.javacode2018.lesson001.demo25.test7;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({BeanConfig1.class, BeanConfig2.class})
public class MainConfig7 {
}
上面通過@Import引入了另外2個配置類
來個測試用例載入MainConfig7配置類
@Test
public void test7() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
context.getBeansOfType(String.class).forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
上面從容器中獲取String型別的bean,然後輸出。
執行輸出
name->路人甲Java
現在我們有個需求
當容器中有Service這種型別的bean的時候,BeanConfig2才生效。
很簡單吧,加個Condition就行了,內部判斷容器中是否有Service型別的bean,繼續
來個自定義的Condition
package com.javacode2018.lesson001.demo25.test7;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MyCondition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//獲取spring容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//判斷容器中是否存在Service型別的bean
boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
return existsService;
}
}
上面程式碼很簡單,判斷容器中是否有IService型別的bean。
BeanConfig2上使用Condition條件判斷
@Configuration
@Conditional(MyCondition1.class)
public class BeanConfig2 {
@Bean
public String name() {
return "路人甲Java";
}
}
再次執行test7輸出
無任何輸出
為什麼?
在文章前面我們說過,配置類的處理會依次經過2個階段:配置類解析階段和bean註冊階段,Condition介面型別的條件會對這兩個階段都有效,解析階段的時候,容器中是還沒有Service這個bean的,配置類中通過@Bean註解定義的bean在bean註冊階段才會被註冊到spring容器,所以BeanConfig2在解析階段去容器中是看不到Service這個bean的,所以就被拒絕了。
此時我們需要用到ConfigurationCondition了,讓條件判斷在bean註冊階段才起效。
自定義一個ConfigurationCondition類
package com.javacode2018.lesson001.demo25.test7;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MyConfigurationCondition1 implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN; //@1
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//獲取spring容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//判斷容器中是否存在Service型別的bean
boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
return existsService;
}
}
@1:指定條件在bean註冊階段,這個條件才有效
matches方法中的內容直接複製過來,判斷規則不變。
修改BeanConfig2的類容
將
@Conditional(MyCondition1.class)
替換為
@Conditional(MyConfigurationCondition1.class)
再次執行test7輸出
name->路人甲Java
此時name這個bean被輸出了。
可以再試試將BeanConfig1中service方法上面的@Bean去掉,此時Service就不會被註冊到容器,再執行一下test7,會發現沒有輸出了,此時BeanConfig2會失效。
判斷bean存不存在的問題,通常會使用ConfigurationCondition這個介面,階段為:REGISTER_BEAN,這樣可以確保條件判斷是在bean註冊階段執行的。
對springboot比較熟悉的,它裡面有很多@Conditionxxx這樣的註解,可以去看一下這些註解,很多都實現了ConfigurationCondition介面。
Spring中這塊的原始碼
@Conditional註解是被下面這個類處理的
org.springframework.context.annotation.ConfigurationClassPostProcessor
又是這個類,說了很多次了,非常重要的一個類,大家下去了多擼一下這個類的原始碼,這樣理解起來更順暢一些。
案例原始碼
https://gitee.com/javacode2018/spring-series
路人甲java所有案例程式碼以後都會放到這個上面,大家watch一下,可以持續關注動態。
總結
@Conditional註解可以標註在spring需要處理的物件上(配置類、@Bean方法),相當於加了個條件判斷,通過判斷的結果,讓spring覺得是否要繼續處理被這個註解標註的物件
spring處理配置類大致有2個過程:解析配置類、註冊bean,這兩個過程中都可以使用@Conditional來進行控制spring是否需要處理這個過程
Condition預設會對2個過程都有效
ConfigurationCondition控制得更細一些,可以控制到具體那個階段使用條件判斷
Spring系列
更多好文章
感謝大家的閱讀,也歡迎您把這篇文章分享給更多的朋友一起閱讀!謝謝!
路人甲java
▲長按圖片識別二維碼關注
路人甲Java:工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
相關文章
- 我只是下了個訂單,鬼知道我在微服務裡經歷了什麼微服務
- 2022 招聘季 | 面試了一個月,我經歷了什麼面試
- 面試官:素有Java鎖王稱號的‘StampedLock’你知道嗎?我:這什麼鬼?面試Java
- Hello World 我們經歷了些什麼?
- 五年Java開發經驗,裸辭準備半月面試阿里,阿里巴巴卻“不講武德”居然面了我7輪,歷經千辛萬苦終於斬獲P7及OfferJava面試阿里
- 阿里雲崩了,總結我們從雲上搬到線下經歷了什麼阿里
- 免試晉升P10,他在阿里10年經歷了什麼?阿里
- 都說搭部落格簡單,鬼知道後端程式設計師要經歷什麼!後端程式設計師
- 天啦,從Mongo到ClickHouse我到底經歷了什麼?Go
- 成為比開發硬氣的測試人,我都經歷了什麼?
- Android,你從入門到放棄了嗎?程式設計師:你不知道我經歷了什麼!Android程式設計師
- 面試經歷面試
- 阿里歷年經典Java面試題彙總阿里Java面試題
- 2020畢業後我所經歷的面試【面試系列】 面試題四面試題
- [面試]記一次被問到煩起來的面試經歷。面試
- 2018年,JavaScript都經歷了什麼?JavaScript
- GM到GMP,Golang經歷了什麼?Golang
- JVM筆記 -- JVM經歷了什麼?JVM筆記
- 什麼鬼,面試官竟然讓我用Redis實現一個訊息佇列!!?面試Redis佇列
- "instanceof 的原理是什麼"?大聲告訴面試官,我知道!面試
- 我入職阿里後,才知道原來簡歷這麼寫阿里
- 從前端工程師到前端架構師, 我們經歷了什麼?前端工程師架構
- 我所經歷的Android面試|掘金技術徵文Android面試
- 大型面試現場:一條update sql執行都經歷什麼?面試SQL
- 我整理了 50 多個簡歷致命問題,知道為什麼投簡歷沒回復了!
- 面試之 一個頁面從輸入url到頁面載入顯示完成,中間都經歷了什麼面試
- 2018年5-7月面試經歷總結:阿里面試題阿里面試題
- 分享:兩年兩度升級資料庫,我們經歷了什麼資料庫
- Max:從Abi到末日餘暉,我和莉莉絲都經歷了什麼
- 面試官問:你知道Redis能做什麼?不能做什麼?面試Redis
- 你的JavaScript程式碼都經歷了什麼JavaScript
- 容易陷入焦慮的人,到底經歷了什麼?
- 兩次面試的經歷面試
- 2018Android面試經歷Android面試
- 我的奇葩面試經歷分享:喊價25K,HR 卻給了30K。。。面試
- 阿里雲釋出的數加是什麼鬼阿里
- 阿里一年,聊聊我成長了什麼阿里
- 面試了一個 39 歲程式設計師後,我被罵了……面試程式設計師