I need to get the following system property within one of my configuration files: -Dspring.profiles.active="development". Now I have seen countless of people suggest that this can be done with Spring Expression Language, but I cannot get it to work. Here is what I have tried (plus many variations hereof).
#Configuration
#ComponentScan(basePackages = { ... })
public class AppConfig {
#Autowired
private Environment environment;
#Value("${spring.profiles.active}")
private String activeProfileOne;
#Value("#{systemProperties['spring.profiles.active']}")
private String activeProfileTwo;
#Bean
public PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
Resource[] resources = {
new ClassPathResource("application.properties"),
new ClassPathResource("database.properties")
// I want to use the active profile in the above file names
};
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocations(resources);
propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertySourcesPlaceholderConfigurer;
}
}
All of the properties are NULL. The same happens if I try to access any other system property. I can access them without problems by invoking System.getProperty("spring.profiles.active"), but this is not as nice.
Many examples that I found configure the PropertySourcesPlaceholderConfigurer to also search system properties, so maybe this is why it does not work. However, those examples are in Spring 3 and XML configuration, setting a property that no longer exists on the class. Perhaps I have to call the setPropertySources method, but I am not entirely sure how to configure this.
Either way, I found multiple examples that suggest that my approach should work. Believe me, I searched around a lot. What's wrong?
Just autowire an instance of Spring's Environment interface, as you're actually doing and then ask what the active environment is:
#Configuration
public class AppConfig {
#Autowired
private Environment environment;
#Bean
public PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
List<String> envs = Arrays.asList(this.environment.getActiveProfiles());
if (envs.contains("DEV")) {
// return dev properties
} else if (envs.contains("PROD")) {
// return prod properties
}
// return default properties
}
}
Related
For some context:
The component needs to do logic based on the current environment. Since there is no way to set the environment manually, a lot of the #Properties annotations won't work - setting the properties file is just not flexible enough for our needs. However, when I try to pull environment details into my component class, nothing seems to work! Examples make it look so easy but mine always returns null. Could I be missing something below the hood of Spring that would cause this? Here is what I've tried:
Using Resource, and it's logs:
#Component
#Slf4j
public class compClass {
#Resource
private Environment env;
public void map(DtoObj dto){
log.info("RUN PROFILE IS: {}", env.getActiveProfiles());
}
}
java.lang.NullPointerException: null
at com.demo.Com.map(CompClass.java:22) ~[classes/:na]
Using Autowire, and it's logs:
#Component
#Slf4j
public class CompClass {
#Autowired
private Environment env;
public void map(DtoObj dto){
log.info("RUN PROFILE IS: {}", env.getActiveProfiles());
}
}
java.lang.NullPointerException: null
at com.demo.Com.map(CompClass.java:22) ~[classes/:na]
#Value with environment and it's logs:
#Component
#Slf4j
public class CompClass {
#Value("#{environment.activeProfiles}")
private String[] profiles;
public void map(DtoObj dto){
log.info("RUN PROFILE IS: {}", profiles);
}
}
RUN PROFILE IS: null
#Value with properties path and it's logs
#Component
#Slf4j
public class CompClass {
#Value("${spring.profiles.active}")
private String[] profiles;
public void map(DtoObj dto){
log.info("RUN PROFILE IS: {}", profiles);
}
}
RUN PROFILE IS: null
here is what I can share of properties under java/main/resources:
spring:
application:
name: namnam
profiles:
active: dev
I am runing this on IntelliJ using this VM:
-Dspring.profiles.active=dev
I have my properties file in the java/main/resources directory, and we're able to use it's other properties in the code. What about this have I messed up?
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.
This piece of code is working fine with me I hope it will be useful to you.
#Configuration
public class CompClass {
private final Environment env;
public CompClass (Environment env) {
this.env = env;
}
public void map(DtoObj dto){
log.info("RUN PROFILE IS: {}", env.getActiveProfiles());
}
public void doSomethingBasedOnSpeceficProfile() {
if (env.acceptsProfiles(Constants.SPRING_PROFILE_PRODUCTION)) {
// do something if the profile is what you want
} else {
// do something else
}
}}
I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.
I want to configure a tomcat data source in Spring Boot, The properties of the database are stored in another property file (Say dbConnection.properties) with different keys.
For example
dbConnection.properties:
DATABASE_URL=SomeURL
DATABASE_USER=SomeUser
DATABASE_PASSWORD=SomePassword
From what I understand the properties related to a data source must be specified in application.properties as:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
How do I pass the values from dbConnection.properties to application.properties?
From Spring Boot documentation
Property contributions can come from additional jar files on your classpath so you should not consider this an exhaustive list. It is also perfectly legit to define your own properties.
so you can have your own property file and it should be in your classpath,
Inject property using Value annotation
#Value("#{propFileName.propKeyName}")
All you need is override the Spring-Boot's Datasource default configuration. See the example above:
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.username("") // TODO: Get from properties
.password("") // TODO: Get from properties
.url("") // TODO: Get from properties
.driverClassName("") // TODO: Get from properties
.build();
}
In order to get from properties you can use the #Value annotation that #Saravana said you.
#Manish Kothari... try this,create a configuration class with the annotations like
#ConfigurationProperties.
#Component
#PropertySource("classpath:dbConnection.properties")
#ConfigurationProperties
public class ConfigurationClass{
//some code
}
and now it will call your DB properities... I hope this will work
There are multiple ways to do this
1.You can pass the property files from the command promopt
-Dspring.config.location=classpath:job1.properties,classpath:job2.properties
2.Way is to add #PropertySource annotation
public class AppConfig
#PropertySource("classpath:config.properties")
public class LoadDbProps{
#value("${DATABASE_USER}")
private String dbUserName;
private String dbUserName;
}
Later you can set this LoadDbProps to application.properties properties using #Bean configuration.
The below solution worked for me :
#Configuration
public class DataSourceConfig {
#Bean
public DataSource getDataSource() {
Properties properties = null;
InputStream inputStream = null;
DataSourceBuilder dataSourceBuilder =null;
try {
properties = new Properties();
inputStream = new FileInputStream("./src/main/resources/config.properties");
properties.load(inputStream);
dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url(properties.getProperty("url"));
dataSourceBuilder.username(properties.getProperty("user"));
dataSourceBuilder.password(properties.getProperty("password"));
}
catch(Exception ex) {
System.out.println("CONFIG EXCEPTION :"+ex);
}
return dataSourceBuilder.build();
}
}
refer below link for more details:
https://howtodoinjava.com/spring-boot2/datasource-configuration/#:~:text=Spring%20boot%20allows%20defining%20datasource,a%20DataSource%20bean%20for%20us.
I've the following configuration file
#Configuration
#ComponentScan(basePackages = "com.foo")
#EnableTransactionManagement
public class AppSpringConfiguration {
#Autowired
private Environment env;
#Autowired
private ApplicationContext appContext;
#Value("#{cvlExternalProperties['dbDriverClassName']}")
private String dbDriverName;
#Bean
public PropertiesFactoryBean cvlExternalProperties() {
PropertiesFactoryBean res = new PropertiesFactoryBean();
res.setFileEncoding("UTF-8");
res.setLocation(new FileSystemResource(env.resolvePlaceholders("${MY_ENV_VAR}") + "external.properties"));
return res;
}
#Bean
public BasicDataSource datasource() {
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName("myDriverClassName");
basicDataSource.setUrl("MyDbUrl");
basicDataSource.setUsername("myUser");
basicDataSource.setPassword("myPass");
return basicDataSource;
}
}
And in the external properties file I've
dbUrl=jdbc:mysql://localhost:3306/someDb
dbUser=someUser
dbPassword=somePass
dbDriverClassName=com.mysql.jdbc.Driver
In which way I can use the cvlProperties inside the datasource() method?
I've tried
env.getProperty("dbDriverClassName")
env.getProperty("#cvlProperties['dbDriverClassName']")
But I'm not able to retrieve the properties.
The field dbDriverName is correctly filled, so that means the bean declaration is ok.
I want to use the PropertyFactoryBean class because in this way I can specify the encoding to use.
If I use the the following annotation on the top of the configuration class
#PropertySource("file:${MY_ENV_VAR}/external.properties")
I'm able to retrieve the properties with this piece of code
env.getProperty("dbDriverClassName")
But the encoding used by the PropertySource annotation is the windows default, and for me is not correct.
Can you help me?
At the moment the solution(that I don't love so much) is to declare the properties by using the annotation #Value
#Value("#{cvlExternalProperties['dbDriverClassName']}")
private String dbDriverClassName;
and then using it inside the java class
I have small test project to test Spring annotations:
where in nejake.properties is:
klucik = hodnoticka
and in App.java is:
#Configuration
#PropertySource("classpath:/com/ektyn/springProperties/nejake.properties")
public class App
{
#Value("${klucik}")
private String klc;
public static void main(String[] args)
{
AnnotationConfigApplicationContext ctx1 = new AnnotationConfigApplicationContext();
ctx1.register(App.class);
ctx1.refresh();
//
App app = new App();
app.printIt();
}
private void printIt()
{
System.out.println(klc);
}
}
It should print hodnoticka on console, but prints null - String value is not initialized. My code is bad - at the moment I have no experience with annotation driven Spring. What's bad with code above?
You created the object yourself
App app = new App();
app.printIt();
how is Spring supposed to manage the instance and inject the value?
You will however need
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
to make the properties available. Also, because the App bean initialized for handling #Configuration is initialized before the resolver for #Value, the value field will not have been set. Instead, declare a different App bean and retrieve it
#Bean
public App appBean() {
return new App();
}
...
App app = (App) ctx1.getBean("appBean");
You need to access the property from a Spring bean, and you need to properly wire in the properties. First, add to your config class this:
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
PropertySourcesPlaceholderConfigurer props = new PropertySourcesPlaceholderConfigurer();
props.setLocations(new Resource[] { new ClassPathResource("com/ektyn/springProperties/nejake.properties") }); //I think that's its absolute location, but you may need to play around with it to make sure
return props;
}
Then you need to access them from within a Spring Bean. Typically, your config file should not be a bean, so I would recommend you make a separate class, something like this:
#Component //this makes it a spring bean
public class PropertiesAccessor {
#Value("${klucik}")
private String klc;
public void printIt() {
System.out.println(klc);
}
}
Finally, add this to your config to make it find the PropertiesAccessor:
#ComponentScan("com.ektyn.springProperties")
Then you can access the PropertiesAccessor bean from your app context and call its printIt method.