Issue
Currently, I have a component that implements CommandLineRunner
and parses the command line arguments with Commons CLI.
java -jar app.jar --host 123.123.222 --port 8080
There is also another component, Requester
, which depends on (a subset of) those arguments.
@Component
public class Requester
{
// host and port need to be configured once
private final String host;
private final int port;
public Requester(String host, int port)
{
this.host = host;
this.port = port;
}
public boolean doRequest(String name) throws Exception
{
String url = "http://" + host + ":" + port + "/?command=" + name;
URL obj = new URL(url);
HttpURLConnection connection = (HttpURLConnection) obj.openConnection();
int responseCode = connection.getResponseCode();
return 200 == responseCode;
}
}
What can I do to autowire a configured Requester
into future components? What is the Spring way to create parameterized, singleton beans?
One solution would be to have every component, that has any dependency on the program arguments, implement CommandLineRunner
. This way it could parse the program arguments itself, but that is a highly redundant approach. There must be a better solution.
Solution
Inspired by JB Nizet's comment, I now parse the command line arguments and register the beans manually before I start the Spring application.
I identified two ways: provide the c'tor arguments and let Spring create the beans or provide a supplier function to Spring.
Now it's possible to declare @Autowire private Requester requester;
in other parts of the application.
In order for this solution to work the @Component
annotation needs to be removed from Requester
, because otherwise problems may occur when Spring can't provide the necessary constructor arguments.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// parse args here
String host = getHost();
int port = getPort();
SpringApplication application = new SpringApplication(Application.class);
// provide c'tor arguments and let Spring create the instance
BeanDefinitionCustomizer bdc = bd -> {
// working with bd.getPropertyValues() uses setter instead of c'tor
ConstructorArgumentValues values = bd.getConstructorArgumentValues();
values.addIndexedArgumentValue(0, host);
values.addIndexedArgumentValue(1, port);
};
application.addInitializers((GenericApplicationContext ctx) -> ctx.registerBean(Requester.class, bdc));
// or create the instance yourself
Requester requester = new Requester(host, port);
application.addInitializers((GenericApplicationContext ctx) -> ctx.registerBean(Requester.class, () -> requester));
application.run(args);
}
}
Answered By - mike