SpringBoot中四種常用的條件裝配技術(shù)詳解
Spring Boot提供了多種條件裝配技術(shù),允許開發(fā)者根據(jù)不同條件動態(tài)配置應(yīng)用程序,大大提高了應(yīng)用的靈活性,本文將介紹Spring Boot中四種常用的條件裝配技術(shù)。
一、@Conditional注解及派生注解
1. 基本原理
@Conditional注解是Spring 4引入的核心條件裝配機制,它允許開發(fā)者根據(jù)特定條件來決定是否創(chuàng)建某個Bean或啟用某個配置。
@Conditional的基本工作原理是:當(dāng)Spring容器處理帶有@Conditional注解的Bean定義時,會先評估指定的條件是否滿足,只有當(dāng)條件滿足時,才會創(chuàng)建Bean或應(yīng)用配置。
2. @Conditional基本用法
使用@Conditional注解時,需要指定一個實現(xiàn)了Condition接口的條件類:
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
自定義條件類示例:
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
String os = env.getProperty("os.name");
return os != null && os.toLowerCase().contains("linux");
}
}
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
String os = env.getProperty("os.name");
return os != null && os.toLowerCase().contains("windows");
}
}
然后,使用這些條件類來決定Bean的創(chuàng)建:
@Configuration
public class OperatingSystemConfig {
@Bean
@Conditional(LinuxCondition.class)
public CommandService linuxCommandService() {
return new LinuxCommandService();
}
@Bean
@Conditional(WindowsCondition.class)
public CommandService windowsCommandService() {
return new WindowsCommandService();
}
}
上面的配置會根據(jù)運行環(huán)境的操作系統(tǒng)類型,創(chuàng)建不同的CommandService實現(xiàn)。
3. 常用派生注解
Spring Boot提供了一系列基于@Conditional的派生注解,簡化了常見條件判斷的配置:
@ConditionalOnClass/@ConditionalOnMissingClass
根據(jù)類路徑中是否存在特定類來決定配置:
@Configuration
public class JpaConfig {
@Bean
@ConditionalOnClass(name = "javax.persistence.EntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
// 只有當(dāng)類路徑中存在JPA相關(guān)類時,才創(chuàng)建此Bean
return new LocalContainerEntityManagerFactoryBean();
}
@Bean
@ConditionalOnMissingClass("javax.persistence.EntityManager")
public JdbcTemplate jdbcTemplate() {
// 當(dāng)類路徑中不存在JPA相關(guān)類時,采用JdbcTemplate
return new JdbcTemplate();
}
}
@ConditionalOnBean/@ConditionalOnMissingBean
根據(jù)容器中是否存在特定Bean來決定配置:
@Configuration
public class DataSourceConfig {
@Bean
@ConditionalOnMissingBean
public DataSource defaultDataSource() {
// 當(dāng)容器中沒有DataSource類型的Bean時,創(chuàng)建默認(rèn)數(shù)據(jù)源
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@ConditionalOnBean(name = "customDataSourceProperties")
public DataSource customDataSource(CustomDataSourceProperties properties) {
// 當(dāng)存在名為customDataSourceProperties的Bean時,創(chuàng)建自定義數(shù)據(jù)源
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
return dataSource;
}
}
@ConditionalOnProperty
根據(jù)配置屬性的值來決定配置:
@Configuration
public class CacheConfig {
@Bean
@ConditionalOnProperty(name = "cache.type", havingValue = "redis")
public CacheManager redisCacheManager() {
// 當(dāng)cache.type屬性值為redis時,配置Redis緩存管理器
return new RedisCacheManager();
}
@Bean
@ConditionalOnProperty(name = "cache.type", havingValue = "ehcache")
public CacheManager ehCacheManager() {
// 當(dāng)cache.type屬性值為ehcache時,配置EhCache緩存管理器
return new EhCacheCacheManager();
}
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "false", matchIfMissing = true)
public CacheManager noOpCacheManager() {
// 當(dāng)cache.enabled為false或未設(shè)置時,使用空操作緩存管理器
return new NoOpCacheManager();
}
}
@ConditionalOnExpression
根據(jù)SpEL表達(dá)式的結(jié)果來決定配置:
@Configuration
public class SecurityConfig {
@Bean
@ConditionalOnExpression("${security.enabled:true} and ${security.type:basic} == 'oauth2'")
public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
// 當(dāng)security.enabled為true且security.type為oauth2時生效
return http
.oauth2Login()
.and()
.build();
}
@Bean
@ConditionalOnExpression("${security.enabled:true} and ${security.type:basic} == 'basic'")
public SecurityFilterChain basicSecurityFilterChain(HttpSecurity http) throws Exception {
// 當(dāng)security.enabled為true且security.type為basic時生效
return http
.httpBasic()
.and()
.build();
}
}
@ConditionalOnWebApplication/@ConditionalOnNotWebApplication
根據(jù)應(yīng)用是否為Web應(yīng)用來決定配置:
@Configuration
public class ServerConfig {
@Bean
@ConditionalOnWebApplication
public ServletWebServerFactory servletWebServerFactory() {
// 只有在Web應(yīng)用中才創(chuàng)建此Bean
return new TomcatServletWebServerFactory();
}
@Bean
@ConditionalOnNotWebApplication
public ApplicationRunner consoleRunner() {
// 只有在非Web應(yīng)用中才創(chuàng)建此Bean
return args -> System.out.println("Running as a console application");
}
}
4. 實戰(zhàn)示例:構(gòu)建適應(yīng)不同緩存環(huán)境的應(yīng)用
下面通過一個實際例子,展示如何使用@Conditional系列注解構(gòu)建一個能夠適應(yīng)不同緩存環(huán)境的應(yīng)用:
@Configuration
public class FlexibleCacheConfiguration {
@Bean
@ConditionalOnClass(name = "org.springframework.data.redis.core.RedisTemplate")
@ConditionalOnProperty(name = "cache.type", havingValue = "redis")
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheManager.RedisCacheManagerBuilder builder =
RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);
return builder.build();
}
@Bean
@ConditionalOnClass(name = "org.ehcache.jsr107.EhcacheCachingProvider")
@ConditionalOnProperty(name = "cache.type", havingValue = "ehcache")
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager ehCacheCacheManager() {
return new JCacheCacheManager(getJCacheCacheManager());
}
@Bean
@ConditionalOnProperty(
name = "cache.type",
havingValue = "simple",
matchIfMissing = true
)
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager simpleCacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("users"),
new ConcurrentMapCache("transactions"),
new ConcurrentMapCache("products")
));
return cacheManager;
}
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "false")
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager noOpCacheManager() {
return new NoOpCacheManager();
}
private javax.cache.CacheManager getJCacheCacheManager() {
// 創(chuàng)建JCache CacheManager的邏輯...
return null; // 實際代碼需要返回真實的CacheManager
}
}
在上面的配置中:
- 如果類路徑中有Redis相關(guān)類,且配置了
cache.type=redis,則使用Redis緩存 - 如果類路徑中有EhCache相關(guān)類,且配置了
cache.type=ehcache,則使用EhCache - 如果配置了
cache.type=simple或未指定類型,則使用簡單的內(nèi)存緩存 - 如果配置了
cache.enabled=false,則使用不執(zhí)行任何緩存操作的NoOpCacheManager
5. 優(yōu)缺點分析
優(yōu)點:
- 靈活強大,能適應(yīng)幾乎所有條件判斷場景
- 與Spring生態(tài)系統(tǒng)無縫集成
- 派生注解簡化了常見場景的配置
- 條件判斷邏輯與業(yè)務(wù)邏輯分離,保持代碼清晰
缺點:
- 復(fù)雜條件可能導(dǎo)致配置難以理解和調(diào)試
- 條件裝配的順序可能影響最終的Bean定義
二、Profile條件配置
1. 基本原理
Profile是Spring提供的另一種條件裝配機制,主要用于按環(huán)境(如開發(fā)、測試、生產(chǎn))管理Bean的創(chuàng)建。與@Conditional相比,Profile更專注于環(huán)境區(qū)分,配置更簡單。
2. @Profile注解用法
使用@Profile注解標(biāo)記Bean或配置類,指定它們在哪些Profile激活時才會被創(chuàng)建:
@Configuration
public class DataSourceConfig {
@Bean
@Profile("development")
public DataSource developmentDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("production")
public DataSource productionDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/proddb");
dataSource.setUsername("produser");
dataSource.setPassword("prodpass");
return dataSource;
}
}
也可以在配置類級別應(yīng)用@Profile注解,控制整個配置類的激活:
@Configuration
@Profile("development")
public class DevelopmentConfig {
// 開發(fā)環(huán)境特有的Bean定義...
}
@Configuration
@Profile("production")
public class ProductionConfig {
// 生產(chǎn)環(huán)境特有的Bean定義...
}
3. 激活Profile的方式
有多種方式可以激活指定的Profile:
通過配置文件
在application.properties或application.yml中:
# application.properties spring.profiles.active=development
或
# application.yml
spring:
profiles:
active: development
通過命令行參數(shù)
java -jar myapp.jar --spring.profiles.active=production
通過環(huán)境變量
export SPRING_PROFILES_ACTIVE=production java -jar myapp.jar
通過代碼激活
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setAdditionalProfiles("production");
app.run(args);
}
}
4. Profile組合與否定
Spring Boot 2.4及以上版本提供了更靈活的Profile表達(dá)式:
使用Profile組
# application.properties spring.profiles.group.production=proddb,prodmq spring.profiles.group.development=devdb,devmq
上面的配置定義了兩個Profile組:當(dāng)激活"production"時,會同時激活"proddb"和"prodmq";當(dāng)激活"development"時,會同時激活"devdb"和"devmq"。
使用否定表達(dá)式
@Bean
@Profile("!development")
public MonitoringService productionMonitoringService() {
return new DetailedMonitoringService();
}
上面的配置表示,除了"development"之外的所有Profile都會創(chuàng)建這個Bean。
5. 實戰(zhàn)示例:基于Profile的消息隊列配置
下面通過一個實際例子,展示如何使用Profile來配置不同環(huán)境的消息隊列連接:
@Configuration
public class MessagingConfig {
@Bean
@Profile("local")
public ConnectionFactory localConnectionFactory() {
// 本地開發(fā)使用內(nèi)嵌的ActiveMQ
return new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
}
@Bean
@Profile("dev")
public ConnectionFactory devConnectionFactory() {
// 開發(fā)環(huán)境使用開發(fā)服務(wù)器上的RabbitMQ
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("dev-rabbitmq.example.com");
connectionFactory.setUsername("dev_user");
connectionFactory.setPassword("dev_pass");
return connectionFactory;
}
@Bean
@Profile("prod")
public ConnectionFactory prodConnectionFactory() {
// 生產(chǎn)環(huán)境使用生產(chǎn)級RabbitMQ集群
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses("prod-rabbitmq-1.example.com,prod-rabbitmq-2.example.com");
connectionFactory.setUsername("prod_user");
connectionFactory.setPassword("prod_pass");
// 生產(chǎn)環(huán)境增加額外配置
connectionFactory.setPublisherConfirms(true);
connectionFactory.setPublisherReturns(true);
return connectionFactory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
// 通用的RabbitTemplate配置,使用當(dāng)前Profile對應(yīng)的ConnectionFactory
return new RabbitTemplate(connectionFactory);
}
}
結(jié)合特定環(huán)境的配置文件:
# application-local.yml
spring:
rabbitmq:
listener:
simple:
concurrency: 1
max-concurrency: 5
# application-dev.yml
spring:
rabbitmq:
listener:
simple:
concurrency: 5
max-concurrency: 10
# application-prod.yml
spring:
rabbitmq:
listener:
simple:
concurrency: 10
max-concurrency: 50
retry:
enabled: true
initial-interval: 5000
max-attempts: 3
6. 優(yōu)缺點分析
優(yōu)點:
- 使用簡單直觀,專門為環(huán)境區(qū)分設(shè)計
- 與Spring Boot配置系統(tǒng)完美集成
- 支持組合和否定表達(dá)式,增強表達(dá)能力
- 可以通過多種方式切換Profile,適應(yīng)不同部署場景
缺點:
- 表達(dá)能力有限,不如
@Conditional注解靈活 - 主要基于預(yù)定義的命名環(huán)境,處理動態(tài)條件能力較弱
三、自動配置條件
1. 基本原理
自動配置是Spring Boot的核心特性之一,它允許框架根據(jù)類路徑、已有Bean和配置屬性等條件,自動配置應(yīng)用程序。自動配置條件是實現(xiàn)這一功能的基礎(chǔ),它通過組合使用多種條件注解,實現(xiàn)復(fù)雜的條件判斷邏輯。
2. 常用自動配置條件組合
在Spring Boot的自動配置類中,經(jīng)??梢钥吹蕉喾N條件注解的組合使用:
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "enabled", matchIfMissing = true)
public class DataSourceAutoConfiguration {
// 數(shù)據(jù)源自動配置...
}
上面的配置表示:
- 只有當(dāng)類路徑中存在
DataSource類 - 且容器中沒有
DataSource類型的Bean - 且
spring.datasource.enabled屬性不存在或為true時 - 才會啟用這個自動配置類
3. 自定義自動配置類
開發(fā)者可以創(chuàng)建自己的自動配置類,使用條件注解控制其激活條件:
@Configuration
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@ConditionalOnProperty(prefix = "mycache", name = "type", havingValue = "redis")
@EnableConfigurationProperties(MyCacheProperties.class)
public class RedisCacheAutoConfiguration {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
MyCacheProperties properties) {
RedisCacheManager.RedisCacheManagerBuilder builder =
RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);
if (properties.getExpireTime() > 0) {
builder.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(properties.getExpireTime())));
}
return builder.build();
}
}
配置屬性類:
@ConfigurationProperties(prefix = "mycache")
public class MyCacheProperties {
private String type;
private int expireTime = 3600;
// getters and setters
}
4. 啟用自動配置
要啟用自定義的自動配置類,需要創(chuàng)建META-INF/spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.config.RedisCacheAutoConfiguration
或者在Spring Boot 2.7及以上版本,可以使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件:
com.example.config.RedisCacheAutoConfiguration
5. 自動配置順序控制
在復(fù)雜系統(tǒng)中,可能需要控制自動配置類的加載順序,這可以通過@AutoConfigureBefore、@AutoConfigureAfter和@AutoConfigureOrder注解實現(xiàn):
@Configuration
@ConditionalOnClass(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class JdbcTemplateAutoConfiguration {
// JDBC模板自動配置,確保在數(shù)據(jù)源配置之后
}
@Configuration
@ConditionalOnClass(SecurityFilterChain.class)
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
public class SecurityAutoConfiguration {
// 安全配置應(yīng)該在Web MVC配置之前
}
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class EarlyInitAutoConfiguration {
// 需要最先初始化的配置
}
6. 實戰(zhàn)示例:自定義監(jiān)控系統(tǒng)自動配置
下面通過一個實際例子,展示如何使用自動配置條件創(chuàng)建一個可插拔的應(yīng)用監(jiān)控組件:
// 配置屬性類
@ConfigurationProperties(prefix = "app.monitoring")
public class MonitoringProperties {
private boolean enabled = true;
private String type = "jmx";
private int sampleRate = 10;
private boolean logMetrics = false;
// getters and setters
}
// JMX監(jiān)控自動配置
@Configuration
@ConditionalOnProperty(prefix = "app.monitoring", name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(prefix = "app.monitoring", name = "type", havingValue = "jmx", matchIfMissing = true)
@ConditionalOnClass(name = "javax.management.MBeanServer")
@EnableConfigurationProperties(MonitoringProperties.class)
public class JmxMonitoringAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MetricsCollector metricsCollector(MonitoringProperties properties) {
JmxMetricsCollector collector = new JmxMetricsCollector();
collector.setSampleRate(properties.getSampleRate());
return collector;
}
@Bean
@ConditionalOnMissingBean
public MetricsExporter metricsExporter(MonitoringProperties properties) {
JmxMetricsExporter exporter = new JmxMetricsExporter();
exporter.setLogMetrics(properties.isLogMetrics());
return exporter;
}
}
// Prometheus監(jiān)控自動配置
@Configuration
@ConditionalOnProperty(prefix = "app.monitoring", name = "enabled", havingValue = "true")
@ConditionalOnProperty(prefix = "app.monitoring", name = "type", havingValue = "prometheus")
@ConditionalOnClass(name = "io.prometheus.client.CollectorRegistry")
@EnableConfigurationProperties(MonitoringProperties.class)
public class PrometheusMonitoringAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MetricsCollector metricsCollector(MonitoringProperties properties) {
PrometheusMetricsCollector collector = new PrometheusMetricsCollector();
collector.setSampleRate(properties.getSampleRate());
return collector;
}
@Bean
@ConditionalOnMissingBean
public MetricsExporter metricsExporter(MonitoringProperties properties) {
PrometheusMetricsExporter exporter = new PrometheusMetricsExporter();
exporter.setLogMetrics(properties.isLogMetrics());
return exporter;
}
@Bean
public CollectorRegistry collectorRegistry() {
return new CollectorRegistry(true);
}
@Bean
public HttpHandler prometheusEndpoint(CollectorRegistry registry) {
return new PrometheusHttpHandler(registry);
}
}
// 日志監(jiān)控自動配置
@Configuration
@ConditionalOnProperty(prefix = "app.monitoring", name = "enabled", havingValue = "true")
@ConditionalOnProperty(prefix = "app.monitoring", name = "type", havingValue = "log")
@EnableConfigurationProperties(MonitoringProperties.class)
public class LogMonitoringAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MetricsCollector metricsCollector(MonitoringProperties properties) {
LogMetricsCollector collector = new LogMetricsCollector();
collector.setSampleRate(properties.getSampleRate());
return collector;
}
@Bean
@ConditionalOnMissingBean
public MetricsExporter metricsExporter() {
return new LogMetricsExporter();
}
}
META-INF/spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.monitoring.JmxMonitoringAutoConfiguration,\ com.example.monitoring.PrometheusMonitoringAutoConfiguration,\ com.example.monitoring.LogMonitoringAutoConfiguration
使用示例:
# 使用JMX監(jiān)控(默認(rèn))
app:
monitoring:
enabled: true
type: jmx
sample-rate: 5
log-metrics: true
# 或使用Prometheus監(jiān)控
app:
monitoring:
enabled: true
type: prometheus
sample-rate: 10
# 或使用日志監(jiān)控
app:
monitoring:
enabled: true
type: log
sample-rate: 30
# 或完全禁用監(jiān)控
app:
monitoring:
enabled: false
7. 優(yōu)缺點分析
優(yōu)點:
- 實現(xiàn)真正的"約定優(yōu)于配置"原則
- 可以創(chuàng)建可插拔的組件,極大提高代碼復(fù)用性
- 與Spring Boot生態(tài)系統(tǒng)無縫集成
缺點:
- 學(xué)習(xí)曲線陡峭,需要了解多種條件注解的組合使用
- 自動配置類過多可能導(dǎo)致應(yīng)用啟動時間增加
- 調(diào)試?yán)щy,排查問題需要深入了解Spring Boot啟動機制
八、總結(jié)
| 條件裝配技術(shù) | 核心特點 | 主要應(yīng)用場景 | 復(fù)雜度 |
|---|---|---|---|
| @Conditional及派生注解 | 最靈活,支持自定義條件 | 需要復(fù)雜條件判斷的場景 | 中 |
| Profile條件配置 | 專注于環(huán)境區(qū)分 | 多環(huán)境部署,環(huán)境特定配置 | 低 |
| 自動配置條件 | 組合多種條件,實現(xiàn)自動配置 | 可插拔組件,框架開發(fā) | 高 |
通過合理利用Spring Boot提供的條件裝配技術(shù),開發(fā)者可以構(gòu)建出靈活、可配置的應(yīng)用程序,滿足不同環(huán)境和業(yè)務(wù)場景的需求。
到此這篇關(guān)于SpringBoot中四種常用的條件裝配技術(shù)詳解的文章就介紹到這了,更多相關(guān)SpringBoot條件裝配技術(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Quartz+Spring Boot實現(xiàn)動態(tài)管理定時任務(wù)
最近做項目遇到動態(tài)管理定時任務(wù)的需求,剛拿到這個需求還真不知道從哪下手,經(jīng)過一番思考,終于找出實現(xiàn)思路,接下來通過本文給大家介紹了Quartz+Spring Boot實現(xiàn)動態(tài)管理定時任務(wù)的相關(guān)知識,需要的朋友可以參考下2018-09-09
如何使用Spring+redis實現(xiàn)對session的分布式管理
本篇文章主要介紹了如何使用Spring+redis實現(xiàn)對session的分布式管理,本文主要是在Spring中實現(xiàn)分布式session,采用redis對session進(jìn)行持久化管理,感興趣的小伙伴們可以參考一下2018-06-06
java策略枚舉:消除在項目里大批量使用if-else的優(yōu)雅姿勢
這篇文章主要給大家介紹了關(guān)于Java徹底消滅if-else的8種方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2021-06-06

