How to work with annotation based properties in Spring - java

I would like to use 'someotherproperty' value inside SomeIfaceDaoImpl
But when I debug, it's always null, inside my bean definition and inside my bean constructor as well. I also tried to use #Value annotation inside my class but this does not work either.
However, all database values works fine and available inside jdbcTemplate bean.
My properties file contains
database.url=jdbc:mysql://localhost:3306/databasename
database.username=root
database.password=password
someotherproperty=HelloWorld
My configuration class:
#Configuration
#Profile("production")
#ComponentScan(basePackages = { "com.packagename" })
#PropertySource({"classpath:packagename.properties"})
public class ContextConfig {
#Value("${database.url}")
private String url;
#Value("${database.username}")
private String username;
#Value("${database.password}")
private String password;
#Value("${someotherproperty}")
private String someotherproperty;
#Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(StringUtil.appendObjects(url, "?", "useServerPrepStmts=false&rewriteBatchedStatements=true"));
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername(username);
dataSource.setPassword(password);
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
#Bean
public ISomeIfaceDao iSomeIfaceDao() {
return new ISomeIfaceDaoImpl(); //<---- I would like to have someotherproperty value here or inside the constructor
}
}
Thank you.

You should be able to use 'someotherproperty' directly in your bean method is there's no misconfiguration in your property file. A better approach to avoid having multiple fields annotated with #Value would be using the Environment abstraction
#Configuration
#Profile("production")
#ComponentScan(basePackages = { "com.packagename" })
#PropertySource({"classpath:packagename.properties"})
public class ContextConfig {
#Autowired
private Environment env;
#Bean
public ISomeIfaceDao iSomeIfaceDao() {
return new ISomeIfaceDaoImpl(env.getRequiredProperty("someotherproperty"));
}
}

Related

#Value isn't working on particular #Configuration class on Springboot

