Passing constructor args for Spring CommandLineRunner - spring

I have an application that uses a class myClass that implements Spring CommanLineRunner:
#SpringBootApplication
#ComponentScan({ . . . } )
public class myClass implements CommandLineRunner {
. . .
public myClass() { . . . }
public myClass( String anArg ) { . . . }
public static void main(String[] args) {
SpringApplication application = new SpringApplication( myClass.class );
application.setWebEnvironment( false );
application.run( args );
}
#Override
public void run(String... args) {
. . .
}
}
this in the run method is bound to an instance of myClass that Spring constructs automagically. My problem is that for this constructing, I want to use the non-default constructor that takes an argument
and I want to pass to that argument one of the command line args.
How do I tell Spring to use the non-default constructor, and how do I supply a value to the constructor argument anArg?

In order to be able to pass other instances to CommandLineRunner it is recommended to separate your application class from the CommandLineRunner interface. So, your main class would look like this:
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication .class, args);
}
In the other hand we should instanciate the CommandLineRunner as a component:
#Component
public class MyCommandLineRunner implements CommandLineRunner {
private MyOtherComponent otherComponent
#Autowired
public MyCommandLineRunner(MyOtherComponent otherComponent) {
this.otherComponent = otherComponent;
}
public void run(String... args) {
otherComponent.setArg(arg[0]);
}
}
A more convenient way is to create a bean by passing a lambda function to it:
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Bean
CommandLineRunner initialize(MyOtherComponent otherComponent) {
return args -> {
otherComponent.setArg(arg[0]);
};
}
}

Related

Picocli Spring boot CLI Application

I am trying to create a spring boot CLI App using picocli. I followed the steps as mentioned in the tutorial, but when I start the service the whole flow runs.
What I want is to call the command from the terminal then only the flow should trigger.Can anyone please help me resolving this.
Below is my code.
Component class
public class AppCLI implements CommandLineRunner {
#Autowired
AppCLI appCLI;
public String hello(){
return "hello";
}
#CommandLine.Command(name = "command")
public void command() {
System.out.println("Adding some files to the staging area");
}
#Override
public void run(String... args) throws Exception {
CommandLine commandLine = new CommandLine(appCLI);
commandLine.parseWithHandler(new CommandLine.RunLast(), args);
System.out.println("In the main method");
hello();
command();
}
}
Command class
#Controller
#CommandLine.Command(name = "xyz",description = "Performs ", mixinStandardHelpOptions = true, version = "1.0")
public class AppCLI implements Runnable{
#Override
public void run() {
System.out.println("Hello");
}
}
Main Class
#SpringBootApplication
public class Application {
private static Logger LOG = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Thanks in advance.
If you want to add command line parsing to Spring Boot's external
configuration in the #SpringBootApplication, do something like (see
Connect.java):
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine;
#SpringBootApplication
#Command
#NoArgsConstructor #ToString #Log4j2
public class Connect implements ApplicationRunner {
#Option(description = { "connection_file" }, names = { "-f" }, arity = "1")
#Value("${connection-file:#{null}}")
private String connection_file = null;
#Override
public void run(ApplicationArguments arguments) throws Exception {
new CommandLine(this)
.setCaseInsensitiveEnumValuesAllowed(true)
.parseArgs(arguments.getNonOptionArgs().toArray(new String [] { }));
/*
* Command implementation; command completes when this method
* completes.
*/
}
}
There is a similar example in
Install.java.

Read application.properties value from Spring boot main class

In my spring boot app, I have variable value set in application.properties.
I want to read it in the main java class. How can I do that? following code returns null.
#SpringBootApplication
public class MyApplication {
#Value( "${spring.shutdown.sleep.time}" )
private static String shutdownSleepTime;
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(MysApplication.class, args);
System.out.println("sleep time : " + shutdownSleepTime);
You can read the property from the applicationContext.
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(MysApplication.class, args);
String shutdownSleepTime = applicationContext.getEnvironment().getProperty("spring.shutdown.sleep.time");
}
I recommend it
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(MysApplication.class, args);
}
#Component
public static class CmdRunner implement CommandLineRunner {
#Value( "${spring.shutdown.sleep.time}" )
private static String shutdownSleepTime;
public void run(String... args) throws Exception {
System.out.println("sleep time : " + shutdownSleepTime);
}
}
So you want to print something when the application context is created.
For that You can use event listeners:
#SpringBootApplication
public class MyApplication {
#Value( "${spring.shutdown.sleep.time}" )
private String shutdownSleepTime; // not static
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext =
SpringApplication.run(MysApplication.class, args);
}
#EventListener
public void onAppContextStarted(ApplicationStartedEvent e) {
System.out.println("sleep time : " + shutdownSleepTime);
}
}

SpringBootTest - how to assert if context loading fails

