Getting qualifier names from initialized bean objects - spring

I have two beans of the same type;
#Bean
public RestTemplate jsonTemplate() {
return new RestTemplate();
}
#Bean
public RestTemplate xmlTemplate() {
return new RestTemplate();
}
And I autowire both beans into a list as follows;
#Autowired
private List<RestTemplate> templates;
The list templates will have both beans inside with size=2.
From this list, how can I get their names (["jsonTemplate", "xmlTemplate"])?

It was really simple...
Just doing;
#Autowired
private Map<String, RestTemplate> templates;
will let Spring to insert the names as keys and the beans themselves as the values in
the map.
It seems Spring just stops keeping track of the naming after the injection. So I don't know if there is any other way (or, if even possible, simpler way) than this?

You could use map of beans:
#Bean
public Map<String, RestTemplate> templateMap(RestTemplate jsonTemplate, RestTemplate xmlTemplate) {
Map<String, RestTemplate> map = new HashgMap<>();
map.put("jsonTemplate", jsonTemplate);
map.put("xmlTemplate", xmlTemplate);
return map;
}
#Autowired
private Map<String, RestTemplate> templates;

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

How to create a second RedisTemplate instance in a Spring Boot application

According to this answer, one RedisTemplate cannot support multiple serializers for values. So I want to create multiple RedisTemplates for different needs, specifically one for string actions and one for object to JSON serializations, to be used in RedisCacheManager. I'm using Spring Boot and the current RedisTemplate is autowired, I'm wondering what's the correct way to declare a second RedisTemplate instance sharing the same Jedis connection factory but has its own serializers?
Tried something like this in two different components,
Component 1 declares,
#Autowired
private RedisTemplate redisTemplate;
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Instance.class));
Component 2 declares,
#Autowired
private StringRedisTemplate stringRedisTemplate;
In this case the two templates actually are the same. Traced into Spring code and found component 1's template got resolved to autoconfigured stringRedisTemplate.
Manually calling RedisTemplate's contructor and then its afterPropertiesSet() won't work either as it complains no connection factory can be found.
I know this request probably is no big difference from defining another bean in a Spring app but not sure with the current Spring-Data-Redis integration what's the best way for me to do. Please help, thanks.
you can follow two ways how to use multiple RedisTemplates within one Spring Boot application:
Named bean injection with #Autowired #Qualifier("beanname") RedisTemplate myTemplate and create the bean with #Bean(name = "beanname").
Type-safe injection by specifying type parameters on RedisTemplate (e.g. #Autowired RedisTemplate<byte[], byte[]> byteTemplate and #Autowired RedisTemplate<String, String> stringTemplate).
Here's the code to create two different:
#Configuration
public class Config {
#Bean
public RedisTemplate<String, String> stringTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> stringTemplate = new RedisTemplate<>();
stringTemplate.setConnectionFactory(redisConnectionFactory);
stringTemplate.setDefaultSerializer(new StringRedisSerializer());
return stringTemplate;
}
#Bean
public RedisTemplate<byte[], byte[]> byteTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> byteTemplate = new RedisTemplate<>();
byteTemplate.setConnectionFactory(redisConnectionFactory);
return byteTemplate;
}
}
HTH, Mark

Spring #Autowire instance variable without xml config

I am trying to autowire java.util.concurrent.ConcurrentHashMap to enforce a singleton implementation without using any spring-config.xml file. I'm fairly certain there is a way to do this via some spring annotation directly at the Java side of things, is this possible?
#Component
public class MyClass{
//Some annotation goes here?
private ConcurrentHashMap<String, String> myMap;
}
Actualy, you can not inject MAP.
But you can wrap your map in another class then inject it.
#Component
Public class MapWrapper {
public Map<String, String> map = newConcurrentHashMap<String, String>();
}
...
#Inject
private MapWrapper wrapper;
...

Spring java config: bean config after component scan

I have the following configuration:
#Configuration
#ComponentScan("com.xyz.svc")
public class SvcConfig {
#Autowired private Filter filter1;
#Autowired private Filter filter2;
#Autowired private Filter filter3;
#Bean
public List<Filter> filters() {
// Filters are added in the desired order of execution
return ImmutableList.of(
filter1,
filter2,
filter3);
}
}
When leadFilters() method is run all the components that it depends on (ie filter1, filter2, filter3) are null. Basically, these components are registered through #ComponentScan. The problem is leadFilters() method is getting executed before #ComponentScan.
How do I make this work?
Basically, you can't, reliably. A #Configuration class is a #Component that is meant to register bean definitions through #Bean annotated methods. If a request for a bean (handled through a #Bean method) comes in before the BeanPostProcessor that handles #Autowired, then you will see the behavior you are describing.
Note that the following will cause you problems as Spring won't know which to inject.
#Autowired
private Filter filter1;
#Autowired
private Filter filter2;
#Autowired
private Filter filter3;
Assuming this was just an example, you could refactor so that instead of having #Component classes for these filters, you instead declare #Bean methods for them.
#Bean
public Filter filter1() {
return new FilterImpl1();
}
#Bean
public Filter filter2() {
return new FilterImpl2();
}
#Bean
public Filter filter3() {
return new FilterImpl3();
}
You can then use these beans in your other #Bean method
#Bean
public List<Filter> filters() {
// Filters are added in the desired order of execution
return ImmutableList.of(
filter1(),
filter2(),
filter3());
}

How do I set #Qualifier without XML in Spring 3+

I'm using the below configuration setup. The #configuration class loads the property file, and then there is an arraylist that produced which extracts the relevant chunks of the property file in a way that the classes that depend on barUserList and fooUserList can consume easily. They don't even know that it came from a property file. Huzzah for DI!
My problem comes when I try to tell Spring which one of these I want. Class Foo wants fooUserList so I should be able to use the #Qualifier annotation, but I can't find a way to /set/ the qualifier outside of XML.
So my question is this, how do I set the Qualifier for these two Spring beans in Javaland? Zero XML config is a big goal for me. I know that you can set #name and the #qualifier mechanism for Spring will default to the #name, but I'd like to avoid using that. I don't like things that "default to" other things.
I'm using Spring 3.2.5.RELEASE
#Configuration
public class AppConfig {
#Bean
Properties loadProperties() throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("checker.properties"));
return properties;
}
#Bean
#Autowired
ArrayList<String> barUserList(Properties properties) {
ArrayList<String> barUsernames = new ArrayList<String>();
Collections.addAll(barUsernames, properties.getProperty("site.bar.watchedUsernames", "").split(","));
return barUsernames;
}
#Bean
#Autowired
ArrayList<String> fooUserList(Properties properties) {
ArrayList<String> fooUsernames = new ArrayList<String>();
Collections.addAll(fooUsernames, properties.getProperty("site.foo.watchedUsernames", "").split(","));
return fooUsernames;
}
}
One way could be by defining a name for the #Bean and using it on #Qualifier as follows:
#Bean(name="barUserList")
#Autowired
ArrayList<String> barUserList(Properties properties) {
ArrayList<String> barUsernames = new ArrayList<String>();
Collections.addAll(barUsernames, properties.getProperty("site.bar.watchedUsernames", "").split(","));
return barUsernames;
}
and within the use you could have something like:
// ...
#Autowired
#Qualifier("barUserList")
private List<String> userList;
// ...

Resources