Hi I have been making spring boot project which have 2 of datasource.
I want to use those datasources properties' value from application.yml file and
so that I configured #Configuration Class on our project.
But, even if both of Configuration class was set same way, #Value annotation isn't working on the OracleConfig.class.
I tested related oracle setting which wrote on application.yml file is working on the HiveConfig.class and I confirmed that.
how can I inject value to OracleConfig.class :( ,,,
I currently strongly doubt OracleConfig.class was working before #Value annotation or other Beans had been initialized.
should I configure order about bean creation process or (is that make sense)force that OracleConfig.java's #Bean will be created after injection of #Value?
HiveConfig.class
#Configuration
public class HiveConfig {
#Value("${hive.custom.use}")
private boolean useCustomHive;
#Value("${hive.custom.some-of-setting}")
private String SomeOfSetting;
#Bean
#ConfigurationProperties(prefix = "hive.datasource")
public HikariDataSource dataSourceHive() throws Exception {
HikariDataSource hikariDataSource = new HikariDataSource();
if (useCustomHive){
hikariDataSource.SetSomeOfSetting(SomeOfSetting);
}
return hikariDataSource;
}
OracleConfig.class
#Configuration
public class OracleConfig {
#Value("${oracle.custom.use}")
private boolean useCustomOracle;
#Value("${oracle.custom.some-of-setting}")
private String SomeOfSetting;
#Bean
#ConfigurationProperties(prefix = "oracle.datasource")
public HikariDataSource dataSourceOracle() throws Exception {
HikariDataSource hikariDataSource = new HikariDataSource();
if (useCustomOracle){
hikariDataSource.SetSomeOfSetting(SomeOfSetting);
}
return hikariDataSource;
}
application.yml (this values(oracle and hive) can read from HiveConfig.class via #Value)
oracle:
custom:
use:true
some-of-setting: str
hive:
custom:
use:true
some-of-setting: str
Have you tried with constructor injection? See if this works.
private boolean useCustomHive;
private String SomeOfSetting;
HiveConfig (#Value("${hive.custom.use}") boolean useCustomHive, #Value("${hive.custom.some-of-setting}") String SomeOfSetting) {
this.useCustomHive = useCustomHive;
this.SomeOfSetting = SomeOfSetting;
}

How to create datasource bean at runtime without crashing app in Spring?

I am connecting to multiple datasources but sometimes some datasources may be offline and at that time I am geting errors on app and application is failing at startup.
I want to skip datasource configuration at startup... I have tried several ways by adding
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
to the application.properties and also I have tried adding
#SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
to the main class but still it tries to configure the datasource.
I also tried to use #Lazy annotation on all methods and on constructor as below but still getting error while creating fooEntityManagerFactory
#Lazy
#Configuration
#EnableJpaRepositories(basePackages = "com.heyo.tayo.repository.foo", entityManagerFactoryRef = "fooEntityManagerFactory", transactionManagerRef = "fooTransactionManager")
public class PersistencefooConfiguration {
#Autowired
private DbContextHolder dbContextHolder;
#Lazy
#Bean
#ConfigurationProperties("tay.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
return new DataSourceProperties();
}
#Lazy
#Bean
#ConfigurationProperties("tay.datasource.foo.configuration")
public DataSource fooDataSource() {
DataSource dataSource = fooDataSourceProperties().initializeDataSourceBuilder()
.type(BasicDataSource.class).build();
dbContextHolder.addNewAvailableDbType(DbTypeEnum.foo);
return dataSource;
}
#Lazy
#Bean(name = "fooEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean fooEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
//THE CODE IS FAILING AT BELOW RETURN CASE
return builder
.dataSource(fooDataSource())
.packages("com.heyo.tayo.model.foo")
.build();
}
#Lazy
#Bean
public PlatformTransactionManager fooTransactionManager(
final #Qualifier("fooEntityManagerFactory") LocalContainerEntityManagerFactoryBean fooEntityManagerFactory) {
return new JpaTransactionManager(fooEntityManagerFactory.getObject());
}
}
I have multiple classes like above for different configs for different datasources and I am adding them to available dbs static list at datasource Bean.
Here is my dbadapter factory class.
Here is my dbAdaptor factory that creates corresponding db adaptor
#Service
public class DbAdapterFactory {
#Autowired
private BeanFactory beanFactory;
#Autowired
private DbContextHolder dbContextHolder;
public DBAdapter dbAdapter(){
DbTypeEnum currentDb = dbContextHolder.getCurrentDb();
DBAdapter dbAdapter = null;
if(currentDb == DbTypeEnum.FOODB) {
dbAdapter = beanFactory.getBean(foodbadaptor.class);
} else {
dbAdapter = beanFactory.getBean(koodbadaptor.class);
}
return dbAdapter;
}
Here is db context holder that makes operation like setting default db or getting current db etc.:
#Component
public class DbContextHolder {
private DbTypeEnum dbType = DbTypeEnum.FOODB;
private Set<DbTypeEnum> availableDbTypes = new HashSet<>();
public void setCurrentDb(DbTypeEnum dbType) {
this.dbType = dbType;
}
public DbTypeEnum getCurrentDb() {
return this.dbType;
}
public List<DbTypeEnum> getAvailableDbTypes() {
return new ArrayList<>(availableDbTypes);
}
public void addNewAvailableDbType(DbTypeEnum dbTypeEnum) {
availableDbTypes.add(dbTypeEnum);
}
}
I made all #Lazy or tried #SpringBootApplication(exclude={DataSourceAutoConfiguration.class}) but still something is calling to create bean and getting error and app is closing. I want to use that config and datasource in a try-catch block and don't stop application at runtime. How can I achieve this or what am I missing on that configs or annotations ?
I believe that you can simply add in your application properties
spring.sql.init.continue-on-error=true
According to the Spring Boot 2.5.5 user guide:
https://docs.spring.io/spring-boot/docs/2.5.5/reference/htmlsingle/#howto-initialize-a-database-using-spring-jdbc
Spring Boot enables the fail-fast feature of its script-based database initializer. If the scripts cause exceptions, the application fails to start. You can tune that behavior by setting spring.sql.init.continue-on-error.
Depending on your spring boot version the property will be named either
spring.sql.init.continue-on-error
or before Spring Boot 2.5
spring.datasource.continue-on-error
It is so dumb but I solved the problem by adding following to application.properties.
spring.jpa.database=sql_server
I have no idea why I need to specify that explicitly in properties file but the problem is solved. I will search for it

Spring Boot 2 Instanciating Bean from Configuration List

Hy all,
in my current application, I need to be able to load a number of database connections into my context (they are then used in camel routes, but thats not part of this question).
For this I have a List of settings in my application.yml file, where each entry is all that is needed for one connection:
- name: mysql1
jdbcUrl: "jdbc:mysql://localhost:3306/dbScheme"
username: a
password: a
- name: mssql1
jdbcUrl: "jdbc:sqlserver://localhost:1433;databaseName=dbScheme"
username: a
password: a
There is a variable amount of connections, that can be configured this way.
I need each of those configurations as a javax.sql.DataSource object/bean in the registy (bean name would be the name property from the configuration object):
DataSource dataSourceBean = DataSourceBuilder.create()
.driverClassName(driverClassName) // Resolved automatically from the jdbcUrl
.url(dataSourceFromYaml.getJdbcUrl())
.username(dataSourceFromYaml.getUsername())
.password(dataSourceFromYaml.getPassword())
.build();
The question now is, how I do put those objects into the context as beans?
As I see it, its not possible with annotations, because we have a variable amount of objects and they are also not loaded into the context directly, but have to be created first. (Or am I wrong here?)
Thanks in advance for any ideas
Chris
Edit:
To clarify: The two connections I put here are not the connections used in production, they are just an example.
The issue is, that they are configured in production and there can be any amount of them.
I have no way of predicting how many there are and how they are named.
After trying a lot of different spring context objects I finally found one, that worked.
Here is the solution:
#Configuration
#ConfigurationProperties(prefix = "jdbc")
#RequiredArgsConstructor // Lombok
public class DataSourceBeanFactory {
// The DataSourceConfiguration holds everything from one property object
#Setter // Lombok
private List<DataSourceConfiguration> dataSources;
private final ConfigurableApplicationContext applicationContext;
#PostConstruct
public void resolveAndCreateDataSourceBeans() {
dataSources.forEach(dataSourceFromYaml -> {
/*
* Code to resolve driver class name
*/
String driverClassName = ....
DataSource dataSourceBean = DataSourceBuilder.create()
.driverClassName(driverClassName)
.url(dataSourceFromYaml.getJdbcUrl())
.username(dataSourceFromYaml.getUsername())
.password(dataSourceFromYaml.getPassword())
.build();
applicationContext
.getBeanFactory()
.registerSingleton(dataSourceFromYaml.getName(), dataSourceBean);
});
}
Thank to everybody, that answered my question.
Chris
You could use the following approach
create properties file application-dev.properties
mysql1.jdbc.jdbcUrl=jdbc:mysql://localhost:3306/dbScheme
mysql1.jdbc.username=a
mysql1.jdbc.password=a
mssql1.jdbc.jdbcUrl=jdbc:sqlserver://localhost:1433;databaseName=dbScheme
mssql1.jdbc.username=a
mssql1.jdbc.password=a
Create configuration class
#Configuration
public class JDBCConfigExample {
#Bean(name = "mysql1DataSource")
#ConfigurationProperties(prefix = "mysql1.jdbc")
public DataSource mysql1DataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "mssql1DataSource")
#ConfigurationProperties(prefix = "mssql1.jdbc")
public DataSource mssql1DataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "mysql1JdbcTemplate")
public JdbcTemplate mysql1JdbcTemplate(#Qualifier("mysql1DataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean(name = "mssql1JdbcTemplate")
public JdbcTemplate mssql1JdbcTemplate(#Qualifier("mssql1DataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
After that you could use both JdbcTemplate and DataSource in any Spring managed bean by using Qualifier or name convention:
private final JdbcTemplate mssql1JdbcTemplate;
If you don't have list of datasources beforehand and want to configure this dynamically you can do following thing:
1) In your application.yml:
datasources:
ds1:
name:
pw:
ds2:
name:
pw:
2) Create properties class:
#Configuration
#ConfigurationProperties(prefix = "datasources")
public class DataSourcesInfo {
Map<String, DataSourceInfo> dataSources;
public #Data static class DataSourceInfo {
private String pw;
private String name;
}
}
In this map you will have list of entries for all datasources defined.
3) And now you can create beans dynamically:
#Configuration
public class Config {
#Autowired
private GenericApplicationContext genericApplicationContext;
#Autowired
DataSourcesInfo properties;
#PostConstruct
public void createDataSources() {
// iterate through properties and register beans
genericApplicationContext.registerBean(dataSourceName,
DataSource.class,
() -> {
DataSource ds = new DataSource();
ds.set(...);
....
return ds;
})
}
}

Spring Configuration creating two beans instead of one

I'm writing a RESTful web service using Spring MVC, using Java Configuration. My configuration file is below. My issue is this -- I discovered that 2 instances of "myService" bean is being created, instead of just one instance. I'm not sure why? How can I adjust the configuration to create only one?
Can anyone point me in the right direction? Thanks!
Here's my configuration class....
#Configuration
public class MyConfig {
#Bean(name = "dataSource")
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// datasource set up
return dataSource;
}
#Autowired
#Bean(name = "sessionFactory")
public SessionFactory getSessionFactory(DriverManagerDataSource dataSource) {
LocalSessionFactoryBuilder sessionBuilder = new LocalSessionFactoryBuilder(dataSource);
sessionBuilder.scanPackages("com.mypackages");
sessionBuilder.addProperties(getHibernateProperties());
return sessionBuilder.buildSessionFactory();
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.enable_lazy_load_no_trans", "true");
properties.put("hibernate.id.new_generator_mappings", "true");
return properties;
}
#Autowired
#Bean(name = "transactionManager")
public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager transactionManager = new HibernateTransactionManager(sessionFactory);
return transactionManager;
}
#Bean
public MyMainBean MyMainBean() {
MyMainBean bean = new MyMainBean();
bean.setService(myService());
bean.setValidator(myValidator());
return bean;
}
#Bean(name = "myService")
public MyService myService() {
MyService s = new MyService();
s.setDao1(myDao1());
s.setDao2(myDao2());
s.setCopyUtil(copyUtil());
return s;
}
#Bean
public MyDao1 myDao1() {
return new MyDao1();
}
#Bean
public MyDao2 myDao2() {
return new MyDao2();
}
#Bean
public CopyUtil copyUtil() {
return new CopyUtil();
}
#Bean
public ReportValidator reportValidator() {
ReportValidator validator = new ReportValidator();
validator.setService(myService());
return validator;
}
#Bean
public XMLValidator xmlValidator() {
XMLValidator validator = new XMLValidator();
validator.setService(myService());
return validator;
}
}
Actually, Spring is smart when wiring beans and should only call the myService() function once, and then pass the result to the other myService() calls, resulting in only one bean of MyService.
Make sure you really are getting 2 instances of MyService, e.g. by adding a log in the constructor of the MyService class.
If you truly see more than one constructor log statement, make sure that you are not declaring other MyService beans in other #Configuration classes, or that you are not using any component annotation on the MyService class (i.e. don't use #Service, #Component, #Repository).
If you declare the class with #Service, it effectively instantiates the class and adds it to the context. When you declare it again with #Bean you end up with 2 instances, so don't mix them.
Also, you don't need to use those #Autowired annotations here, or even calls to other beans, because the following will also work:
#Configuration
public class DbConfiguration {
#Bean
public MovieDao dao() {
return new MovieDao();
}
#Bean
public MovieService service(MovieDao dao) {
return new MovieService(dao);
}
}
Spring will see that you need a MovieDao to build a MovieService and it will instantiate the dao first and pass it to the service bean. You don't even need to add #Service or similar annotations to your classes!
It really is that good, hope these tips help ;)

