how to use the ApplicationArguments in spring-boot - spring-boot

I am learning the Spring-Boot(I am new to it), reading the Spring Boot Document. In the 23.6 Accessing application arguments, It talk about the ApplicationArguments, and the code is:
package com.example.project;
import org.springframework.boot.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import java.util.*;
#Component
public class MyBean {
#Autowired
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
System.out.println(debug);
System.out.println(files);
}
}
It says if run with "--debug logfile.txt" debug=true, files=["logfile.txt"].
But in my project, I don't know how to run it. I create the spring-boot using Maven: The Project Structure

In Spring Boot doc ApplicationArguments is autowired in a bean. Here is a more hands on example where it's used in a Main method.
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args).stop();
}
#Override
public void run(ApplicationArguments args) throws Exception {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
System.out.println(debug);
System.out.println(files);
}
}

Assuming that you have an Application class with annotation #SpringBootApplication like in the answer provided by a.b.d.
To be able to provide the arguments within IntelliJ IDEA environment you will need to first Run the main method and then Edit 'Run/Debug Configurations' and under Main Class fill Program arguments field with "--debug logfile.txt":

In one word like a thousand :
the 'Program arguments' in your IDE field prefixed by -- is simply the same name as the 'Option' expected in the 'ApplicationArguments'.
Hence you can match --debug and "args.containsOption("debug")".

Related

Error Messages as Key Value Pairs - from a Properties File in classpath - Spring boot 2.0

We are currently on a Spring Boot Version 1.x
We have Error Messages (Error Key -> Error Code) pairs in our error.properties file (this is in the class path).
We leveraged PropertiesConfigurationFactory to get these Error Key and Error Code pairs in to a POJO, this POJO had a Map
Hence very convenient to be used across our application to get an Error code for a given Error Key.
What is its equivalent in Spring Boot 2.x ?.
Assuming you have error.properties file with the below contents:
errors.error1=101
errors.error2=102
errors.error3=103
A simple spring boot app that demonstrates the injection of these properties :
package snmaddula.remittance;
import java.util.Map;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
#SpringBootApplication
#ConfigurationProperties
#PropertySource("classpath:error.properties")
public class DemoApplication {
private Map<String, Integer> errors;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public CommandLineRunner cli() {
return (args) -> {
System.out.println(errors); // you can print and see the error properties injected to this map.
};
}
public void setErrors(Map<String, Integer> errors) {
this.errors = errors;
}
}
With the use of #PropertySource and #ConfigurationProperties we can enable property injection provided we have a setter method for our attribute.
When you run this program, you can see the properties getting printed on to the console as I added a CommandLineRunner cli() {..} to show the working of it.
The working sample is available on GitHub.

Autowiring Issue with using Springboot PropertiesFactoryBean

