Spring #RefreshScope with #Configuration not refreshed dynamically - spring-boot

I am using spring boot version(2.2.5.RELEASE) and spring-cloud-dependencies(Hoxton.SR3).
I have a class as shown below:
#RefreshScope
#Configuration
public class JavaMailConfig {
#Value("${email.common.config.host:ERROR: Could not load email config host}")
private String host;
#Value("${email.common.config.port:ERROR: Could not load email config port}")
private String port;
#Value("${email.common.config.transport.protocol:ERROR: Could not load email config protocol}")
private String protocol;
#Value("${email.common.config.username:ERROR: Could not load email config username}")
private String mailUserName;
#Value("${email.common.config.password:ERROR: Could not load email config passsword}")
private String mailPassword;
#Value("${email.common.config.password:ERROR: Could not load email config smtpAuth}")
private String smtpAuth;
#Value("${email.common.config.password:ERROR: Could not load email config startTlsEnable}")
private String startTlsEnable;
#Value("${email.common.config.password:ERROR: Could not load email config sslTrust}")
private String sslTrust;
#Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
CommonUtility.setPort(mailSender, port);
mailSender.setUsername(mailUserName);
mailSender.setPassword(mailPassword);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", protocol);
props.put("mail.smtp.auth", smtpAuth);
props.put("mail.smtp.starttls.enable", startTlsEnable);
props.put("mail.smtp.ssl.trust", sslTrust);
return mailSender;
}
}
I am using spring cloud config to get my information from git. In the same project I have a class below:
#RestController
#RefreshScope
#RequestMapping("/email")
public class EmailController {
private static final Logger LOG = LoggerFactory.getLogger(EmailController.class);
#Autowired
SendMailService sendMailService;
#Value("${email.common.config.username:ERROR: Could not load email config username}")
private String mailUserName;
#PostMapping(value = "/sendMail")
//Note:Not to remove #RequestBody and #RequestBody as swagger UI will not interpret it correctly
public ResponseEntity<String> sendMail(#RequestBody EmailRequestDto emailRequestDto) {
if (checkAllEmailAddValid(emailRequestDto)) {
System.out.println("mailUserName from controller " + mailUserName);
System.out.println("profile " + profile);
sendMailService.sendEmail(emailRequestDto);
LOG.debug("Send mail completed successfully ");
return new ResponseEntity<>("Mail has been sent successfully", HttpStatus.OK);
} else {
LOG.error("Email addresse provided is invalid");
return new ResponseEntity<>("Email address provided is invalid", HttpStatus.BAD_REQUEST);
}
}
When I refresh the url with "actuator/refresh" the restcontroller get refreshed successfully but not the #Configuration class as stated earlier.
Update: The class below I am using the JavaMailSender:
#Component
#RefreshScope
public class SendMailServiceImpl implements SendMailService {
private static final Logger LOG = LoggerFactory.getLogger(SendMailServiceImpl.class);
#Autowired
private JavaMailSender javaMailSender;
/**
* {#inheritDoc}
*/
#Override
public void sendEmail(EmailRequestDto emailRequestDto) {
....
}
So is it possible to use refresh scope annotation with Configuration annotation?
Thanks in advance for any advice

Just in case if someone have this issue I manage to make it work but I don't know if it the correct way though.
I have added the ConfigurationProperties annotation with the prefix which contains the key of my properties file on top of my bean annotation:
#ConfigurationProperties(prefix = "email.common.config")
#Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
CommonUtility.setPort(mailSender, port);
mailSender.setUsername(mailUserName);
mailSender.setPassword(mailPassword);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", protocol);
props.put("mail.smtp.auth", smtpAuth);
props.put("mail.smtp.starttls.enable", startTlsEnable);
props.put("mail.smtp.ssl.trust", sslTrust);
return mailSender;
}

Related

Field properties in xx.xxx.configuration.ElasticSearchConfiguration required a single bean, but 2 were found:

As part of spring-framework up-gradation from 1.5.6.RELEASE to 2.4.0 i am facing one issue
I am using 2 different ElasticSearch Host so i create 2 different classes one for ESConfigForMasterData and Other for ElasticSearchConfiguration while running spring boot application it throws an error
APPLICATION FAILED TO START
**Description:
Field properties in xxx.xxx.configuration.ElasticSearchConfiguration required a single bean, but 2 were found:
- integrationGlobalProperties: defined in null
- systemProperties: a programmatically registered singleton
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed**
#Configuration
#PropertySource("classpath:application.properties")
public class ElasticSearchConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ElasticSearchConfiguration.class);
#Autowired
private Properties properties;
#Value("${elasticsearch.cluster-nodes}")
private String clusterNodes;
#Value("${elasticsearch.cluster-name}")
private String clusterName;
#Value("${elasticsearch.host}")
private String elasticHost;
#Value("${elasticsearch.port}")
private String elasticPort;
#Value("${elasticsearch.protocol}")
private String elasticProtocol;
private Header[] headers = { new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")};
#Bean(name = "restClient")
public RestClient restClient(){
return RestClient.builder(new HttpHost(elasticHost, Integer.parseInt(elasticPort), elasticProtocol)).setDefaultHeaders(headers).build();
}
#Bean(name = "restHighLevelClient")
public RestHighLevelClient restHighLevelClient(){
logger.info(" elastic port = " + elasticPort + " host = " + elasticHost);
return new RestHighLevelClient(RestClient.builder(new HttpHost(elasticHost, Integer.parseInt(elasticPort), elasticProtocol)));
}
}
#Configuration
#PropertySource("classpath:application.properties")
public class
ESConfigForMasterData {
private static final Logger logger = LoggerFactory.getLogger(ElasticSearchConfiguration.class);
#Autowired
private Properties properties;
#Value("${elasticsearch.masterdata.cluster-nodes}")
private String clusterNodes;
#Value("${elasticsearch.masterdata.cluster-name}")
private String clusterName;
#Value("${elasticsearch.masterdata.host}")
private String elasticHost;
#Value("${elasticsearch.masterdata.port}")
private String elasticPort;
#Value("${elasticsearch.masterdata.protocol}")
private String protocol;
private Header[] headers = { new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")};
#Bean(name = "restHighLevelClientMasterData")
public RestHighLevelClient restHighLevelClientMasterData(){
logger.info(" elastic port = " + elasticPort + " host = " + elasticHost);
return new RestHighLevelClient(RestClient.builder(new HttpHost(elasticHost, Integer.parseInt(elasticPort), protocol)));
}
#Bean(name = "restClientMasterData")
public RestClient restClientMasterData(){
RestClientBuilder builder = RestClient.builder(new HttpHost(elasticHost, Integer.parseInt(elasticPort), protocol))
.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
#Override
public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {
return requestConfigBuilder.setConnectTimeout(60000000)
.setSocketTimeout(60000000);
}
});
//TODO: As of now we are ignoring it we need to find alternative method for this setMaxRetryTimeoutMillis
// .setMaxRetryTimeoutMillis(60000000);
//return RestClient.builder(new HttpHost(elasticHost, Integer.parseInt(elasticPort), protocol)).setDefaultHeaders(headers).build();
return builder.build();
}
}
#Repository
public class SearchDao {
private static final Logger logger = LoggerFactory.getLogger(SearchDao.class);
#Autowired
#Qualifier("restHighLevelClientMasterData")
private RestHighLevelClient restHighLevelClientMasterData;
#Autowired
#Qualifier("restClientMasterData")
private RestClient restClientMasterData;
}
I have tryed keeping #Primary & #Qualifier annotation but it doesn't work for me
please help me in sorting this issue