#Value annotation not being properly set

Here's my app:
public static void main( String[] args ) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
//run the importer
final ImportNewOrders importer = (ImportNewOrders) ApplicationContextProvider.getApplicationContext().getBean("importNewOrders");
importer.run();
//importer.runInBackground();
}
Here's my config:
#Configuration
#ComponentScan(basePackages = {
"com.production"
})
#PropertySource(value = {
"classpath:/application.properties",
"classpath:/environment-${MY_ENVIRONMENT}.properties"
})
#EnableJpaRepositories("com.fettergroup.production.repositories")
#EnableTransactionManagement
public class Config {
.... skipping things that aren't relevant
#Bean
public ImportNewOrders importNewOrders() {
return new ImportNewOrders();
}
Here's my class...
#Component
public class ImportNewOrders implements Task {
private final static Logger logger = Logger.getLogger(ImportNewOrders.class.getName());
#Autowired
private OrderService orderService;
#Autowired
private ImportOrderRequest importOrderRequest;
#Value("${api.user}")
private String apiUser;
#Value("${api.password}")
private String apiPassword;
#Value("${api.orders.pingFrequency}")
private String pingFrequency;
And finally the application.properties:
# ------------------- Application settings -------------------
#Base URL to the API application
api.baseUrl=http://localhost:9998
#Unique token used to authenticate this vendor
api.vendor.token=asdf
#API credentials
api.user=myuser
api.password=mypassword
#How often to check for new orders; frequency is in seconds
api.orders.pingFrequency=60
This worked an hour or two ago, now it's decided it doesn't like these values. I'm at a loss as to why. Everything looks correct to me.
Update
#Configuration
#ComponentScan(basePackages = {
"com.production"
})
#PropertySource(value = {
"classpath:/application.properties",
"classpath:/environment-${MY_ENVIRONMENT}.properties"
})
#EnableJpaRepositories("com.production.repositories")
#EnableTransactionManagement
public class Config {
#Value("${db.url}")
private static String PROPERTY_DATABASE_URL;
#Bean
public DataSource dataSource() {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl(PROPERTY_DATABASE_URL); //is null
/*dataSource.setUser(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USER));
dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));*/
return dataSource;
}
#Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer () {
return new PropertySourcesPlaceholderConfigurer();
}
}
Your properties file is found by your #Configuration and is using it for your database properties within that class because of #PropertySource. But #Value fields and ${} evaluation need more than that.
From Javadoc for #PropertySource
In order to resolve ${...} placeholders in definitions or
#Value annotations using properties from a PropertySource, one must
register a PropertySourcesPlaceholderConfigurer. This happens
automatically when using in XML, but
must be explicitly registered using a static #Bean method when using
#Configuration classes. See the "Working with externalized values"
section of #Configuration Javadoc and "a note on
BeanFactoryPostProcessor-returning #Bean methods" of #Bean Javadoc for
details and examples.
So declare a
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer p = new PropertySourcesPlaceholderConfigurer();
p.setLocation(new ClassPathResource("your properties path"));
// other properties
return p;
}
in your config class, or as ach has aptly mentioned in the comments if you use #PropertySource your can omit setLocation altogether:
#Configuration
#PropertySource(value="classpath:your_file.properties")
public class MyConfiguration{
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer p = new PropertySourcesPlaceholderConfigurer();
return p;
}
}
You shouldn't need the environment when you have the PropertySourcesPlaceholderConfigurer
In most cases, however, application-level beans should not need to>
interact with the Environment directly but instead may have to have
${...} property values replaced by a property placeholder configurer
such as PropertySourcesPlaceholderConfigurer, which itself is
EnvironmentAware and as of Spring 3.1 is registered by default when
using < context:property-placeholder/>.

Categories

Resources