First off, let me clarify something:
I do not suggest placing @Configuration on your business objects (beans). classes annotated with @Configuration are meant to be a tool for definitions of other beans. They usually look like:
@Configuration
public class SampleConfiguration {
@Bean
public SomeBean someBean() {
return new SomeBean();
}
@Bean
public AnotherBean anotherBean() {
return new AnotherBean();
}
}
In this example, SomeBean and AnotherBean are "business" objects. This works as an alternative to putting @Service/@Component on these classes, and in any case you never should put @Configuration on them.
Now regarding the question and proposed solutions:
In a nutshell, you can inject the environment like this:
@Service
public class EnvChecker {
private final Environment env;
public EnvChecker(Environment env) {
this.env = env;
}
@PostConstruct
public void init() {
System.out.println(env); // this is not null
}
}
If you want to activate the profile, use --spring.profiles.active=dev as a "Program Argument" text field in intelliJ (-D won't work)
Although this answers the question, since its real project, let me express some thoughts that may influence the actual code you'll write at the end.
IMO: its not a good idea to make your business code (I mean at the level of coding) dependent on different environments, what if tomorrow you'll add yet another environment (test, qa, integration, production 1, production 2 - you name it). This logic will become unmaintainable.
To address this, spring traditionally used profiles.
Instead of using if-condition in the code depending on the injected environment you can create two beans and activate with different profiles:
Example:
You probably have something like this:
public class MyClass {
private Environment env; // lets assume it works right
public void doSomething() {
if(env.getActiveProfiles() contains "dev") { // its a pseudo code, not real java, but still...
doFoo(); // some code
}
else {
doBar(); // another piece of code
}
}
}
So The first suggestion using profiles is:
@Service
@Profile("dev")
public class DevBean {
public void doSomething() {
doFoo();
}
}
@Service
@Profile("prod")
public class ProdBean {
public void doSomething() {
doBar();
}
}
This approach is better because the business code will be easier to read and maintain, on the other hand, there is still a code that depends on profile state.
However there is another solution that I like even better and can recommend:
The idea is "treat" a profile as a series of business features that can be enabled or disabled. This makes sense because profiles by themselves usually denote the environment but the project is always comprised of business features.
So, lets assume that your code implements the feature X that deals with database connectivity (for the sake of example). You want to disable the database in the development environment and would like to work "in-memory".
So in the terms of feature that the system provides you can define the following:
feature.X.mode=database / in-memory
Then you can configure the dev profile with "in-memory" property and the default value (the rest of profiles) can be "database":
application-dev.yml:
feature:
X:
mode: "in-memory"
application.yml:
feature:
X:
mode: "database" # or you can even omit the definition, leaving the default to be the "database"
Now the beans can be defined in a similar manner to the previous technique but now you won't depend anymore on the profile, and instead will depend on the availability of the business feature:
@Service
@ConditionalOnProperty(name = "feature.X.mode", havingValue="in-memory")
public class DevBean {
public void doSomething() {
doFoo();
}
}
@Service
@ConditionalOnProperty(name = "feature.X.mode", havingValue="database", matchIfMissing=true) // the last parameter guarantees that the bean will be loaded even if there is no configuration at all
public class ProdBean {
public void doSomething() {
doBar();
}
}
Now finally you don't have the "environment dependent code".
The last small tip is what if you have several beans like this (service/components).
In this case you can define the "stereotype annotation" (@FeatureXService) or use Java Configuration (class annotated with @Configuration) and place that @ConditionalOnProperty annotation only once.