我试图在Spring Boot 2.2中使用Picocli来将命令行参数传递给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);
}
}
我在这里陷入了一个循环。我需要命令行用户名/密码来实例化我的JMSPublisherBean,但是这些仅在运行时可用,而在启动时不可用。
我已经通过使用惰性初始化,将ClearJdoCommand
bean注入Configuration bean并从Spring上下文中的run()
中检索JMSPublisherBean来解决了这个问题,但这看起来很丑陋骇客。此外,它迫使我所有的豆子都变得懒惰,这不是我的喜好。
还有另一种/更好的方法来实现这一目标吗?
答案 0 :(得分:1)
一个可能有用的想法是分两遍解析命令行:
为了实现这一点,我将创建一个单独的类来“复制”配置所需的选项。此类将具有用于剩余参数的 @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;
}
在第一遍中,我们只想提取用户名/密码信息,我们还不想执行应用程序。为此,我们可以使用 CommandLine.parseArgs
或 CommandLine.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)));
}
我仍会保留(复制)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);
}
可能有更好的方法将信息传递给此方法。 是否有任何 Spring 专家愿意加入并向我们展示更好的替代方案?
答案 1 :(得分:1)
第二个选项可能是使用纯 PicoCli(不是 PicoCli spring boot starter)并让它运行命令;命令不会是 Spring bean,只会用于验证参数。
在它的 call
方法中,Command
将创建 SpringApplication
,用属性填充它(通过 setDefaultProperties
或使用 JVM System.setProperty
- 不同之处在于环境变量将覆盖默认属性,而系统属性具有更高的优先级)。
@Override
public Integer call() {
var application = new SpringApplication(MySpringConfiguration.class);
application.setBannerMode(Mode.OFF);
System.setProperty("my.property.first", propertyFirst);
System.setProperty("my.property.second", propertySecond);
try (var context = application.run()) {
var myBean = context.getBean(MyBean.class);
myBean.run(propertyThird);
}
return 0;
}
这样,PicoCli 将验证输入、提供帮助等,但您可以控制 Spring Boot 应用程序的配置。您甚至可以为不同的命令使用不同的 Spring 配置。我相信这种方法比将所有属性传递给 Spring 容器中的 CommandLineRunner
更自然