成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深度解析@Conditional注解

開發 前端
首先介紹了@Conditional注解的源碼和使用場景。隨后,列舉了四個關于@Conditional注解的案例,分別是:無條件案例、標注到方法上的案例、標注到類上的案例和同時標注到類和方法上的案例。接下來,介紹了@Conditional注解執行的源碼時序圖和源碼流程。

一、學習指引

Spring是如何根據條件創建Bean的?

日常工作過程中,相信這種情況是最常見的:根據某個或某些條件來執行相應的邏輯。換句話說,會通過if-else語句來執行一定的業務邏輯功能。

在Spring中,就有這樣一個注解,它支持根據一定的條件來創建對應的Bean對象,并將Bean對象注冊到IOC容器中。滿足條件的Bean就會被注冊到IOC容器中,不滿足條件的Bean就不會被注冊到IOC容器中。這個注解就是@Conditional注解,本章,就對@Conditional注解進行簡單的介紹。

二、注解說明

關于@Conditional注解的一點點說明~~

Spring提供的@Conditional注解支持按照條件向IOC容器中注冊Bean,滿足條件的Bean就會被注冊到IOC容器中,不滿足條件的Bean就不會被注冊到IOC容器中。

2.1 注解源碼

@Conditional注解可以標注到類或方法上,能夠實現按照條件向IOC容器中注冊Bean。源碼詳見:org.springframework.context.annotation.Conditional。

