如何将 Picocli 解析的参数注入到 Spring Bean 定义中?

Eri*_* B. 5 spring-boot picocli

我正在尝试将Picocli与 Spring Boot 2.2 结合使用,将命令行参数传递给 Spring Bean,但不确定如何构造它。例如,我有以下@Command命令从命令行指定连接用户名和密码,但是我想使用这些参数来定义 Bean:

@Component
@CommandLine.Command
public class ClearJdoCommand extends HelpAwarePicocliCommand {
    @CommandLine.Option(names={"-u", "--username"}, description = "Username to connect to MQ")
    String username;

    @CommandLine.Option(names={"-p", "--password"}, description = "Password to connect to MQ")
    String password;

    @Autowired
    JMSMessagePublisherBean jmsMessagePublisher;

    @Override
    public void run() {
        super.run();
        jmsMessagePublisher.publishMessage( "Test Message");
    }
}


@Configuration
public class Config {
    @Bean
    public InitialContext getJndiContext() throws NamingException {
        // Set up the namingContext for the JNDI lookup
        final Properties env = new Properties();
        env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
        env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
        env.put(Context.SECURITY_PRINCIPAL, username);
        env.put(Context.SECURITY_CREDENTIALS, password);
        return new InitialContext(env);
    }

    @Bean
    public JMSPublisherBean getJmsPublisher(InitialContext ctx){
        return new JMSPublisherBean(ctx);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在这里陷入了一个循环。我需要命令行用户名/密码来实例化我的 JMSPublisherBean,但这些仅在运行时可用,在启动时不可用。

我已经设法通过使用延迟初始化、将ClearJdoCommandbean 注入到配置 bean 中并从 Spring 上下文中检索 JMSPublisherBean 来解决这个问题run(),但这似乎是一个丑陋的黑客行为。此外,它迫使我所有的豆子变得懒惰,这不是我的偏好。

是否有另一种/更好的方法来实现这一目标?

Rem*_*pma 1

一个可能有用的想法是分两遍解析命令行:

  1. 第一遍只是获取配置/初始化所需的信息
  2. 在第二遍中,我们选择其他选项并执行应用程序

为了实现这一点,我将创建一个单独的类来“复制”配置所需的选项。这个类将有一个@Unmatched用于剩余参数的字段,因此它们会被 picocli 忽略。例如:

class Security {
    @Option(names={"-u", "--username"})
    static String username;

    @Option(names={"-p", "--password"}, interactive = true, arity = "0..1")
    static String password;

    @Unmatched List<String> ignored;
}
Run Code Online (Sandbox Code Playgroud)

在第一遍中,我们只想提取用户名/密码信息,我们还不想执行应用程序。我们可以使用CommandLine.parseArgsorCommandLine.populateCommand方法来实现这一点。

所以,我们的main方法可以看起来像这样:

public static void main(String[] args) throws Exception {

  // use either populateCommand or parseArgs
  Security security = CommandLine.populateCommand(new Security(), args); 
  if (security.username == null || security.password == null) {
      System.err.println("Missing required user name or password");
      new CommandLine(new ClearJdoCommand()).usage(System.err);
      System.exit(CommandLine.ExitCode.USAGE);
  }

  // remainder of your normal main method here, something like this?
  System.exit(SpringApplication.exit(SpringApplication.run(MySpringApp.class, args)));
}
Run Code Online (Sandbox Code Playgroud)

我仍然会在类中保留(复制)使用情况和密码选项ClearJdoCommand,以便应用程序可以在需要时打印一条不错的使用帮助消息。

Security请注意,我在类中创建了字段static。这是一种解决方法(hack?),它允许我们将信息传递给该getJndiContext方法。

@Bean
public InitialContext getJndiContext() throws NamingException {
    // Set up the namingContext for the JNDI lookup
    final Properties env = new Properties();
    env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
    env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
    env.put(Context.SECURITY_PRINCIPAL, Security.username); // use info from 1st pass
    env.put(Context.SECURITY_CREDENTIALS, Security.password);
    return new InitialContext(env);
}
Run Code Online (Sandbox Code Playgroud)

可能有更好的方法将信息传递给此方法。有 Spring 专家愿意介入并向我们展示更好的选择吗?