在Spring Boot中根据环境控制CommandLineRunner执行

CommandLineRunner及任何spring的bean都可以根据不同的Spring环境(profile)决定是否执行业务逻辑。以下是CommandLineRunner场景下的几种实现方式:

1. 使用@Profile注解控制整个Runner

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("dev")  // 只在dev环境执行
public class DevCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("Dev环境初始化逻辑执行");
    }
}

@Component
@Profile("prod")  // 只在prod环境执行
public class ProdCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("生产环境初始化逻辑执行");
    }
}

2. 在run方法内判断环境

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class EnvironmentAwareRunner implements CommandLineRunner {
    private final Environment env;

    public EnvironmentAwareRunner(Environment env) {
        this.env = env;
    }

    @Override
    public void run(String... args) throws Exception {
        if (env.acceptsProfiles("dev")) {
            System.out.println("执行开发环境特定逻辑");
        } else if (env.acceptsProfiles("prod")) {
            System.out.println("执行生产环境特定逻辑");
        }
        
        // 所有环境都执行的公共逻辑
        System.out.println("公共初始化逻辑");
    }
}

3. 使用条件注解组合

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(
    value = "app.feature.enabled",
    havingValue = "true",
    matchIfMissing = false
)
public class ConditionalRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("仅在app.feature.enabled=true时执行");
    }
}

4. 针对RocketMQ消费者的环境控制实现

import org.apache.rocketmq.client.apis.consumer.PushConsumer;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("!test")  // 在非test环境执行
public class RocketMQConsumerRunner implements CommandLineRunner {
    private final PushConsumer pushConsumer;
    
    public RocketMQConsumerRunner(PushConsumer pushConsumer) {
        this.pushConsumer = pushConsumer;
    }
    
    @Override
    public void run(String... args) throws Exception {
        // 只有非测试环境才启动消费者
        System.out.println("RocketMQ消费者在非测试环境下启动");
    }
}

5. 动态决定是否执行的更复杂逻辑

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class SmartRunner implements CommandLineRunner {
    private final Environment env;
    
    public SmartRunner(Environment env) {
        this.env = env;
    }
    
    @Override
    public void run(String... args) throws Exception {
        boolean shouldRun = shouldExecuteBusinessLogic();
        if (shouldRun) {
            System.out.println("执行业务逻辑");
            // 实际业务代码...
        } else {
            System.out.println("跳过业务逻辑执行");
        }
    }
    
    private boolean shouldExecuteBusinessLogic() {
        // 复杂的环境判断逻辑
        return env.acceptsProfiles("dev", "staging") || 
               !env.containsProperty("disable.runner");
    }
}

配置示例

application-dev.yml中:

spring:
  profiles:
    active: dev
app:
  feature:
    enabled: true

application-prod.yml中:

spring:
  profiles:
    active: prod
app:
  feature:
    enabled: false

最佳实践建议

  1. 简单场景使用@Profile注解
  2. 复杂条件使用Environment判断或@Conditional系列注解
  3. 避免在Runner中做耗时操作,特别是生产环境
  4. 考虑使用ApplicationRunner替代CommandLineRunner,它提供了更丰富的ApplicationArguments参数处理
  5. 测试时可以使用@MockBean替换真实的Runner实现

通过以上方式,你可以灵活控制不同环境下CommandLineRunner的执行行为,满足各种初始化需求。