Is there a way to inject a particular interface implementation based on a command line argument in Spring Boot?
I have a data loading app and based on the command line argument I need to load a specific type of data.
Here is my main class and CommandLineRunner:
#SpringBootApplication
public class DataLoadersApplication implements CommandLineRunner {
private Type1LoadProcess type1LoadProcess;
private Type2LoadProcess type2LoadProcess;
public DataLoadersApplication(Type1LoadProcess type1LoadProcess,
Type2LoadProcess type2LoadProcess) {
this.type1LoadProcess = type1LoadProcess;
this.type2LoadProcess = type2LoadProcess;
}
public static void main(String[] args) {
SpringApplication.run(DataLoadersApplication.class, args);
}
#Override
public void run(String... args) {
if (args[0].equalsIgnoreCase("load-type1")) {
type1LoadProcess.process();
} else if (args[0].equalsIgnoreCase("load-type2")) {
type2LoadProcess.process();
}
}
}
Is there a way where I create a DataLoadeProcess interface with two implementations Type1DataLoadProcess and Type2DataLoadProcess and inject the implementaion in main class based on the commandline arg?
You can use Spring profiles to achieve your goal:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
Create the interface DataLoadProcess
Then the classes:
#Component
#Profile("type1")
public class Type1LoadProcess implements DataLoadProcess {
}
#Component
#Profile("type2")
public class Type2LoadProcess implements DataLoadProcess {
}
Then you can inject the interface type like:
#Autowired
DataLoadProcess dataLoadProcessor;
And now you can start your application with one of the profiles for example with a system property set:
-Dspring.profiles.active=type1
A complete example for this is
#SpringBootApplication
public class DataLoadersApplication implements CommandLineRunner {
public interface LoadProcess {
void doLoad();
}
#Component // default that exists unconditionally in any profile
static class Type1LoadProcess implements LoadProcess {
#Override public void doLoad() { System.out.println("Load1"); }
}
#Profile("type2") // this only exists in the type2 profile
#Primary // if it exists it gets picked over others
#Component
static class Type2LoadProcess implements LoadProcess {
#Override public void doLoad() { System.out.println("Load2"); }
}
// need a 3rd? #Profile("type3") #Primary #Component
#Autowired // need one of them here
private LoadProcess loadProcess;
#Override
public void run(String... args) {
loadProcess.doLoad();
}
public static void main(String[] args) {
SpringApplication.run(DataLoadersApplication.class, args);
}
}
This makes use of profiles and uses the primary bean mechanism to allow for a default implementation when no profile is specified.
You can then select which profile is used via any of the options listed at https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html e.g. by setting an environemnt variable
SPRING_PROFILES_ACTIVE=type2 java -jar myApp.jar
using a property
java -Dspring.profiles.active=type2 java -jar myApp.jar
or even a parameter
java -jar myApp.jar --spring.profiles.active=type2
when you want the type2 implementation. You can still put "type1" as active profile even though it is nowhere defined. It will still do the right thing and use the type1 code since that's the default.
I would use Spring profiles for this. Just turn your implementations into Spring Beans and then load the desired Bean based on an active profile.
When you then specify the active profile(s) as command line parameters when starting the app the respective Bean should get used.
Related
I am implementing a custom Spring context customizer, as I have to perform some operations during startup of the application. The result of the operation is need to configure the datasource I need in my application.
My problem is now, that I need for those operations access to my configuration properties (from application.yaml), as they are the base for my operations.
My, simplified, implementation looks currently like this. Nothing special.
public class MyContextCustomizerFactory
implements ContextCustomizerFactory {
#Target(TYPE) #Retention(RUNTIME)
#Documented #Inherited
public #interface EnabledPostgresTestContainer {
}
#Override
public ContextCustomizer createContextCustomizer(Class<?> c,
List<ContextConfigurationAttributes> a) {
}
static class MyContextCustomizer implements ContextCustomizer {
#Override
public void customizeContext(ConfigurableApplicationContext c,
MergedContextConfiguration mc) {
}
}
}
Of is there an alternatvie approach. Using Springs DynamicPropertySource is currently not an option.
Not sure ContextCustomizerFactory is what you are looking for, because you are talking about "application" but ContextCustomizerFactory is designed for running tests, anyway...
What exactly has confused you?
public class MyContextCustomizerFactory implements ContextCustomizerFactory {
#Override
public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
return new MyContextCustomizer();
}
}
public class MyContextCustomizer implements ContextCustomizer {
#Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
ConfigurableEnvironment environment = context.getEnvironment();
// reading properties
String applicationName = environment.getProperty("application.name");
// enriching properties
Properties jdbcProperties = new Properties();
jdbcProperties.put("spring.datasource.url", "jdbc://....");
environment.getPropertySources().addFirst(
new PropertiesPropertySource("customizerProperties", jdbcProperties)
);
}
}
UPD.
If the goal is to modify env/properties after Spring has parsed #Configuration classes with #PropertySource we may use BeanFactoryPostProcessor, below are some examples from spring:
EmbeddedDataSourceBeanFactoryPostProcessor - very similar to what TC needs
PropertySourceOrderingPostProcessor - reorders property sources
PropertyOverrideConfigurer
In my SpringBoot app, I have Autowired an configObject in the class that implements EnvironmentPostProcessor.
The injected class reads data from a different source on startup as this is required for the app to work.
But upon starting the application, the configObject is coming off as Null.
#SpringBootApplication
#EnableEncryptableProperties
#EnableConfigurationProperties
#EnableCaching
#Slf4j
public class SBApplication {
public static void main(String[] args) {
SpringApplication.run(SBApplication.class, args);
}
}
And the AppEnvironmentPostProcessor class where the Autowired object is called. This class is configured as org.springframework.boot.env.EnvironmentPostProcessor in spring.factories. The class gets called on start up.
#Slf4j
public class AppEnvironmentPostProcessor implements
EnvironmentPostProcessor, Ordered {
#Autowired
KeysConfig keysConfig;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// keysConfig is null
String key = keysConfig.getSecretKeyMap().get("key12");
}
}
And in the KeysConfig class
#Component
public final class KeysConfig {
public Map getSecretKeyMap() {
//Returns key map
}
}
I am using Intellij Ultimate. How can I debug and resolve this?
EnvironmentPostProcessors are created before the application context has been created and, therefore, before dependency injection is possible. This means that #Autowired won’t work.
You’ll have to update your implementation to create an instance of KeysConfig itself, or to use a different approach that mimics whatever KeysConfig currently does.
I have an interface like so:
public interface Animal {
void setName(String animal);
String getName();
}
and I have a Class that implements the interface:
#Component
public class Dog implements Animal {
private String name;
public void setName(String name) {
this.name= name;
}
public String getName() {
return this.name;
}
}
In another class (ProcessAnimal), I AutoWire the interface:
public class ProcessAnimal {
#Autowired
public Animal animal;
public void processAnimals() {
animal.setName("Fido");
}
}
I only have one class that implements Animal so this should work, however, I get a NullPointerException when it hits the animal.setName("Fido"); line. IntelliJ is complaining that Autowired members must be defined in valid Spring bean (#Component|#Service...) which I have... I don't understand what I'm doing wrong. I've tried to add a #Qualifier, but still it didn't work and it shouldn't be necessary since I only have one implementation.
-java
-com.example.com.AnimalProcessing
-Animal
-Animal.java
-Dog.java
-ProcessAnimal.java
-AnimalProcessingApplication.java
AnimalProcessingApplication.java
#SpringBootApplication
public class AnimalProcessingApplication {
public static void main(String[] args) {
SpringApplication.run(AnimalProcessingApplication.class, args);
run();
}
public static void run() {
ProcessAnimal processAnimal = new ProcessAnimal();
processAnimal.processAnimals();
}
}
AnimalProcessingApplication class should be one level above all other classes.
Also you are using new for creation of object instead of using Dependency Injection (autowiring).
Replace below -
ProcessAnimal processAnimal = new ProcessAnimal();
with
#Autowired
ProcessAnimal processAnimal;
Also make sure that ProcessAnimal is a bean and Animal is injected in this class using autowiring.
Animal Processing Application.java must be on root folder of all classes.
Then all components in child folders are recognized automatically.
Update:
Create a config class with #Bean method to create an instance with a Dog. Also then you can get rid of the #Component annotation of the class.
The problem here is the constructor String name which cannot be injected.
Update 2:
Don't create the instances by yourself. Let spring container create them. Remove the run method.
Following are to be done to make this program work.
1.ProcessAnimal should be made a component . Annotating the class with #Component will mark the class to be autodetected during component scan.
#Component
public class ProcessAnimal {
#Autowired
public Animal animal;
public void processAnimals() {
animal.setName("Fido");
}
}
Obtain the ProcessAnimal class from the application context. The spring will prepare the ProcessAnimal bean with all its dependencies set.
You may do this in multiple ways and following is one of those
#Component
public class CheckRedRunner implements ApplicationRunner {
#Autowired
ProcessAnimal process;
#Override
public void run(ApplicationArguments args) throws Exception {
process.processAnimals();
}
}
A bean implementing ApplicationRunner will be run when the application starts.
or else
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AnimalProcessingApplication.class, args);
ProcessAnimal process = ctx.getBean(ProcessAnimal.class);
process.processAnimals();
}
Couple of observations
the package names by convention uses lower case letters
example : com.example.process.entity
Please go through the official documentation to learn the expected way of writing Spring boot application.
In Spring Boot, a main class needs to be specified, which is the entry point to the app. Typically this is a simple class with a standard main method, as follows;
#SpringBootApplication
public class MySpringApplication {
public static void main(String [] args) {
SpringApplication.run(MySpringApplication.class, args);
}
}
This class is then specified as the main entry point when the application runs.
However, I want to run my code using a different main class using config to define this, And without using a different jar!! (I know rebuilding the jar will enable me to specify an alternative main class, but this effectively gives me two apps, not one! So, how can I do this to utilise one jar with two main classes and select the one to use via the Spring application.yml file?
I found an answer - use the CommandLineRunner interface...
So now I have two classes;
public class ApplicationStartupRunner1 implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
//implement behaviour 1
}
and
public class ApplicationStartupRunner2 implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
//implement behaviour 2
}
and how to switch between them in config..
#Configuration
public class AppConfig {
#Value("${app.runner}")
private int runner;
#Bean
CommandLineRunner getCommandLineRunner() {
CommandLineRunner clRunner = null;
if (runner == 1) {
clRunner = new ApplicationStartupRunner1();
} (else if runner == 2) {
clRunner = new ApplicationStartupRunner2();
} else {
//handle this case..
}
return clRunner;
}
}
and finally in the application.properties file, use
app.runner=1
I would stick with the original pattern of having just one main class and main method, however within that method you could configure where you want to go. I.e. rather than having 2 main methods and configuring which gets called make these 2 methods just normal methods, and create one main method which uses the config to determine which of your two methods get run.
The answer of jonny.l is fine.
Another very similar, but more manual/DIY solution is to get the ApplicationContext, from which you can get all other things:
#SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(App.class);
ConfigurableEnvironment env = context.getBean(ConfigurableEnvironment.class);
String mainApp = env.getProperty("app.runner");
if (mainApp.equals("Main1")) {
Main1 main1 = context.getBean(Main1.class);
main1.run();
} else if (mainApp.equals("Main2")) {
Main2 main2 = context.getBean(Main2.class);
main2.run();
}
}
}
#Service #Lazy
public class Main1 {
public void run() {}
}
#Service #Lazy
public class Main2 {
public void run() {}
}
#Lazyis used to prevent those beans from loading unnecessarily automatically.
I've got two beans. Both implement the mailing function. One is only working when it is deployed to an application server. The other one is used for testing.
We have profile for each developer and environment. I want to wire the testing bean only when actually testing. The other bean should be used when not testing. How can I archive this?
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Solution approaches:
Using "default" I read this somewhere, but there seems to be no fall-back to "default" for a profile like "dev":
#Component
#Profile("default")
public class OnlineMail implements Mailing {}
-> Exception for no bean for wiring found.
Leaving the profile out:
#Component
public class OnlineMail implements Mailing {}
-> Throws a unique exception when running the "localtest" profile.
Adding all profiles:
#Component
#Profile("prod")
#Profile("integration")
#Profile("test")
#Profile("dev1")
#Profile("dev2")
#Profile("dev3")
...
public class OnlineMail implements Mailing {}
This is actually working, however our devs aren't numbered they use "dev<WindowsLogin>" and adding the profiles, may work for one bean, but one will get into trouble when using it for several beans as this definitely gets ugly.
Using something like #Profile("!localtest") doesn't seem to work as well.
Does anyone know a nicer way to get a "wire by default if no specific bean is found"?
I finally found an easy solution.
The online mail is just wired by default.
#Component
public class OnlineMail implements Mailing {}
Using the #Primary annotation the offline mail takes precedence over the OnlineMail and avoids the Unique exception.
#Component
#Profile("localtest")
#Primary
public class OfflineMail implements Mailing {}
Try this:
#Component
#Profile("production")
public class OnlineMail implements Mailing {}
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Then run tests using #ActiveProfiles("localtest") and run production enviroment using "production" as DEFAULT profile.
Also I hope in next version of Spring ActiveProfilesResolver will be introduced SPR-10338 - it may be helpfull for you (to avoid "dev1", "dev2" and so on).
Spring supports inject the Bean by #Profile very well:
interface Talkative {
String talk();
}
#Component
#Profile("dev")
class Cat implements Talkative {
public String talk() {
return "Meow.";
}
}
#Component
#Profile("prod")
class Dog implements Talkative {
public String talk() {
return "Woof!";
}
}
Works in unit test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:applicationContex-test.xml"})
#ActiveProfiles(value = "dev")
public class InjectByDevProfileTest
{
#Autowired
Talkative talkative;
#Test
public void TestTalkative() {
String result = talkative.talk();
Assert.assertEquals("Meow.", result);
}
}
Works in Main():
#Component
public class Main {
public static void main(String[] args) {
// Enable a "dev" profile
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Main p = context.getBean(Main.class);
p.start(args);
}
#Autowired
private Talkative talkative;
private void start(String[] args) {
System.out.println(talkative.talk());
}
}
Check this for the Demo code: https://github.com/m2land/InjectByProfile