feat(mq-starter-core): 启动期校验 mq.type 有对应 MqDriver(③ fail-fast)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package com.njcn.mq.autoconfig;
|
||||
|
||||
import com.njcn.mq.spi.MqDriver;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* ③ 通用校验:显式配了 {@code mq.type},却没有装配任何 {@link MqDriver},
|
||||
* 说明忘引对应 starter 或 mq.type 拼错 —— 启动失败并给中文提示。
|
||||
*
|
||||
* <p>为何“有没有 MqDriver”就够:两个 driver 的 AutoConfiguration 都用
|
||||
* {@code @ConditionalOnProperty(name="mq.type", havingValue=...)} 按 mq.type 条件装配,
|
||||
* 因此“存在 MqDriver bean”已等价于“配的 mq.type 有对应 driver”。
|
||||
*
|
||||
* <p>仅当显式配了 {@code mq.type} 时装配;没配则完全不介入(存量零影响)。
|
||||
* 放在 core 是因为:redis/rocketmq 包没引入时其模块内的类自己都不加载,无法自证缺失;
|
||||
* 只有 core 有“到底有没有 MqDriver”的全局视角。
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = "mq.type")
|
||||
public class MqDriverPresenceAutoConfiguration implements SmartInitializingSingleton {
|
||||
|
||||
private final ObjectProvider<MqDriver> drivers;
|
||||
private final Environment env;
|
||||
|
||||
public MqDriverPresenceAutoConfiguration(ObjectProvider<MqDriver> drivers, Environment env) {
|
||||
this.drivers = drivers;
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
if (!MqStartupChecks.enabled(env)) {
|
||||
return;
|
||||
}
|
||||
String configured = env.getProperty("mq.type");
|
||||
// 用 stream() 而非 getIfAvailable():同时引多个 driver 包时不会抛 NoUniqueBeanDefinitionException
|
||||
if (drivers.stream().findAny().isPresent()) {
|
||||
return; // 有 driver 装配,放行
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"MQ 启动失败:已配置 mq.type=" + configured + ",但没有装配任何 MqDriver。\n"
|
||||
+ "请检查:\n"
|
||||
+ " 1) 是否引入对应 starter 依赖(redis-stream 需 mq-starter-redis-stream + spring-boot-starter-data-redis;\n"
|
||||
+ " rocketmq 需 mq-starter-rocketmq + rocketmq starter);\n"
|
||||
+ " 2) mq.type 是否拼写正确(支持:redis-stream / rocketmq)。");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.mq.autoconfig;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/** MQ 启动校验的公共工具:统一读取逃生开关 {@code mq.startup-check.enabled}(默认 true)。 */
|
||||
public final class MqStartupChecks {
|
||||
|
||||
private MqStartupChecks() {
|
||||
}
|
||||
|
||||
/** @return true 表示应执行启动校验;false 表示使用者主动关闭。 */
|
||||
public static boolean enabled(Environment env) {
|
||||
return Boolean.TRUE.equals(
|
||||
env.getProperty("mq.startup-check.enabled", Boolean.class, Boolean.TRUE));
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.njcn.mq.autoconfig.MqCoreAutoConfiguration
|
||||
com.njcn.mq.autoconfig.MqCoreAutoConfiguration,\
|
||||
com.njcn.mq.autoconfig.MqDriverPresenceAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.njcn.mq.autoconfig;
|
||||
|
||||
import com.njcn.mq.spi.MqDriver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class MqDriverPresenceAutoConfigurationTest {
|
||||
|
||||
private final ApplicationContextRunner runner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(MqDriverPresenceAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void noMqType_doesNotFail() {
|
||||
runner.run(ctx -> assertThat(ctx).hasNotFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mqTypeButNoDriver_fails() {
|
||||
runner.withPropertyValues("mq.type=redis-stream")
|
||||
.run(ctx -> {
|
||||
assertThat(ctx).hasFailed();
|
||||
assertThat(ctx).getFailure().hasMessageContaining("redis-stream");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void mqTypeWithDriver_ok() {
|
||||
runner.withPropertyValues("mq.type=redis-stream")
|
||||
.withBean("someDriver", MqDriver.class, () -> mock(MqDriver.class))
|
||||
.run(ctx -> assertThat(ctx).hasNotFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabled_skipsCheck() {
|
||||
runner.withPropertyValues("mq.type=redis-stream", "mq.startup-check.enabled=false")
|
||||
.run(ctx -> assertThat(ctx).hasNotFailed());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user