Set int value from properties file using spring - spring

Trying to bind int value from properties file using spring
But everytime getting below exception :
Failed to instantiate [org.springframework.amqp.rabbit.connection.ConnectionFactory]:
Factory method 'connectionFactory' threw exception; nested exception is java.lang.NumberFormatException: For input string: \"${rabitmq.server.host.connectionclosetimeout}\"
Caused by: java.lang.NumberFormatException: For input string: \"${rabitmq.server.host.connectionclosetimeout}\""}}
My properties file look like below :
rabitmq.server.host.connectionclosetimeout=30000
My Bean
#Value("${rabitmq.server.host.connectionclosetimeout}")
private int connectionCloseTimeOut;
Configuration Class
#Configuration
#PropertySource("classpath:config/service.properties")
public class RabbitMqConfiguration {
#Value("${rabitmq.server.host.connectionclosetimeout}")
private Integer connectionCloseTimeOut;
/**
* Establishing connection
*
* #return
*/
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host);
connectionFactory.setCloseTimeout(connectionCloseTimeOut);
return connectionFactory;
}
}
If I add below bean then its working fine. But I want to work without below bean
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
I have also tried with below method :
#Value("#{new Integer.parseInt('${rabitmq.server.host.connectionclosetimeout}')}")
private int connectionCloseTimeOut;
It's also not working.
Please suggest what is way to get it working.

Your SPEL isn't correct. This should work
#Value("#{ T(java.lang.Integer).parseInt('${rabitmq.server.host.connectionclosetimeout}') }")
private int connectionCloseTimeOut;
Try it out

Below is from the doc of PropertySource https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/PropertySource.html
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.
So if you are using #PropertySource, to resolve the ${...} placeholders in #Value annotations you must register the PropertySourcesPlaceholderConfigurer either by
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
or by configuring the PSPC in the XML as below
<context:property-placeholder location="classpath:config/service.properties" />
Please refer the spring JIRA ticket https://jira.spring.io/browse/SPR-8539 and the SO thread #Value not resolved when using #PropertySource annotation. How to configure PropertySourcesPlaceholderConfigurer? for some more reference.

Related

SpEL KafkaListener. How can i inject custom deserializer through properties?

