You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单体Spring Boot项目支持Web与多CLI进程的架构实现咨询

单模块Spring Boot多进程实现方案(符合Spring最佳实践)

针对你的需求,以下是一套基于Spring Boot条件化配置的单模块实现方案,既能支持Web服务启动,也能独立运行多个CLI进程,同时避免多模块带来的复杂度,适配小团队快速交付MVP的场景。

核心思路

通过主类分发逻辑+条件化配置实现不同进程的启动:

  1. 主类解析命令行参数(如--mode),决定启动Web环境还是CLI环境
  2. 用Spring的@ConditionalOnProperty注解控制不同进程的组件、配置是否加载
  3. CLI进程通过CommandLineRunner实现业务逻辑,仅在对应模式下实例化执行

具体实现步骤

1. 主类:命令行参数解析与环境配置

主类作为唯一入口,根据--mode参数设置Web环境类型,并传递参数给Spring上下文,触发条件化配置:

@SpringBootApplication
public class MultiModeApplication {

    public static void main(String[] args) {
        String mode = extractCommandLineArg(args, "mode");
        if (mode == null) {
            System.err.println("必须指定运行模式:--mode=web | --mode=ingestion | --mode=analysis");
            System.exit(1);
        }

        SpringApplication app = new SpringApplication(MultiModeApplication.class);
        // 非Web模式下关闭Web服务器
        if (!"web".equals(mode)) {
            app.setWebApplicationType(WebApplicationType.NONE);
        }
        // 将mode存入环境变量,供条件注解识别
        app.setDefaultProperties(Map.of("app.run.mode", mode));
        app.run(args);
    }

    // 通用参数提取方法
    private static String extractCommandLineArg(String[] args, String argName) {
        for (String arg : args) {
            String prefix = "--" + argName + "=";
            if (arg.startsWith(prefix)) {
                return arg.substring(prefix.length());
            }
        }
        return null;
    }
}

2. Web服务组件:条件化加载

Web相关的控制器、配置仅在mode=web时加载:

@RestController
@ConditionalOnProperty(name = "app.run.mode", havingValue = "web")
@RequestMapping("/reports")
public class ReportController {

    private final ReportService reportService;

    public ReportController(ReportService reportService) {
        this.reportService = reportService;
    }

    @GetMapping
    public List<ReportDto> generateReport() {
        return reportService.generateFromMysql();
    }
}

// Web专属配置示例(可选)
@Configuration
@ConditionalOnProperty(name = "app.run.mode", havingValue = "web")
public class WebConfig {
    // 配置Web相关Bean,如拦截器、跨域规则等
}

3. CLI进程实现:独立逻辑与条件化执行

每个CLI进程对应一个CommandLineRunner,仅在匹配的mode下执行:

数据采集进程(Ingestion)

@Component
@ConditionalOnProperty(name = "app.run.mode", havingValue = "ingestion")
public class IngestionProcess implements CommandLineRunner {

    private final PulsarProducer pulsarProducer;
    private final ExternalDataSource externalDataSource;

    public IngestionProcess(PulsarProducer pulsarProducer, ExternalDataSource externalDataSource) {
        this.pulsarProducer = pulsarProducer;
        this.externalDataSource = externalDataSource;
    }

    @Override
    public void run(String... args) throws Exception {
        // 拉取外部数据
        List<DataRecord> data = externalDataSource.pullBatch();
        // 发送到Pulsar队列
        pulsarProducer.sendBatch("data-ingestion-topic", data);
        // 任务完成后主动退出进程
        System.exit(0);
    }
}

数据分析进程(Analysis)

@Component
@ConditionalOnProperty(name = "app.run.mode", havingValue = "analysis")
public class AnalysisProcess implements CommandLineRunner {

    private final PulsarConsumer pulsarConsumer;
    private final MysqlRepository mysqlRepository;

    public AnalysisProcess(PulsarConsumer pulsarConsumer, MysqlRepository mysqlRepository) {
        this.pulsarConsumer = pulsarConsumer;
        this.mysqlRepository = mysqlRepository;
    }

    @Override
    public void run(String... args) throws Exception {
        // 消费Pulsar数据并处理
        pulsarConsumer.consume("data-ingestion-topic", record -> {
            ProcessedData processed = analyzeData(record);
            mysqlRepository.save(processed);
        });
        // 持续消费,无需主动退出(如需批量处理可自行控制)
    }

    private ProcessedData analyzeData(DataRecord record) {
        // 数据分析逻辑
        return new ProcessedData();
    }
}

4. 多采集进程扩展方案

针对你提到的20-30个轻量级采集进程,无需为每个进程写独立的CommandLineRunner,可以通过通用采集器+数据源参数实现:

// 定义采集器接口
public interface DataSourceCollector {
    List<DataRecord> pullData();
    String getSourceType();
}

// MySQL采集实现
@Component
public class MysqlCollector implements DataSourceCollector {
    @Override
    public List<DataRecord> pullData() { /* MySQL拉取逻辑 */ }
    @Override
    public String getSourceType() { return "mysql"; }
}

// Kafka采集实现
@Component
public class KafkaCollector implements DataSourceCollector {
    @Override
    public List<DataRecord> pullData() { /* Kafka拉取逻辑 */ }
    @Override
    public String getSourceType() { return "kafka"; }
}

// 通用采集进程
@Component
@ConditionalOnProperty(name = "app.run.mode", havingValue = "ingestion")
public class GenericIngestionProcess implements CommandLineRunner {

    private final List<DataSourceCollector> collectors;
    private final PulsarProducer pulsarProducer;

    public GenericIngestionProcess(List<DataSourceCollector> collectors, PulsarProducer pulsarProducer) {
        this.collectors = collectors;
        this.pulsarProducer = pulsarProducer;
    }

    @Override
    public void run(String... args) throws Exception {
        String source = extractCommandLineArg(args, "source");
        if (source == null) {
            System.err.println("必须指定数据源:--source=mysql | --source=kafka | ...");
            System.exit(1);
        }

        DataSourceCollector collector = collectors.stream()
                .filter(c -> source.equals(c.getSourceType()))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("未知数据源:" + source));

        List<DataRecord> data = collector.pullData();
        pulsarProducer.sendBatch("data-ingestion-topic", data);
        System.exit(0);
    }
}

5. Spring Modulith的角色

Spring Modulith主要用于单模块内的代码边界划分,它不直接控制进程启动,但可以配合上述方案优化代码结构:

  • 将Web、Ingestion、Analysis逻辑分别放在com.xxx.app.web、com.xxx.app.ingestion、com.xxx.app.analysis包下
  • 用@Module标记每个子模块,通过Modulith工具检查模块间依赖合理性
  • 进程启动逻辑仍由主类的--mode参数控制,Modulith仅帮助保持代码整洁,不增加部署复杂度

启动命令示例

# 启动Web服务(8080端口)
java -jar your-app.jar --mode=web

# 启动MySQL数据采集进程
java -jar your-app.jar --mode=ingestion --source=mysql

# 启动数据分析进程
java -jar your-app.jar --mode=analysis

方案优势

  • 单模块部署,避免多模块的构建、维护复杂度
  • 完全基于Spring条件化配置最佳实践,符合Spring生态规范
  • 灵活扩展CLI进程,新增采集器仅需实现DataSourceCollector接口
  • 代码结构清晰,小团队易上手、易维护,适配MVP快速交付

内容的提问来源于stack exchange,提问作者Nawab

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 16:40:39