I wrote an ApplicationListener that should check if the environment is prepared during context initialization. I'm having trouble testing the scenario since I'm adding the listener manually both in my configure() and main() methods.
ApplicationListener class:
public class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
//code that checks if conditions are met
if (checkTrue) {
throw new RuntimeException();
}
}
}
Main class:
public class MyApp extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
setRegisterErrorPageFilter(false);
return application.listeners(new EnvironmentPrepared()).sources(MyApp.class);
}
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MyApp.class);
springApplication.addListeners(new EnvironmentPrepared());
springApplication.run(args);
}
}
The test I want to execute:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(loader = OverriddenProfilesTest.CustomLoader.class)
public class OverriddenProfilesTest {
public static class CustomLoader extends SpringBootContextLoader {
#Override
protected SpringApplication getSpringApplication() {
SpringApplication app = super.getSpringApplication();
app.addListeners(new EnvironmentPrepared());
return app;
}
}
/**
* Checks if spring can bootstrap everything
*/
#Test(expected = RuntimeException.class)
public void test() {
}
}
This would be the test I want. A RuntimeException is thrown but the exception happens during context initialization so the test doesn't even start.
Here is the solution I used. I removed the manual adding of the listener to the application and used spring.factories file instead.
Regarding the test, I first created a custom runner class:
public class SpringRunnerWithExpectedExceptionRule extends SpringJUnit4ClassRunner {
public SpringRunnerWithExpectedExceptionRule(Class<?> clazz) throws InitializationError {
super(clazz);
}
#Override
protected Statement methodBlock(FrameworkMethod frameworkMethod) {
List<ExpectedException> testRules = getTestClass().getAnnotatedFieldValues(null, ExpectedExceptionClassRule.class, ExpectedException.class);
Statement result = super.methodBlock(frameworkMethod);
for (TestRule item : testRules) {
result = item.apply(result, getDescription());
}
return result;
}}
Then I create following annotation:
#Retention(RUNTIME)
#Target({ FIELD })
public #interface ExpectedExceptionClassRule {
}
And finally, I was able to run the test with my runner:
#RunWith(SpringRunnerWithExpectedExceptionRule.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OverriddenProfilesTest {
#ExpectedExceptionClassRule
public static ExpectedException expectedException = ExpectedException.none();
#BeforeClass
public static void before() {
expectedException.expectCause(runtimeExceptionMethod());
}
#Test
public void testThatShouldThrowExceptionWhileSettingContext {
}
static Matcher<Throwable> runtimeExceptionMethod() {
return new IsRuntimeException();
}
static class IsRuntimeException extends TypeSafeMatcher<Throwable> {
//do stuff
}
More on the solution can be found here.

How can I Spring-ify my stand-alone (non Web Service) application?

I have a working stand-alone application, which uses Postgres, with the following
rough structure:
class myClass {
public myClass( String filePath ) {...}
public void calculate( ...args... ) {...}
public static void main(String[] args) {
...process args...
new myClass(...).calculate(...)
}
}
I am trying to convert this to a Spring Boot application to take advantage of
Spring JDBC. Here's the rough structure, a modification of the above:
#SpringBootApplication
class myClass implements implements CommandLineRunner {
public myClass( String filePath ) {...}
public void calculate( ...args... ) {...}
public static void main(String[] args) {
SpringApplication.run( myClass.class, args);
}
#Override
public void run(String... args) throws Exception {
...process args...
new myClass(...).calculate(...)
}
}
When I try running this from Eclipse, I get the following error message:
Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
Why is this happening, what can I do to fix it? And, why is it even complaining about "WebApplication",
since I do not include anything having to do with Web or Controller in my build.gradle.
I was able to find a solution to the error above by slightly modifying how I structured my main method:
#SpringBootApplication
class myClass implements implements CommandLineRunner {
public myClass( String filePath ) {...}
public void calculate( ...args... ) {...}
public static void main(String[] args) {
SpringApplication application = new SpringApplication( myClass.class );
application.setWebEnvironment( false );
application.run( args );
}
#Override
public void run(String... args) throws Exception {
...process args...
this.calculate(...) <<<=== Note change here, too
}
}

Spring Boot application to read a value from properties file in main method

I am trying to get the value of a property
hello.world=Hello World
in MainApp class
#SpringBootApplication
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
This didn't work as its the main method.
#Value("${hello.world}")
public static String helloWorld;
Maybe its possible to load by
Properties prop = new Properties();
// load a properties file
prop.load(new FileInputStream(filePath));
Is there any other better way to get the properties using Spring in the main method of SpringBoot before SpringApplication.run
ConfigurableApplicationContext ctx =
SpringApplication.run(MainApp.class, args);
String str = ctx.getEnvironment().getProperty("some.prop");
System.out.println("=>>>> " + str);
You have declared the variable helloWorld as static. Hence you need to use Setter Injection and not Field Injection.
Injecting a static non-final field is a bad practice. Hence Spring doesn't allow it. But you can do a workaround like this.
public static String helloWorld;
#Value("${hello.world}")
public void setHelloWorld(String someStr) {
helloWorld = someStr
}
You can access this variable helloWorld at any point in the class, if its any other class. But if you want to do it in the main class. You can access the variable only after this line
SpringApplication.run(MainApp.class, args);)
i.e only after the application has started.
Don't do this. Its better to use CommandLineRunner.
Thanks to this you can have a non static method that Spring Boot will run for you automatically:
#SpringBootApplication
public class SimulatorApplication implements CommandLineRunner {
#Value("${my-value}")
private myValue;
public static void main(String[] args) {
SpringApplication.run(SimulatorApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
// here you can access my-value
}
}
#SpringBootApplication
public class MainApp {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MainApp.class);
springApplication.addListeners(new VersionLogger());
springApplication.run(args);
}
// The VersionLogger Class
public class VersionLogger implements ApplicationListener<ApplicationEnvironmentPreparedEvent>{
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEvent) {
String helloWorld = applicationEvent.getEnvironment().getProperty("hello.world");
}
}
ApplicationEnvironmentPreparedEvent
Event published when a SpringApplication is starting up and the Environment is first available for inspection and modification.
ApplicationContext applicationContext = SpringApplication.run(Application.class, args);
String applicationPropertyVersion=applicationContext.getEnvironment().getProperty("application.property.version");
LOGGER.info("RELEASE CODE VERSION {} and applicationProperty Version {} ", LcoBuildVersion.version,
applicationPropertyVersion);
We can't read values into static fields . Here is the explanation gives a better insight How to assign a value from application.properties to a static variable?

Resources