How to get Redis Hash Configuration such as Time To Live from application.properties at Spring Boot?

I use
#Value("${cache.host}")
private String redisHost;
#Value("${cache.port}")
private int redisPort;
I want to get timeToLive in #RedishHash from application properties. How can get this config?
#RedisHash(value = "UserModel", timeToLive = 5)
I give manually above however I want to give from application.properties
i'm not sure if you can do that from application.properties, but u can do it by configuring a RedisCacheManager bean with java based configuration like below :
#Bean
public RedisCacheManager RedisCacheManager(RedisConnectionFactory redisConnectionFactory) {
Map<String, RedisCacheConfiguration> cacheConfig = new HashMap<String, RedisCacheConfiguration>();
cacheConfig.put("UserModel", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(5)));
RedisCacheManager rdisCacheManager = new RedisCacheManager(
RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),
RedisCacheConfiguration.defaultCacheConfig(), cacheConfig);
return rdisCacheManager;
}
PS : this method should be in a class with #Configuration annotation
You can create a #Component where you are going to take the values from properties
#Component
public class RedisHashCustom {
private static String redisHashValue;
public static String getRedisHashVaue() {
return redisHashValue;
}
#Value("${application.redis.redishash.value}")
public void setRedisHashValue(String newRedisHashValue) {
redisHashValue= newRedisHashValue;
}
}
Then you need to reference as
#RedisHash(value = "#{T(com.redis.model.RedisHashCustom).getRedisHashValue() }")