I am using spring.
I have a configured ObjectMapper for the entire project and I use it to set up a kafka deserializer.
And then I need a custom kafka deserializer to be used in KafkaListener.
I'm configuring KafkaListener via autoconfiguration, not via #Configuration class.
#Component
#RequiredArgsConstructor
public class CustomMessageDeserializer implements Deserializer<MyMessage> {
private final ObjectMapper objectMapper;
#SneakyThrows
#Override
public MyMessage deserialize(String topic, byte[] data) {
return objectMapper.readValue(data, MyMessage.class);
}
}
If i do like this
#KafkaListener(
topics = {"${topics.invite-user-topic}"},
properties = {"value.deserializer=com.service.deserializer.CustomMessageDeserializer"}
)
public void receiveInviteUserMessages(MyMessage myMessage) {}
I received KafkaException: Could not find a public no-argument constructor
But with public no-argument constructor in CustomMessageDeserializer class i am getting NPE because ObjectMapper = null. It creates and uses a new class, not a spring component.
#KafkaListener supports SpEL expressions.
And I think that this problem can be solved using SpEL.
Do you have any idea how to inject spring bean CustomMessageDeserializer with SpEL?
There are no easy ways to do it with SPeL.
Analysis
To get started, see the JavaDoc for #KafkaListener#properties:
/**
*
* SpEL expressions must resolve to a String ...
*/
The value of value.deserializer is used to instantiate the specified deserializer class. Let's follow the call chain:
You specify this value in the #KafkaListener annotation, then you are probably not creating a bean of the ConsumerFactory.class. So Spring creates this bean class itself - see KafkaAutoConfiguration#kafkaConsumerFactory.
Next is the creation of the returned object new DefaultKafkaConsumerFactory(...) as ConsumerFactory<?,?> using the constructor for default delivery expressions keyDeserializer/valueDeserializer = () -> null
This factory is used to create a Kafka consumer (The entry point is the constructor KafkaMessageListenerContainer#ListenerConsumer, then KafkaMessageListenerContainer.this.consumerFactory.createConsumer...)
In the KafkaConsumer constructor, the valueDeserializer object is being created, because it is null (for the default factory of point 2 above):
if (valueDeserializer == null) {
this.valueDeserializer = config.getConfiguredInstance(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, Deserializer.class);
The implementation of config.getConfiguredInstance involves instantiating your deserializer class via a parameterless constructor using reflection and your String "com.service.deserializer.CustomMessageDeserializer" class name
Solutions
To use value.deserializer with your customized ObjectMapper, you must create the ConsumerFactory bean yourself using the setValueDeserializer(...) method. This is also mentioned in the second Important part of the JSON.Mapping_Types.Important documentation
If you don't want to create a ConsumerFactory bean, and also don't have complicated logic in your deserializer (you only have return objectMapper.readValue(data, MyMessage.class);), then register DefaultKafkaConsumerFactoryCustomizer:
#Bean
// inject your custom objectMapper
public DefaultKafkaConsumerFactoryCustomizer customizeJsonDeserializer(ObjectMapper objectMapper) {
return consumerFactory ->
consumerFactory.setValueDeserializerSupplier(() ->
new org.springframework.kafka.support.serializer.JsonDeserializer<>(objectMapper));
}
In this case, you don't need to create your own CustomMessageDeserializer class (remove it) and Spring will automatically parse the message into your MyMessage.
#KafkaListener annotation should also not contains the property properties = {"value.deserializer=com.my.kafka_test.component.CustomMessageDeserializer"}. This DefaultKafkaConsumerFactoryCustomizer bean will automatically be used to configure the default ConsumerFactory<?, ?> (see the implementation of the KafkaAutoConfiguration#kafkaConsumerFactory method)
Here how it works for me:
#KafkaListener(topics = "${solr.kafka.topic}", containerFactory = "batchFactory")
public void listen(List<SolrInputDocument> docs, #Header(KafkaHeaders.BATCH_CONVERTED_HEADERS) List<Map<String, Object>> headers, Acknowledgment ack) throws IOException {...}
And then I have 2 beans defined in my Configuration
#Profile("!test")
#Bean
#Autowired
public ConsumerFactory<String, SolrInputDocument> consumerFactory(KafkaProperties properties) {
Map<String, Object> props = properties.buildConsumerProperties();
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
DefaultKafkaConsumerFactory<String, SolrInputDocument> result = new DefaultKafkaConsumerFactory<>(props);
String validatedKeyDeserializerName = KafkaMessageType.valueOf(keyDeserializerName).toString();
ZiDeserializer<SolrInputDocument> deserializer = ZiDeserializerFactory.getInstance(validatedKeyDeserializerName);
result.setValueDeserializer(deserializer);
return result;
}
#Profile("!test")
#Bean
#Autowired
public ConcurrentKafkaListenerContainerFactory<String, SolrInputDocument> batchFactory(ConsumerFactory<String, SolrInputDocument> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, SolrInputDocument> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory);
factory.setBatchListener(true);
factory.setConcurrency(2);
ExponentialBackOffWithMaxRetries backoff = new ExponentialBackOffWithMaxRetries(10);
backoff.setMultiplier(3); // Default is 1.5 but this seems more reasonable
factory.setCommonErrorHandler(new DefaultErrorHandler(null, backoff));
// Needed for manual commits
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
return factory;
}
Note that the interface ZiDeserializer<SolrInputDocument> deserializeris my interface and ZiDeserializerFactory.getInstance(validatedKeyDeserializerName); returns my custom implementation of ZiDeserializer. And ZiDeserializer extends org.apache.kafka.common.serialization.Deserializer. This works for me

Setting class level annotation values from application.yml

I was trying to externalise my configuration of a class level annotation using allication.yaml. But the spring is not loading it right. Any idea how to do this ?
Here is my Service classI am trying to set
#Service
#DefaultProperties(threadPoolProperties = {
#HystrixProperty(name = "coreSize", value =
"${cyclone.hystrix.lease.thread.coreSize}") })
public class LeaseService {
}
And application.yml
cyclone:
hystrix:
lease:
thread:
coreSize: 10
Getting an error --
java.lang.IllegalArgumentException: bad property value. property name 'coreSize'. Expected int value, actual = ${cyclone.hystrix.lease.thread.coreSize}
I can load the same property using #Value("${cyclone.hystrix.lease.thread.coreSize}"). But not working on the above mentioned case.
Any help on how to properly configure this ?
In order to make spring evaluate placeholders you need to register a PropertySourcesPlaceholderConfigurer bean using a static #Bean method when using #Configuration classes as follows:
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
According to the JavaDoc:
Specialization of PlaceholderConfigurerSupport that resolves ${...} placeholders within bean definition property values and #Value annotations against the current Spring Environment and its set of PropertySources.

#Value is not injected