I am new to Springboot PropertiesFactoryBean and want to inject a file from classpath so that I can populate it into a Map
Location of properties file on Eclipse: src/main/resources
contents of File: simple_property.properties:
key1=value1
key2=value2
key3=value3
My ApplicationConfiguration.java looks as below:
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
#Configuration
public class ApplicationConfiguration {
#Bean(name = "simpleMapping")
public static PropertiesFactoryBean artifactMapping() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("simple_property.properties"));
return bean;
}
}
I have a ApplicationRunner interface for bean to be run:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class pCli implements ApplicationRunner {
#Autowired
private SomeClass someProgram;
public static void main(String[] args) {
SpringApplication.run(pCli.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
someProgram.run(args.getOptionValues("param1").get(0));
}
}
I am unable to understand & proceed ahead how I can use bean to read all properties ? Example how I can get the properties into a variable of Map and access them ? (If #Autowired has already loaded the properties file from classpath then how can I access it ? )
Say you have your map in the property file like this,
app.number-map={KEY1:1, KEY2:2, KEY3:3}
You can use this value by injecting the property using the #Value annotation. Following is an example where the value is injected to a method. Spring expression #{${app.number-map}} will fetch the value in the properties file.
someMethod(#Value("#{${app.number-map}}")
Map<String, Integer> numberMap) {
// ...
}
Use application.properties since you're still learning. It'll make your life easy. Also, keeping a separate configuration bean would really help you to manage and access property values easily.

Two Spring Boot projects both with #SpringBootApplication

I have a data project and UI project. Both projects are Spring Boot applications. Both projects have the same root package (com.myorg) with a main class annotated with #SpringBootApplication.
Data project's main class is:
package com.myorg;
#SpringBootApplication
public class DataApplication {
public static void main(String[] args) {
SpringApplication.run(DataApplication.class, args);
}
}
The UI project's main class is:
package com.myorg;
#SpringBootApplication
public class UiApplication {
public static void main(String[] args) {
SpringApplication.run(UiApplication .class, args);
}
}
The UI project depends on the data project via the following Gradle dependency:
dependencies {
compile('com.myorg:data:1.0')
}
If I run the UI application, it runs without issue. However, if I run an integration test within the UI application such as follows:
package com.myorg
#RunWith(SpringRunner.class)
#SpringBootTest
public class UiIntTest {
#Test
public void contextLoads() {
}
}
The following initialization error occurs:
java.lang.IllegalStateException: Found multiple #SpringBootConfiguration annotated classes
In the data project's main class, if I replace #SpringBootApplication with
#Configuration
#EnableAutoConfiguration
#ComponentScan({ "com.myorg" })
I get the following initialization error when trying to run its integration tests:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
For example, if I try to run:
package com.myorg
#RunWith(SpringRunner.class)
#SpringBootTest
public class DataIntTest {
#Test
public void contextLoads() {
}
}
How can I properly configure the data and UI projects?
You need to specify which Spring Boot Main class to use along with #SpringBootTest:
#SpringBootTest(classes = YourUiSpringBootApp.class)
You shouldn't have two SpringApplication annotations in the same package.
Package one.
twoapps.one;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication(scanBasePackageClasses = {One.class})
#EnableAutoConfiguration
public class One extends SpringApplication {
}
Package two.
twoapps.two;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication(scanBasePackageClasses = {Two.class})
#EnableAutoConfiguration
public class Two extends SpringApplication {
}
Root package and launcher
package twoapps;
import org.springframework.boot.SpringApplication;
import twoapps.one.One;
import twoapps.two.Two;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Application {
public static void main(String[] args) throws Exception {
new Thread(() -> SpringApplication.run(One.class, args(args, "--spring.profiles.active=one"))).start();
new Thread(() -> SpringApplication.run(Two.class, args(args, "--spring.profiles.active=two"))).start();
}
private static String[] args(String[] args, String s) {
List<String> collect = Arrays.stream(args).collect(Collectors.toList());
collect.add(s);
String[] strings = collect.toArray(new String[]{});
return strings;
}
}
This is a terrible idea. please don't do it. It is much better to have two different projects and a common project.

Multiple ApplicationRunners on classpath, how to make SpringApplication.run() only run one

Context: I have a project with some utilities to do things like data fixing. Each utility is a Java application, i.e. class with main() method. I want to define them as Spring Boot applications so I can use the ApplicationRunner and ApplicationArguments facility. The Spring configuration is defined via annotations in a shared configuration class. I've put a minimal example of this setup below.
Expectation: if I call SpringApplication.run(SomeClass.class, args) where SomeClass is an ApplicationRunner, it runs the run() on that class and not on any other classes that may be in the app context.
What actually happens: it calls all ApplicationRunners that it has in the context.
Why? I understood SpringApplication.run(Class, String[]) to mean, "run this class" whereas it appears to mean "load an app context from this class and run anything you can find in it". How should I fix it to run only 1 class? I don't mind if my other application class isn't in the app context, because all the configuration I need is in the shared config class. But I don't want to have to edit code (e.g. add or remove annotations) according to which class I need to run.
Minimal example:
A Spring config class (shared):
package com.stackoverflow.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ExampleSpringConfig {
/** Some bean - just here to check that beans from this config are injected */
#Bean public FooService fooService () {
return new FooService();
}
}
Two application classes
package com.stackoverflow.example;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
#SpringBootApplication
public class SomethingJob implements ApplicationRunner {
#Resource private FooService fooService;
public void run(ApplicationArguments args) throws Exception {
System.out.println("Doing something"); // do things with FooService here
}
public static void main(String[] args) {
SpringApplication.run(SomethingJob.class, args);
}
}
and another that is identical except that it prints "Doing something else".
Output:
[Spring Boot startup logs...]
Doing something else
Doing something
[Spring Boot shutdown logs...]
Firstly, only one class should be annotated with #SpringBootApplication. As you've noticed in your answer, this defines the external "main" entry point. I would recommend this is a different class to your ApplicationRunner classes for clarity and conceptual separation.
To only have some but not all runners run, I've done this by parsing the arguments, and quickly exiting from the runner which should not be called. e.g.
package com.stackoverflow.example;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
#Component
public class SomethingJob implements ApplicationRunner {
#Resource private FooService fooService;
public void run(ApplicationArguments args) throws Exception {
if (!args.containsOption("something")) return
System.out.println("Doing something"); // do things with FooService here
}
}
That way you can do java -jar myjar.jar --something or java -jar myjar.jar --something-else depending which one you want to be run.
I found a workaround while experimenting with my minimal example.
#SpringBootApplication is just an alias for #ComponentScan, #EnableAutoConfiguration and #Configuration. By applying them separately, I discovered that it's the #Configuration annotation that causes this behaviour. If I only apply the other 2, I don't get the issue.
I guess this is because #Configuration means "I'm a configuration class, and any beans I define should be pulled into the context during component scan" and although this class doesn't define an ApplicationRunner, it is one, which has the same effect. Therefore if you have 2 such beans on the classpath, they both get pulled into the app context.
Without #Configuration, the bean you want to run still gets registered since it's referenced by the call to run(), but other ApplicationRunners on the classpath don't.
This fixes my immediate problem by making sure I only have one ApplicationRunner in my app context. But it doesn't answer the wider question, "If I do have several ApplicationRunners, how do I tell Spring Boot which one to run?" So I'd still appreciate any more complete answer or suggestions for a different approach.

Loading properties using #Value into a BeanFactory object using #Bean in Spring Boot

Can anyone help me with a Spring Boot problem?
I want to create a factory bean as part of my application context but I want to be able to instantiate it with injected property values. However it seems that Spring will load FactoryBeans before anything else as demonstrated here:
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
#EnableAutoConfiguration
public class TestClass
{
#Value("${test.value}")
String value;
#Bean
public Object test1()
{
System.out.println("test.value=" + value );
List<String> list = new ArrayList<String>();
ListFactoryBean factory = new ListFactoryBean();
factory.setSourceList(list);
return factory;
}
public static void main(String[] args)
{
SpringApplication.run(TestClass.class, args);
}
}
When run with
java -Dtest.value=HELLO -jar myTest.jar
It loads in the value correctly:
test.value=HELLO
However, when I specify that the bean to be loaded is in fact a factory bean, and run it in the same way:
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
#EnableAutoConfiguration
public class TestClass
{
#Value("${test.value}")
String value;
#Bean
public AbstractFactoryBean test1()
{
System.out.println("test.value=" + value );
List<String> list = new ArrayList<String>();
ListFactoryBean factory = new ListFactoryBean();
factory.setSourceList(list);
return factory;
}
public static void main(String[] args)
{
SpringApplication.run(TestClass.class, args);
}
}
The value is null because it hasn't been injected yet.
test.value=null
Is there any way around this?
Thanks
Spring often has to query bean definitions for the type of object they produce. Factory beans are always problematic because they can cause dependency cascades in a futile attempt to resolve all dynamic information available before asking for the type.
I think ListFactoryBean is insufficiently precise about its product type (getObjectType() can only return a non-generic List.class). You might be able to write your own factory that is parameterized with the correct generic type. Or you might get away with just declaring the #Bean to return a FactoryBean<List<String>.
Another tip is to move the #Bean definition to a separate class (e.g. a nested static one) so that it can be instantiated independently of the rest of the application context. E.g.
#EnableAutoConfiguration
public class TestClass
{
protected static class NestedConfiguration {
#Value("${test.value}")
String value;
#Bean
public FactoryBean<Properties> test1()
{
System.out.println("test.value=" + value );
// ...
return factory;
}
}
...
}
Not really a Boot question this one so you might consider changing the tags.
Take look at Empowering your apps with Spring Boot's property support
There is new annotation #EnableConfigurationProperties in Spring Boot Actuator
The Spring Environment is a collection of name-value pairs taken from (in order of decreasing precedence)
1) the command line,
2) the external configuration file,
3) System properties,
4) the OS environment.
There is also possible to define application properties (external configuration) in YAML format.

Resources