Spring component depends on the configuration which load props from application.properties but props are not loaded

I have some difficulties and I can't realize how to let it work because of lack of knowledge in Spring framework.
What I'm trying to do is pretty simple. I want to use injected AppProperties in RestTemplateComponent constuctor or getRestTemplate method but all props are null.
I think I understand why.. it's because RestTemplateComponent by the order was loaded first and that's why AppProperties props are null.
Is it possible some how to tell to Spring to load AppProperties first in order to use it in RestTemplateComponent.
By the way problem occurs only on start there is no any problem when I inject it in controller and during request use these objects.
application.properties
integration.url=http://...
#Configuration
#ConstructorBinding
#ConfigurationProperties("integration")
public class AppProperties {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
#Component
public class RestTemplateComponent {
private final AppProperties appProperties;
public RestTemplateComponent(AppProperties appProperties) {
this.appProperties = appProperties;
}
#Bean
public RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
//Custom authorization
return restTemplate;
}
}
Ok, finally I did it work by adding annotation #PropertySource({"classpath:application.properties"}) within my AppProperties class and then when I try to autowire AppProperties in SpringBootApplication I see in debug that props are initialized.

Spring, inject properties in a Bean instance based one of that Bean's field value, is it possible?

I have a Pojo I use to configure webservices' clients:
public class ServiceConfig {
private String url;
private String endpoint;
private String serviceName;
public ServiceConfig(String serviceName) {
super();
this.serviceName = serviceName;
}
}
Now, this is what my application.properties file looks like:
service1.url=http://localhost:8087/
service1.endpoint=SOME_ENDPOIT1
service2.url=http://localhost:8085/
service2.endpoint=SOME_ENDPOIT2
service3.url=http://localhost:8086/
service3.endpoint=SOME_ENDPOIT3
service4.url=http://localhost:8088/
service4.endpoint=SOME_ENDPOIT4
What I'm trying to achieve is for Spring to inject the correct properties when I instantiate ServiceConfig like this:
ServiceConfig sc = new ServiceConfig("service1");
Is it possible?
Are you using just spring or also spring-boot?
What about injecting org.springframework.core.env.Environment to your pojo and configuring it with it.
so something like this could work:
public class ServiceConfig {
private String url;
private String endpoint;
private String serviceName;
public ServiceConfig(String serviceName, Environment env) {
// TODO assert on serviceName not empty
this.serviceName = serviceName;
this.url = env.getProperty(serviceName.concat(".url");
this.endpoint = env.getProperty(serviceName.concat(".endpoint");
}
}
I guess there could be a simpler/more elegant solution, but I don't know your case.
spring-boot version
well with spring boot just define your pojo (field names must match property names)
public class ServiceConfig {
private String url;
private String endpoint;
// getters setters
}
and then in some configuration you can do this (note: value in ConfigurationProperties is prefix of your configuration in application.properties):
#Configuration
public class ServicesConfiguration {
#Bean
#ConfigurationProperties("service1")
ServiceConfig service1(){
return new ServiceConfig();
}
#Bean
#ConfigurationProperties("service2")
ServiceConfig service2(){
return new ServiceConfig();
}
}

Spring boot common application properties

Spring boot application properties needs to follow convention from https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html when we use any DB like cassandra/mongo. In case if we want to declare our own properties for DB setup instead of spring-boot convention, what are all the steps we need to do for setting up DB?
You can do this: Spring boot - custom variables in Application.properties
or you can just create your own property in your application.properties file like:
my.property.someDb.hostname=http://wherever.comand then reference to it in your code like:
#Value("${my.property.someDb.hostname}")
private String someDbHostname;
Update 1:
If you want to create the MongoDb with your own properties you have to define the right Java Beans in an #Configuration file. For MongoDB it could look like the following:
#Configuration
public class MyMongoConfig extends AbstractMongoConfiguration{
#Value("${my.property.someDb.hostname}")
private String someDbHostname;
#Value("${my.property.someDb.myOwnPortDefinition}")
private int myOwnPortDefinition;
#Value("${my.property.someDb.myDatabasename}")
private String myDatabasename;
#Override
protected String getDatabaseName() {
return myDatabasename;
}
#Override
#Bean
public Mongo mongo() throws Exception{
return new MongoClient(someDbHostname, myOwnPortDefinition );
}
#Bean
public MongoTemplate mongoTemplate() throws Exception{
return new MongoTemplate(mongo(), getDatabaseName());
}
}
These are the essential steps you need in order to get a data source like Jdbc, mongodb set up in Spring Boot
Need a #Configuration class that has transaction management enabled
on it
Read the environment properties for the datasource i.e. dataSource
url, username, password etc.
Create beans for datasource, session factory, transaction manager
etc.
Once all of the above setup, use this #Configuration in your
consumer to initialize the spring application context
Here are some snippets of wiring mongodb datasource in spring boot
DataSourceConfiguration.java
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {"com.example.xyz"})
public class DatabaseEntityConfiguration {
public static final String DATABASE_ENTITY_DATA_SOURCE = "databaseDataSource";
public static final String DATABASE_HIBERNATE_PROPERTIES = "databaseHibernateProperties";
public static final String DATABASE_ENTITY_SESSION_FACTORY = "databaseSessionFactory";
public static final String DATABASE_ENTITY_TRANSACTION_MANAGER = "databaseTransactionManager";
public static final String DATABASE_ENTITY_DB_CONFIG_DAO = "dmdatabaseDbConfigDao";
public static final String DATABASE_ENTITY_DB_CONFIG_SERVICE = "dmdatabaseDbConfigService";
private static final String ENTITY_PACKAGE = "com.example.xyz.database.entity";
#Autowired
private org.springframework.core.env.Environment environment;
#Bean(name = DATABASE_ENTITY_DATA_SOURCE)
public DataSource databaseEntitydataSource() throws PropertyVetoException {
// mongodb properties
String driverClass = environment.getProperty("databaseEntity.mongodb.driverClassName");
String mongodbUrl = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.url");
String user = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.username");
String password = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.password");
Preconditions.checkArgument(StringUtils.isNotBlank(driverClass), "The property mongodb driverClass must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(mongodbUrl), "The property mongodb mongodbUrl must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(user), "The property mongodb user must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(password), "The property mongodb password must not be null or blank");
dataSource.setDriverClass(driverClass);
dataSource.setmongodbUrl(mongodbUrl);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}
#Bean(name = DATABASE_ENTITY_SESSION_FACTORY)
public AnnotationSessionFactoryBean databaseEntitySessionFactory() throws PropertyVetoException {
AnnotationSessionFactoryBean annotationSessionFactoryBean = new AnnotationSessionFactoryBean();
annotationSessionFactoryBean.setDataSource(databaseEntitydataSource());
annotationSessionFactoryBean.setPackagesToScan(ENTITY_PACKAGE);
annotationSessionFactoryBean.setAnnotatedClasses(DBConfig.class);
annotationSessionFactoryBean.setHibernateProperties(databaseEntityHibernateProperties());
return annotationSessionFactoryBean;
}
#Bean(name = DATABASE_ENTITY_TRANSACTION_MANAGER)
public HibernateTransactionManager databaseEntityTransactionManager() throws PropertyVetoException {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(databaseEntitySessionFactory().getObject());
return transactionManager;
}
}

Resources