解密Spring?Boot深入理解條件裝配與條件注解
一、條件裝配概述
1.1 條件裝配的基本原理
條件裝配的基本原理是根據(jù)特定的條件來決定是否應(yīng)用特定的配置或組件。在 Spring Boot 中,條件裝配是通過條件注解來實現(xiàn)的。
條件注解是一種特殊的注解,用于標記在配置類、組件類或方法上。它們根據(jù)某些條件的結(jié)果來決定是否應(yīng)用相應(yīng)的配置或組件。
條件注解的基本原理:
- 條件判斷:Spring 在處理配置類或組件時,會對標記了條件注解的類或方法進行條件判斷。
- 條件匹配:條件注解中定義的條件匹配器會根據(jù)特定的條件,如類路徑是否存在、Bean 是否存在、屬性是否被設(shè)置等,對環(huán)境進行判斷,如果條件滿足則返回 true,否則返回 false。
- 條件注解處理器:Spring 容器會使用條件注解處理器來處理條件注解,根據(jù)條件匹配的結(jié)果來決定是否應(yīng)用相應(yīng)的配置或組件。
- 應(yīng)用配置或組件:根據(jù)條件注解的處理結(jié)果,Spring 容器會決定是否應(yīng)用相應(yīng)的配置或組件。如果條件滿足,則進行相應(yīng)的配置或組件的注冊和初始化;如果條件不滿足,則忽略該配置或組件。
1.2 條件裝配的作用
條件裝配的作用在于根據(jù)特定的條件來決定是否應(yīng)用特定的配置或組件,從而實現(xiàn)靈活性和可配置性。
條件裝配實現(xiàn)的作用:
- 環(huán)境適配:通過條件裝配,可以根據(jù)當前的運行環(huán)境(如開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境)或者配置(如不同的數(shù)據(jù)庫、不同的服務(wù)提供商)來動態(tài)地選擇合適的配置或組件,從而使應(yīng)用程序適應(yīng)不同的環(huán)境。
- 可插拔性:條件裝配可以根據(jù)應(yīng)用程序的需求動態(tài)地選擇性地應(yīng)用不同的配置或組件,使得應(yīng)用程序的功能可以根據(jù)需求進行擴展或者替換,從而增強了應(yīng)用程序的可插拔性和可擴展性。
- 簡化配置:通過條件裝配,可以根據(jù)特定的條件自動地應(yīng)用相應(yīng)的配置或組件,而無需手動配置或編寫復(fù)雜的條件判斷邏輯,從而簡化了配置過程,提高了配置的易用性和可維護性。
- 優(yōu)化性能:通過條件裝配,可以根據(jù)特定的條件選擇性地應(yīng)用相應(yīng)的配置或組件,避免不必要的資源消耗,從而優(yōu)化了應(yīng)用程序的性能和資源利用率。
二、常用注解
2.1 @ConditionalOnClass
@ConditionalOnClass
是 Spring Boot 中的一個條件注解,用于在類路徑中存在指定的類時才會應(yīng)用相應(yīng)的配置。
定義了一個靈活的條件注解 ConditionalOnClass
,它可以根據(jù)類路徑中特定類的存在與否來決定是否應(yīng)用相應(yīng)的配置或組件。
示例和用法說明:
/** * 只有當應(yīng)用程序的類路徑中存在 RedisTemplate 類時,RedisConfiguration 類中定義的 redisTemplate() 方法才會被注冊為 Bean,并被 Spring 容器管理 * 如果類路徑中不存在 RedisTemplate 類,則該配置類中的 Bean 將被忽略 */ @Configuration @ConditionalOnClass({org.springframework.data.redis.core.RedisTemplate.class}) public class RedisConfiguration { @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // 配置 RedisTemplate 的相關(guān)屬性 return redisTemplate; } }
2.2 @ConditionalOnBean
@ConditionalOnBean
是 Spring Framework 中的一個條件注解,它的作用是在容器中存在指定的 Bean 時,才會應(yīng)用相應(yīng)的配置或組件。如果指定的 Bean 不存在,則該配置或組件將被忽略。
定義了一個具有多個屬性的注解 ConditionalOnBean
,可以用于指定條件判斷所依賴的類、名稱、注解等信息,以及搜索依賴 Bean 的策略和泛型容器中的參數(shù)化類型。
示例和用法說明:
基本用法:
/** * MyService 類被標記為 @ConditionalOnBean(MyBean.class),這意味著只有當容器中存在 MyBean 類型的 Bean 時,MyService 才會被創(chuàng)建并添加到容器中 */ @Configuration public class MyConfiguration { @Bean public MyBean myBean() { return new MyBean(); } @ConditionalOnBean(MyBean.class) @Bean public MyService myService() { return new MyService(); } }
多個 Bean 的情況:
/** * MyService 類被標記為 @ConditionalOnBean({MyBean.class, AnotherBean.class}),這意味著只有當容器中同時存在 MyBean 和 AnotherBean 類型的 Bean 時,MyService 才會被創(chuàng)建并添加到容器中 */ @Configuration public class MyConfiguration { @Bean public MyBean myBean() { return new MyBean(); } @Bean public AnotherBean anotherBean() { return new AnotherBean(); } @ConditionalOnBean({MyBean.class, AnotherBean.class}) @Bean public MyService myService() { return new MyService(); } }
使用名稱來指定 Bean:
/** * MyService 類被標記為 @ConditionalOnBean(name = {"myBean", "anotherBean"}),這意味著只有當容器中同時存在名稱為 "myBean" 和 "anotherBean" 的 Bean 時,MyService 才會被創(chuàng)建并添加到容器中 */ @Configuration public class MyConfiguration { @Bean(name = "myBean") public MyBean myBean() { return new MyBean(); } @Bean(name = "anotherBean") public AnotherBean anotherBean() { return new AnotherBean(); } @ConditionalOnBean(name = {"myBean", "anotherBean"}) @Bean public MyService myService() { return new MyService(); } }
2.3 @ConditionalOnProperty
@ConditionalOnProperty
注解是 Spring Framework 中的條件注解之一,用于基于配置屬性的存在與否來決定是否應(yīng)用某個配置。
定義了一個具有多個屬性的注解 ConditionalOnProperty
,它可以用于根據(jù)配置文件中的屬性值來決定是否應(yīng)用某個配置。
示例和說明:
/** * @ConditionalOnProperty 注解指定了一個名為myapp.feature.enabled 的屬性,當這個屬性存在并且其值為"true"時,MyFeatureConfiguration 配置類中的配置會生效 * havingValue 參數(shù)指定了期望的屬性值,如果沒有指定havingValue,則默認匹配任何非空值 * matchIfMissing 參數(shù)指定了當配置文件中未設(shè)置該屬性時,是否應(yīng)該匹配。如果設(shè)置為 true,則表示當屬性不存在時也匹配,這樣可以設(shè)置默認行為 */ @Configuration @ConditionalOnProperty( name = "myapp.feature.enabled", havingValue = "true", matchIfMissing = true ) public class MyFeatureConfiguration { }
myapp.feature.enabled=true
2.4 @ConditionalOnExpression
@ConditionalOnExpression
是 Spring 框架中的一個條件注解,在應(yīng)用配置時根據(jù) SpEL表達式的結(jié)果來決定是否進行配置。它允許我們使用更靈活的表達式來控制配置的條件。
定義了一個具有一個屬性的注解 ConditionalOnExpression
,它可以根據(jù) SpEL
表達式的結(jié)果來決定是否應(yīng)用某個配置。
示例和說明:
/** * 檢查配置文件中的 my.config.enabled 屬性是否等于 'true' * 如果等于 'true',則表達式結(jié)果為 true`,MyBean 實例將會被創(chuàng)建 * 否則,表達式結(jié)果為 false,配置將被忽略,不會創(chuàng)建 MyBean 實例 */ @Configuration public class MyConfig { @Bean @ConditionalOnExpression("#{environment.getProperty('my.config.enabled') == 'true'}") public MyBean myBean() { // 配置生效時創(chuàng)建 MyBean 實例 return new MyBean(); } }
2.5 @ConditionalOnMissingBean
@ConditionalOnMissingBean
是一個 Spring Boot 中常用的條件注解,它的作用是:當容器中不存在指定的 Bean 時,才會進行配置。
定義了一個具有多個屬性的注解 ConditionalOnMissingBean
,用于根據(jù)存在或缺少特定類型的 bean 來決定是否應(yīng)用某個配置。
示例和說明:
/** * 使用 @ConditionalOnMissingBean 注解來判斷容器中是否已經(jīng)存在了 MyService 類型的 Bean * 如果不存在,則創(chuàng)建一個 MyServiceImpl 實例并返回 * 否則,不進行任何操作。 */ @Configuration public class MyConfiguration { @Bean @ConditionalOnMissingBean(MyService.class) public MyService myService() { return new MyServiceImpl(); } }
三、條件裝配的實現(xiàn)原理
條件裝配的實現(xiàn)原理主要基于Spring的IoC容器和@Conditional注解。
在Spring的IoC容器中,BeanFactoryPostProcessor和BeanPostProcessor是兩個核心的接口,它們允許我們在bean的創(chuàng)建和配置過程中添加額外的邏輯。(想要了解源碼,讀者可以查看我前面的博文)
條件裝配的實現(xiàn)原理:
@Conditional注解:這個注解可以標記在類、方法或注解上,用于指定在特定的條件滿足時才創(chuàng)建和配置bean。@Conditional
注解需要一個Class
類型的參數(shù),這個參數(shù)需要實現(xiàn)Condition
接口。Condition接口:這是一個函數(shù)式接口,它定義了一個matches(ConditionContext context, AnnotatedTypeMetadata metadata)
方法。
- 這個方法返回一個boolean值,表示條件是否滿足。如果返回true,則Spring容器會創(chuàng)建和配置相應(yīng)的bean;如果返回false,則不會創(chuàng)建和配置。
- 兩個參數(shù)提供了關(guān)于Spring容器和當前正在評估的bean的元數(shù)據(jù)信息。
自動配置:在Spring Boot中,條件裝配被廣泛應(yīng)用于自動配置。
- Spring Boot會根據(jù)我們在
pom.xml
文件中引入的依賴,自動配置相應(yīng)的bean。 - 這是通過一系列的
AutoConfiguration
類實現(xiàn)的,這些類上通常會使用@ConditionalOnClass
、@ConditionalOnMissingBean
等注解來指定條件。
四、實際案例
假設(shè)正在開發(fā)一個在線商城的 Spring Boot 應(yīng)用程序,其中包含了用戶管理和訂單管理兩個模塊。現(xiàn)在,希望在用戶注冊時發(fā)送一封歡迎郵件,但是如果用戶已經(jīng)在系統(tǒng)中存在,則不發(fā)送郵件。
ps:使用條件注解
@ConditionalOnMissingBean
來實現(xiàn)這一定制化功能。
創(chuàng)建一個郵件服務(wù)接口 EmailService
和實現(xiàn)類 WelcomeEmailService
。
/** * 郵件服務(wù)接口 */ public interface EmailService { void sendWelcomeEmail(String email); } /** * 發(fā)送歡迎郵件 */ @Service public class EmailServiceImpl implements EmailService { @Override public void sendWelcomeEmail(String email) { // 發(fā)送歡迎郵件的邏輯 System.out.println("Sending welcome email to: " + email); } }
創(chuàng)建一個用戶服務(wù)類 UserService
,在用戶注冊時調(diào)用郵件服務(wù)發(fā)送歡迎郵件。
public interface UserService { public void registerUser(String email); } /** * 在用戶注冊時檢查是否已經(jīng)存在該用戶,如果不存在則發(fā)送歡迎郵件 */ @Service public class UserServiceImpl implements UserService { private final UserMapper userMapper; private final EmailService emailService; @Autowired public UserServiceImpl(UserMapper userMapper, EmailService emailService) { this.userMapper = userMapper; this.emailService = emailService; } @Override public void registerUser(String email) { if(!userMapper.existsByEmail(email)) { userMapper.save(email); emailService.sendWelcomeEmail(email); }else { throw new IllegalArgumentException("Email already exists"); } } }
創(chuàng)建一個 UserRepository
實現(xiàn),它使用HashSet
來模擬存儲用戶信息。
/** * 不想使用數(shù)據(jù)庫,直接使用HashSet來模擬存儲用戶信息的email * 使用一個HashSet來存儲注冊過的email,HashSet不允許存儲重復(fù)的元素 * @author LEK */ @Repository public class UserMapper { private final Set<String> registeredEmails = new HashSet<>(); public boolean existsByEmail(String email) { return registeredEmails.contains(email); } public void save(String email) { if (Objects.nonNull(email) && !email.isEmpty()) { registeredEmails.add(email); } } }
使用 @ConditionalOnMissingBean
注解來確保只有在容器中不存在 EmailService
的實現(xiàn)類時才會注入 WelcomeEmailService
。這樣,如果用戶在系統(tǒng)中已經(jīng)存在,就不會發(fā)送歡迎郵件。
@Configuration public class EmailConfig { /** * 郵件配置 * */ @Bean @ConditionalOnMissingBean(EmailService.class) public EmailServiceImpl email() { return new EmailServiceImpl(); } }
新建UserServiceImplTest
測試類,由于是使用HashSet
來模擬運行,每次啟動都是不存在的,然后手動一下。
@SpringBootTest public class UserServiceImplTest { @Autowired private UserService userService; @Test public void testRegisterExistingUser() { String existingEmail = "existing@example.com"; userService.registerUser(existingEmail); // 注冊已存在的用戶,預(yù)期會拋出 IllegalArgumentException userService.registerUser(existingEmail); } }
運行效果。
到此這篇關(guān)于解密Spring Boot深入理解條件裝配與條件注解的文章就介紹到這了,更多相關(guān)Spring Boot條件裝配與條件注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot項目在IntelliJ IDEA中如何實現(xiàn)熱部署
spring-boot-devtools是一個為開發(fā)者服務(wù)的一個模塊,其中最重要的功能就是自動應(yīng)用代碼更改到最新的App上面去。,這篇文章主要介紹了SpringBoot項目在IntelliJ IDEA中如何實現(xiàn)熱部署,感興趣的小伙伴們可以參考一下2018-07-07java如何讀取properties文件將參數(shù)值配置到靜態(tài)變量
這篇文章主要介紹了java如何讀取properties文件將參數(shù)值配置到靜態(tài)變量問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08JAVA將中文轉(zhuǎn)換為拼音簡單實現(xiàn)方法
拼音轉(zhuǎn)換是中文處理的常見需求,TinyPinyin、HanLP、pinyin4j是常用的本地拼音轉(zhuǎn)換庫,各有特點,開發(fā)者可根據(jù)具體需求選擇合適的拼音轉(zhuǎn)換工具,需要的朋友可以參考下2024-10-10java數(shù)據(jù)庫操作類演示實例分享(java連接數(shù)據(jù)庫)
java數(shù)據(jù)庫操作類演示實例分享,大家參考使用吧2013-12-12基于spring?@Cacheable?注解的spel表達式解析執(zhí)行邏輯
這篇文章主要介紹了spring?@Cacheable?注解的spel表達式解析執(zhí)行邏輯,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01