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=\
|
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