/**
* @author Phillip Webb
* @author Sam Brannen
* @since 4.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}

從@Conditional注解的源碼可以看出,@Conditional注解是從Spring 4.0版本開始提供的注解。在@Conditional注解注解中只提供了一個Class數組類型的value屬性,具體含義如下所示。

  • value:指定Condition接口的實現類,Condition接口的實現類中需要編寫具體代碼實現向Spring中注入Bean的條件。

2.2 使用場景

如果使用Spring開發的應用程序需要根據不同的運行環境來讀取不同的配置信息,例如在Windows操作系統上需要讀取Windows操作系統的環境信息,在MacOS操作系統上需要讀取MacOS操作系統的環境信息。此時,就可以使用@Conditional注解實現。

另外,@Conditional注解還有如下一些使用場景:

  • 可以作為類級別的注解直接或者間接的與@Component相關聯,包括@Configuration類。
  • 可以作為元注解,用于自動編寫構造性注解。
  • 作為方法級別的注解,作用在任何@Bean的方法上。

三、使用案例

@Conditional注解案例實戰~~

Spring的@Conditional注解可以標注到類或方法上,并且會實現按照一定的條件將對應的Bean注入到IOC容器中。所以,本節,會列舉無條件(不加@Conditional注解)、@Conditional注解標注到方法上和@Conditional注解標注到類上以及將@Conditional注解同時標注到類上和方法上等四個主要案例。

3.1 無條件案例

本節,主要實現不使用@Conditional注解時,向IOC容器中注入Bean的案例,具體實現步驟如下所示。

(1)新增Founder類

Founder類的源碼詳見:spring-annotation-chapter-08工程下的io.binghe.spring.annotation.chapter08.bean.Founder。

public class Founder {
private String name;
public Founder(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + '}';
}
}

可以看到,Founder類就是Java中的一個普通實體類。

(2)新增ConditionalConfig類

ConditionalConfig類的源碼詳見:spring-annotation-chapter-08工程下的io.binghe.spring.annotation.chapter08.config.ConditionalConfig。

@Configuration
public class ConditionalConfig {
@Bean(name = "bill")
public Founder windowsFounder(){
return new Founder("Bill Gates");
}
@Bean(name = "jobs")
public Founder macosFounder(){
return new Founder("Steve Jobs");
}
}

可以看到,ConditionalConfig類是一個Spring的配置類,并且在ConditionalConfig類中使用@Bean注解創建了兩個Bean對象,并注冊到IOC容器中,一個Bean的名稱為bill,另一個Bean的名稱為jobs。

(3)新增ConditionalTest類

ConditionalTest類的源碼詳見:spring-annotation-chapter-08工程下的io.binghe.spring.annotation.chapter08.ConditionalTest。

public class ConditionalTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionalConfig.class);
String[] definitionNames = context.getBeanDefinitionNames();
Arrays.stream(definitionNames).forEach((definitionName) -> System.out.println(definitionName));
}
}

可以看到,在ConditionalTest類的main()方法中,會打印注入到IOC容器中的Bean名稱。

(4)運行ConditionalTest類

運行ConditionalTest類的main()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
conditionalConfig
bill
jobs

從輸出的結果信息可以看出,向IOC容器中注入了名稱為conditionalConfig、bill和jobs的Bean。

說明:沒設置@Conditional注解時,會向Spring容器中注入所有使用@Bean注解創建的Bean。

3.2 標注到方法上的案例

本節,主要實現將@Conditional注解標注到方法上,向IOC容器中注入Bean的案例,具體實現步驟如下所示。

注意:本節的案例是在3.1節的基礎上進行完善,在對應的方法上添加@Conditional注解。

(1)新增WindowsCondition類

WindowsCondition類的源碼詳見:spring-annotation-chapter-08工程下的io.binghe.spring.annotation.chapter08.condition.WindowsCondition。

public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String osName = context.getEnvironment().getProperty("os.name");
return osName.toLowerCase().contains("windows");
}
}

可以看到,WindowsCondition類實現了Condition接口,并實現了matches()方法。在matches()方法中,通過Spring的環境變量讀取操作系統名稱,如果操作系統名稱中包含windows就返回true,否則返回false。當返回true時,使用@Conditional注解指定的條件為WindowsCondition類的Class對象的Bean會被創建并注入到IOC容器中。

(2)新增MacosCondition類

MacosCondition類的源碼詳見:spring-annotation-chapter-08工程下的io.binghe.spring.annotation.chapter08.condition.MacosCondition。

public class MacosCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String osName = context.getEnvironment().getProperty("os.name");
return osName.toLowerCase().contains("mac");
}
}

可以看到,MacosCondition類實現了Condition接口,并實現了matches()方法。在matches()方法中,通過Spring的環境變量讀取操作系統名稱,如果操作系統名稱中包含mac就返回true,否則返回false。當返回true時,使用@Conditional注解指定的條件為MacosCondition類的Class對象的Bean會被創建并注入到IOC容器中。

(3)修改ConditionalConfig類

在ConditionalConfig類的方法上標注@Conditional注解,修改后的源碼如下所示。

@Bean(name = "bill")
@Conditional(value = {WindowsCondition.class})
public Founder windowsFounder(){
System.out.println("創建名稱為bill的Bean對象");
return new Founder("Bill Gates");
}

@Bean(name = "jobs")
@Conditional(value = {MacosCondition.class})
public Founder macosFounder(){
System.out.println("創建名稱為jobs的Bean對象");
return new Founder("Steve Jobs");
}

可以看到,在創建名稱為bill的Bean的方法上標注了@Conditional注解,并指定了value的屬性為WindowsCondition類的class對象。在創建名稱為jobs的Bean的方法上標注了@Conditional注解,并指定了value的屬性為MacosCondition類的class對象。

(4)運行ConditionalTest類

運行ConditionalTest類的main()方法,輸出的結果信息如下所示。

創建名稱為bill的Bean對象
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
conditionalConfig
bill

可以看到,由于我的電腦是Windows操作系統,所以,打印出的Bean名稱包含conditionalConfig和bill,不包含jobs。

說明:@Conditional注解標注到使用@Bean創建Bean的方法上時,只有滿足@Conditional注解的條件時,才會執行方法體創建Bean對象并注入到IOC容器中。

3.3 標注到類上的案例

本節,主要實現將@Conditional注解標注到類上,向IOC容器中注入Bean的案例,具體實現步驟如下所示。

注意:本節的案例是在3.1節的基礎上進行完善,在對應的類上添加@Conditional注解。

(1)修改ConditionalConfig類

刪除ConditionalConfig類中的方法上的@Conditional注解,并在ConditionalConfig類上標注@Conditional注解。

@Configuration
@Conditional(value = {MacosCondition.class})
public class ConditionalConfig {
@Bean(name = "bill")
public Founder windowsFounder(){
System.out.println("創建名稱為bill的Bean對象");
return new Founder("Bill Gates");
}
@Bean(name = "jobs")
public Founder macosFounder(){
System.out.println("創建名稱為jobs的Bean對象");
return new Founder("Steve Jobs");
}
}

可以看到,在ConditionalConfig類上標注了@Conditional注解,并且將value屬性設置為MacosCondition。也就是說,當前操作系統為MacOS操作系統時,才會創建名稱為bill和jobs的Bean,并將其注入到IOC容器中。

(2)運行ConditionalTest類

運行ConditionalTest類的main()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

從輸出的結果信息可以看出,由于我的電腦是Windows操作系統,所以,在輸出的Bean名稱中,并不包含conditionalConfig、bill和jobs。

說明:當@Conditional注解標注到類上時,如果運行程序時,不滿足@Conditional注解中指定的條件,則當前類的所有Bean都不會被創建,也不會注入到IOC容器中。

3.4 同時標注到類和方法上

本節,主要實現將@Conditional注解同時標注到類和方法上,向IOC容器中注入Bean的案例,具體實現步驟如下所示。

注意:本節的案例是在3.1節的基礎上進行完善,在對應的類上添加@Conditional注解。

(1)修改ConditionalConfig類

在ConditionalConfig類的類上和方法上同時標注@Conditional注解。如下所示。

@Configuration
@Conditional(value = {MacosCondition.class})
public class ConditionalConfig {
@Bean(name = "bill")
@Conditional(value = {WindowsCondition.class})
public Founder windowsFounder(){
System.out.println("創建名稱為bill的Bean對象");
return new Founder("Bill Gates");
}
@Bean(name = "jobs")
@Conditional(value = {MacosCondition.class})
public Founder macosFounder(){
System.out.println("創建名稱為jobs的Bean對象");
return new Founder("Steve Jobs");
}
}

可以看到,在ConditionalConfig類的類上和方法上都標注了@Conditional注解。其中,在類上標注的@Conditional注解的條件是當前操作系統為MacOS系統。

(2)運行ConditionalTest類

運行ConditionalTest類的main()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

從輸出的結果信息可以看出,由于我的電腦是Windows操作系統,所以,在輸出的Bean名稱中,并不包含conditionalConfig、bill和jobs。

說明:當@Conditional注解同時標注到類和方法上時,如果標注到類上的@Conditional注解不滿足條件,即使類中的方法上標注的@Conditional注解滿足條件,也不會創建Bean,并且也不會將Bean注入到IOC容器中。

四、源碼時序圖

結合時序圖理解源碼會事半功倍,你覺得呢?

本節,就以源碼時序圖的方式,直觀的感受下@Conditional注解在Spring源碼層面的執行流程。@Conditional注解的源碼時序圖如圖8-1和8-2所示。

圖片

圖片

五、源碼解析

源碼時序圖整清楚了,那就整源碼解析唄!

@Conditional注解在Spring源碼層面的執行流程,結合源碼執行的時序圖,會理解的更加深刻。本節,就簡單結合源碼時序圖簡單分析下@Conditional注解在Spring源碼層面的執行流程。

注意:@Conditional注解在Spring源碼層面的執行流程與第7章的5.1節@DependsOn注解在Spring源碼層面注冊Bean的執行流程大體類似,只是在執行AnnotatedBeanDefinitionReader類的doRegisterBean()方法的邏輯時,略有差異。

(1)解析AnnotatedBeanDefinitionReader類的doRegisterBean(ClassbeanClass, String name, Class<? extends Annotation>[] qualifiers, Suppliersupplier, BeanDefinitionCustomizer[] customizers)方法

源碼詳見:org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean(ClassbeanClass, String name, Class<? extends Annotation>[] qualifiers, Suppliersupplier, BeanDefinitionCustomizer[] customizers)。

private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

可以看到,在AnnotatedBeanDefinitionReader類的doRegisterBean()方法中,調用了conditionEvaluator對象的shouldSkip()方法判斷是否要忽略當前Bean的注冊。

(2)解析ConditionEvaluator類的shouldSkip(AnnotatedTypeMetadata metadata)方法

源碼詳見:org.springframework.context.annotation.ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata metadata)

public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}

可以看到,在ConditionEvaluator類的shouldSkip()方法中,直接調用了另一個重載的shouldSkip()方法。

(3)解析ConditionEvaluator類的shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase)方法

源碼詳見:org.springframework.context.annotation.ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase)。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}

if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}

List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}

AnnotationAwareOrderComparator.sort(conditions);

for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}

return false;
}

可以看到,在shouldSkip()方法中,首先會判斷類或方法上是否標注了@Conditional注解,如果沒有標注@Conditional注解,則直接返回false,此時對應的Bean會被創建并注入到IOC容器中。

否則,會解析@Conditional注解中的value屬性設置的Class對象,將Class對象的全類名解析到conditionClasses數組中,遍歷conditionClasses數組中的每個元素生成Condition對象,將Condition對象存入conditions集合中。后續會遍歷conditions集合中的每個Condition對象,調用matches()方法,此處的邏輯與matches()方法的返回值正好相反。

  • matches()方法返回false,則此處返回true,表示對應的Bean不會被創建,也不會注入到IOC容器中。
  • matches()方法返回true,則此處返回false,表示對應的Bean會被創建并且會注入到IOC容器中。

接下來,就會回到AnnotatedBeanDefinitionReader類的doRegisterBean()方法繼續執行后續流程,后續流程與第7章的5.1節@DependsOn注解在Spring源碼層面注冊Bean的執行流程一致,這里不再贅述。

至此,@Conditional注解在Spring源碼層面的執行流程分析完畢。

六、擴展注解

@Conditional的擴展注解如下所示:

@ConditionalOnBean:僅僅在當前上下文中存在某個對象時,才會實例化一個Bean。@ConditionalOnClass:某個class位于類路徑上,才會實例化一個Bean。@ConditionalOnExpression:當表達式為true的時候,才會實例化一個Bean。@ConditionalOnMissingBean:僅僅在當前上下文中不存在某個對象時,才會實例化一個Bean。@ConditionalOnMissingClass:某個class類路徑上不存在的時候,才會實例化一個Bean。@ConditionalOnNotWebApplication:不是web應用,才會實例化一個Bean。@ConditionalOnBean:當容器中有指定Bean的條件下進行實例化。@ConditionalOnMissingBean:當容器里沒有指定Bean的條件下進行實例化。@ConditionalOnClass:當classpath類路徑下有指定類的條件下進行實例化。@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下進行實例化。@ConditionalOnWebApplication:當項目是一個Web項目時進行實例化。@ConditionalOnNotWebApplication:當項目不是一個Web項目時進行實例化。@ConditionalOnProperty:當指定的屬性有指定的值時進行實例化。@ConditionalOnExpression:基于SpEL表達式的條件判斷。@ConditionalOnJava:當JVM版本為指定的版本范圍時觸發實例化。@ConditionalOnResource:當類路徑下有指定的資源時觸發實例化。@ConditionalOnJndi:在JNDI存在的條件下觸發實例化。@ConditionalOnSingleCandidate:當指定的Bean在容器中只有一個,或者有多個但是指定了首選的Bean時觸發實例化。

七、總結

@Conditional注解介紹完了,我們一起總結下吧!

本章,首先介紹了@Conditional注解的源碼和使用場景。隨后,列舉了四個關于@Conditional注解的案例,分別是:無條件案例、標注到方法上的案例、標注到類上的案例和同時標注到類和方法上的案例。接下來,介紹了@Conditional注解執行的源碼時序圖和源碼流程。

八、思考

既然學完了,就開始思考幾個問題吧?

關于@Conditional注解,通常會有如下幾個經典面試題:

  • @Conditional注解的作用是什么?
  • @Conditional注解有哪些使用場景?
  • @Conditional注解與@Profile注解有什么區別?
  • @Conditional注解在Spring內層的執行流程?
  • 你在平時工作中,會在哪些場景下使用@Conditional注解?
  • 你從@Conditional注解的設計中得到了哪些啟發?
責任編輯:武曉燕 來源: 冰河技術
相關推薦

2023-03-06 11:13:20

Spring注解加載

2023-03-13 08:12:25

@DependsOn源碼場景

2023-02-27 08:10:00

代理對象Spring

2023-05-08 08:11:49

@Component使用場景時序圖

2022-12-22 08:14:54

2023-05-29 08:11:42

@Value注解Bean

2025-03-25 10:00:00

Spring開發Java

2024-01-11 12:14:31

Async線程池任務

2023-10-10 11:02:00

LSM Tree數據庫

2013-12-09 10:34:12

2019-03-06 09:55:54

Python 開發編程語言

2024-12-17 00:00:00

Spring線程

2009-12-14 17:14:08

Ruby文件操作

2011-06-27 09:15:21

QT Creator

2011-07-29 15:09:48

iPhone Category

2011-07-01 14:39:08

Qt Quick

2011-06-02 11:13:10

Android Activity

2011-08-02 18:07:03

iPhone 內省 Cocoa

2012-08-03 08:57:37

C++

2023-10-12 13:01:29

Redis數據庫
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 在线亚州 | 中日韩毛片 | 欧美日韩电影一区二区 | 成人av观看 | 亚洲成av| 久久网站免费视频 | 在线高清免费观看视频 | 亚洲一区二区在线视频 | 免费在线观看成人av | 日韩欧美国产综合 | 免费一区 | 国产一区二区三区四 | 欧美日韩在线看 | 国产在线资源 | 涩涩视频网站在线观看 | 狠狠狠色丁香婷婷综合久久五月 | 亚洲精品99 | www.色婷婷 | 中文字幕日韩欧美 | 久久久精品久久久 | 亚洲国产成人精品女人久久久野战 | 国产黄色免费网站 | 久久久久国产精品一区 | 69热视频在线观看 | 狠狠ri | 日韩欧美福利视频 | 精品一区二区三区在线观看 | 久久国产精品99久久久大便 | 精品欧美一区二区久久久伦 | 手机在线观看 | 亚洲视频区 | 亚洲一区二区三区免费在线观看 | 欧美国产一区二区三区 | 亚洲精品在线免费观看视频 | 亚洲第一天堂 | 国产日韩欧美激情 | 日韩在线一区二区 | 亚洲成人黄色 | 精品久久久久久一区二区 | 欧美激情综合 | 国产黄色在线观看 |