I'm trying to inject value from properties file into a spring configuration class as follows:
#Configuration
#EnableWebMvc
public class ImagesContext {
#Autowired
#Value("${some.property.com}")
private String property;
#Bean
public MyClass getMyClass() {
return new MyClass(property);
}
}
But the property is not injected correctly. Instead, when I debug, I see that the property string contains ${some.property.com} and not the string value itself.
The #Value annotation is processed by AutowiredAnnotationBeanPostProcessor which is typically registered if you have a <component-scan> or <annotation-config> configuration in XML (or directly with a bean definition). You need to add either of those, probably <annotation-config>
Add the following bean declaration in your Config class if not done already
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
In order for #Value annotations to work PropertySourcesPlaceholderConfigurer should be registered. It is done automatically when using <context:property-placeholder> in XML, but should be registered as a static #Bean when using #Configuration.

Evaluating Spring #Value annotation as primitive boolean

I have a Spring #Configuration annotated class, MappingsClientConfig, with a boolean field as:
#Value("${mappings.enabled:true}")
private boolean mappingsEnabled;
This class is imported into another Spring annotated class like so :
#Configuration
#Import(MappingsClientConfig.class)
public class LookupManagerConfig {
When instantiating the class via Spring context in a test-case, the container is unable to parse the string into the boolean field mappingsEnabled and I get:
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private boolean com.barcap.tcw.mappings.economic.client.config.EconomicMappingsClientConfig.economicMappingsEnabled; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'boolean'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [${mappings.enabled:true}]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:502)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:84)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:282)
... 138 more
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'boolean'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [${mappings.enabled:true}]
at org.springframework.beans.SimpleTypeConverter.convertIfNecessary(SimpleTypeConverter.java:61)
at org.springframework.beans.SimpleTypeConverter.convertIfNecessary(SimpleTypeConverter.java:43)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:718)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:703)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:474)
... 140 more
Caused by: java.lang.IllegalArgumentException: Invalid boolean value [${mappings.enabled:true}]
at org.springframework.beans.propertyeditors.CustomBooleanEditor.setAsText(CustomBooleanEditor.java:124)
at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:416)
at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:388)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:157)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:93)
at org.springframework.beans.SimpleTypeConverter.convertIfNecessary(SimpleTypeConverter.java:49)
... 144 more
Any leads as to what am I missing?
Its an old thread but if you still want to inject Non-String values using #Value Spring annotation, do this:
#Value("#{new Boolean('${item.priceFactor}')}")
private Boolean itemFactorBoolean;
#Value("#{new Integer('${item.priceFactor}')}")
private Integer itemFactorInteger;
Works for me on Spring boot 1.5.9 with Java 8.
Looks like you're missing the PropertyPlaceholderConfigurer. You need to register it as a bean factory post processor. Theoretically this could be done like this:
public class PostProcessorConfig {
#Bean
public BeanFactoryPostProcessor getPP() {
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setLocations(new Resource[]{new ClassPathResource("/my.properties")});
return configurer;
}
}
However, there seems to be a bug that causes other issues doing this from a java based configuration. See ticket for workarounds.
This is how it was solved in our project, as the other answers didn't work for us. We were using spring batch, also.
main job config:
#Configuration
#EnableBatchProcessing
#PropertySource("classpath:application.properties")
public class MainJobConfiguration {
#Autowired
PipelineConfig config;
#Bean
public PipelineConfig pipelineConfig(){
return new PipelineConfig();
}
// To resolve ${} in #Value
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
// job stuff ...
}
properties config loader:
public class PipelineConfig {
#Value("${option}")
private boolean option;
}
Note how the #Value is in the PipelineConfig, but the actual properties from which the option is loaded, is specified in the job class.
You even do not need a properties file, e.g.:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />

Spring #Configuration class needs to be autowired

I've created a Spring #Configuration annotated class and I want to autowire a ResourceLoader to it so that I can use it in one of the #Bean methods to lookup a file given by a String. When I am running the app and initialising the context I get a NPE accessing the autowired field, and in debug mode it is shown as being null/not set. Am I wrong expecting the resourceLoader to be present? Am I wrong asserting the autowiring of the Configuration bean happens before its methods get called? The xml configuration loading this bean is tagged with <context:annotation-config/>
#Configuration
public class ClientConfig {
#Autowired
private ResourceLoader resourceLoader;
public #Bean
String configHome() {
return System.getProperty("CONFIG_HOME");
}
public #Bean
PropertiesFactoryBean appProperties() {
String location = "file:" + configHome() + "/conf/webservice.properties";
PropertiesFactoryBean factoryBean = new PropertiesFactoryBean();
factoryBean.setLocation(resourceLoader.getResource(location));
return factoryBean;
}
}
I'm not sure whether this is a bug or is the expected behavior. Sometimes it worked for me, sometimes didn't. Anyway, there is another way of achieving what you want:
public #Bean PropertiesFactoryBean appProperties(ResourceLoader resourceLoader) {
// resourceLoader is injected correctly
...
}

Resources