I'm building a Spring Boot application and need to read command line argument within method annotated with #Bean. See sample code:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public SomeService getSomeService() throws IOException {
return new SomeService(commandLineArgument);
}
}
How can I solve my issue?
#Bean
public SomeService getSomeService(
#Value("${cmdLineArgument}") String argumentValue) {
return new SomeService(argumentValue);
}
To execute use java -jar myCode.jar --cmdLineArgument=helloWorldValue
You can also inject ApplicationArguments directly to your bean definition method and access command line arguments from it:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public SomeService getSomeService(ApplicationArguments arguments) throws IOException {
String commandLineArgument = arguments.getSourceArgs()[0]; //access the arguments, perform the validation
return new SomeService(commandLineArgument);
}
}
try
#Bean
public SomeService getSomeService(#Value("${property.key}") String key) throws IOException {
return new SomeService(key);
}
If you run your app like this:
$ java -jar -Dmyproperty=blabla myapp.jar
or
$ gradle bootRun -Dmyproperty=blabla
Then you can access this way:
#Bean
public SomeService getSomeService() throws IOException {
return new SomeService(System.getProperty("myproperty"));
}
you can run your app like this:
$ java -server -Dmyproperty=blabla -jar myapp.jar
and can access the value of this system property in the code.
Related
I am trying to create a batch job using ApplicationRunner in my sprinbootApplication and I want to use the command line arguments as variables in my code.
So i want to extract the command line arguments, make beans from them and use them in my code. How to achieve it ?
#SpringBootApplication
public class MySbApp implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Myclass.class, args);
}
#Autowired
private Myclass myclass;
#Override
public void run(ApplicationArguments args) throws Exception {
String[] arguments = args.getSourceArgs();
for (String arg : arguments) {
System.out.println("HEYYYYYY" + arg);
}
Myclass.someMethod();
}
}
How do I create beans here ?
Assume:
// class ...main implements ApplicationRunner { ...
// GenericApplicationContext !!! (but plenty alternatives for/much tuning on this available):
#Autowired
private GenericApplicationContext context;
#Override
public void run(ApplicationArguments args) throws Exception {
String[] arguments = args.getSourceArgs();
for (String arg : arguments) {
System.out.println("HEYYYYYY" + arg);
// simple sample: register for each arg a `Object` bean, with `arg` as "bean id":
// ..registerBean(name, class, supplier)
context.registerBean(arg, Object.class, () -> new Object());
}
}
// ...
Then we can test, like:
package com.example.demo; // i.e. package of main class
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
// fake args:
#SpringBootTest(args = {"foo", "bar"})
public class SomeTest {
// the "testee" ((from) spring context/environment)
#Autowired
ApplicationContext ctxt;
#Test
public void somke() throws Exception {
// should contain/know the beans identified by:
Object foo = ctxt.getBean("foo");
assertNotNull(foo);
Object bar = ctxt.getBean("bar");
assertNotNull(bar);
}
}
or just like:
#Autowired
#Qualifier("foo")
Object fooBean;
#Autowired
#Qualifier("bar")
Object barBean;
#Test
public void somke2() throws Exception {
assertNotNull(fooBean);
assertNotNull(barBean);
}
Refs:
dynamic beans:
Add Bean Programmatically to Spring Web App Context (2010)
How to register bean dynamically in Spring Boot? (2019)
How to add bean programmatically to Spring context? (2017)
https://medium.com/#venkivenki4b6/spring-dynamically-register-beans-in-4-ways-at-run-time-c1dc45dcbeb9
https://www.baeldung.com/spring-5-functional-beans
GenericApplicationContext javadoc
testing:
How do you pass program arguments in a #SpringBootTest?
I have a problem while running spring boot spring shell application. I have implemented commandlinerunner but its never called during application startup.
#Component
public class ParamReader implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
if(args.length>0) {
System.out.println(Arrays.asList(args).stream().collect(Collectors.joining(", ")));
}
}
}
when i run exit command from shell, its calling the run method and then program terminates. I tried implementing commandlinerunner on Spring application main class but the result is same.
Spring shell uses an ApplicationRunner with #Order annotation and precedence 0.
You could try the following:
#Order(-1)
#Component
public class ParamReader implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
if(args.length>0) {
System.out.println(Arrays.asList(args).stream().collect(Collectors.joining(", ")));
}
}
}
I have SpringBootApplication that takes args
public class RocksdbBootApp {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(RocksdbBootApp.class);
app.setDefaultProperties(Collections.singletonMap("server.port", args[0]));
app.run(args);
}
}
How can I get access to args in RestController?
#Slf4j
#RestController
#RequestMapping("/api/rocks")
public class RocksApi {
public RocksApi(KeyValueRepository<String, String> rocksDB) {
System.out.println(args[0]);
}
}
To access arguments passed to your application you can make use of ApplicationArguments and inject it into your controller :
#Slf4j
#RestController
#RequestMapping("/api/rocks")
public class RocksApi {
private final ApplicationArguments arguments;
#Autowired //can be ommited in new versions of Spring
public RocksApi(final ApplicationArguments arguments) {
this.arguments = arguments;
}
public RocksApi(KeyValueRepository<String, String> rocksDB) {
String[] args = arguments.getSourceArgs();
System.out.println(args[0]);
}
}
afaik, you're setting an application property, and can acccess that like any other Spring property:
#Slf4j
#RestController
#RequestMapping("/api/rocks")
public class RocksApi {
#Value("${server.port}")
private Integer serverPort;
For example:
java -jar mySpringApplication --myJsonParameter="{\"myKey\":\"myValue\"}"
This should be resolved like that:
public class MyService {
#Autowired
//or #Value("myJsonParameter") ?
private MyInputDto myInputDto;
}
public class MyInputDto {
private String myKey;
}
The idea is to pass named parameter from command line (and following spring externalization practics) but inject Typed value parsed from json, not string.
You can try using property spring.application.json and annotate your MyInputDto as #org.springframework.boot.context.properties.ConfigurationProperties.
Start your application like this:
java -Dspring.application.json='{"myKey":"myValue"}' -jar mySpringApplication.jar
Implementing your service:
#EnableConfigurationProperties(MyInputDto.class)
public class MyService {
#Autowired
private MyInputDto myInputDto;
// use myInputDto.getMyKey();
}
#ConfigurationProperties
public class MyInputDto {
private String myKey;
public String getMyKey() { return this.myKey; }
public void setMyKey(String myKey) { this.myKey = myKey; }
}
See Spring external configuration documentation for more information.
You can implement CommandLineRunner to your springboot main class and then prepare Bean like this:
#Bean
public MyInputDto prepareMyInputDto(){
return new MyInputDto();
}
Then in your run method you can set values from command line argument.
#Override
public void run(String... args) throws Exception {
MyInputDto bean = context.getBean(MyInputDto.class);
bean.setMyKey(args[0]);
}
I already managed to start Spring Shell using Spring Boot:
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
All my #ShellComponent classes are detected and I can use the shell as expected.
Now I would like to run the shell without Spring Boot, I expect it to look something like this
Shell shell = context.getBean(Shell.class);
shell.run(...);
What approach should I take to configure all required dependencies myself?
Thanks in advance!
By extracting the necessary parts of ebottard's link (Thank you!) I finally managed to run the shell like I wanted:
#Configuration
#Import({
SpringShellAutoConfiguration.class,
JLineShellAutoConfiguration.class,
JCommanderParameterResolverAutoConfiguration.class,
StandardAPIAutoConfiguration.class,
StandardCommandsAutoConfiguration.class,
})
public class SpringShell {
public static void main(String[] args) throws IOException {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringShell.class);
Shell shell = context.getBean(Shell.class);
shell.run(context.getBean(InputProvider.class));
}
#Bean
#Autowired
public InputProvider inputProvider(LineReader lineReader, PromptProvider promptProvider) {
return new InteractiveShellApplicationRunner.JLineInputProvider(lineReader, promptProvider);
}
}
See this example that shows how to wire up everything without relying on Autoconfiguration.
without Spring Boot I write this:
#Configuration
#ComponentScan(value = {"path.to.commands", "org.springframework.shell.commands", "org.springframework.shell.converters", "org.springframework.shell.plugin.support"})
public class TestShell {
private static String[] args;
#Bean("commandLine")
public CommandLine getCommandLine() throws IOException {
return SimpleShellCommandLineOptions.parseCommandLine(args);
}
#Bean("shell")
public JLineShellComponent getShell() {
return new JLineShellComponent();
}
public static void main(String[] args) {
TestShell.args = args;
System.setProperty("jline.terminal", "none");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TestShell.class);
ctx.registerShutdownHook();
JLineShellComponent shell = ctx.getBean(JLineShellComponent.class);
shell.start();
shell.waitForComplete();
ExitShellRequest exitShellRequest = shell.getExitShellRequest();
if (exitShellRequest == null)
exitShellRequest = ExitShellRequest.NORMAL_EXIT;
System.exit(exitShellRequest.getExitCode());
}
}
and command class:
#Component
public class Hello implements CommandMarker {
#CliCommand(value="hi", help = "say hello.")
public String hi() {
return "hello";
}
}
see org.springframework.shell.Bootstrap.