I am trying to use HashMap by putting some key-value pair (very very basic). But I am facing a weird issue. The key that I am putting there is not visible when I iterate over it rather it creates the key with the class name. Here is the code snippet:
#Configuration
public class AppConfig {
#Bean
public Map<String, Command> allCommands(Command command1, Command command2) {
Map<String, Command> map = new HashMap<>();
map.put("first", command1);
map.put("second", command2);
return map;
}
#Bean
public Map<String, RouterStrategy> allStrategies(KafkaRouter kafkaRouter) {
Map<String, RouterStrategy> map = new HashMap<>();
map.put("kafka", kafkaRouter);
return map;
}
}
The class which is using this bean:
#Service
public class Postman {
private Map<String, RouterStrategy> allStrategies;
#Autowired
public Postman(Map<String, RouterStrategy> allStrategies) {
this.allStrategies = allStrategies;
}
public void process(String strategy,Envelope envelope) throws Exception{
allStrategies.forEach((k,v) -> logger.info(k + " -> " + v));
RouterStrategy routerStrategy = allStrategies.get(strategy);
routerStrategy.routeMessage(envelope);
}
}
The method which calls this process() method is as below:
public class SomeCommand implements Command {
Postman postman;
public SomeCommand(Postman postman) {
this.postman = postman;
}
#Override
public void execute(Envelope envelope) throws Exception {
postman.process("kafka",envelope);
}
}
Now, the problem here is if I run the code, process() method of the Postman class should print the strategy map with "kafka" as one of the keys. But it prints below output-
applog.msg=kafkaRouter -> KafkaRouter{kafkaTemplate=org.springframework.kafka.core.KafkaTemplate#79708}
Pretty weird, whatever the key I am setting is simply ignored rather it creates this Map by using characters of the class name (KafkaRouter -> kafkaRouter). This thing is same if I add an additional key-value pair in the strategy map. For example, if I modify the strategy map like mentioned below-
#Bean
public Map<String, RouterStrategy> allStrategies(KafkaRouter kafkaRouter, RestRouter restRouter) {
Map<String, RouterStrategy> map = new HashMap<>();
map.put("kafka", kafkaRouter);
map.put("rest", restRouter);
return map;
}
Strategy interface-
public interface RouterStrategy {
void routeMessage(Envelope envelope) throws Exception;
}
Strategy implementation-
#Service
public class KafkaRouter implements RouterStrategy{
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
public KafkaRouter(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
#Override
public void routeMessage(Envelope envelope) throws Exception {
// Logic to get additional details like Kafka topic name, etc.
sendMessage(envelope.getContent());
}
#Override
public String toString() {
return "KafkaRouter{" +
"kafkaTemplate=" + kafkaTemplate + '}';
}
// Kafka publisher logic
private void sendMessage(String content) throws Exception {
}
}
In this case, two keys are printed kafkaRouter and restRouter. Since it can't "find the key", it throws NullPointerException. Anything I am missing? TIA
Other details:
Springboot version - 2.3.10.RELEASE
JDK - OpenJDK Runtime Environment Zulu11.40+15-NV (build 11.0.7.0.101+5-LTS)
Related
I am writing a service that gets an input based on which I need to call certain impl of one service. This input is a list of names of impls needs to called.
public interface Processor {
Map<String, String> execute();
}
#Service("BUCKET_PROCESSOR")
public class BucketProcessor implements Processor {
..... //first impl
}
#Service("QUERY_PROCESSOR")
public class QueryProcessor implements Processor {
..... //second impl
}
#Service("SQL_PROCESSOR")
public class SQLProcessor implements Processor {
..... //third impl
}
then I have a service where I want to inject a map of all these impls so that I can iterate over input and call respective impl.
#Service
public class MyAysncClient {
#Autowired
private Map<String, Processor> processorMap;
public void execute(List<String> processors) {
List<Future> tasks = new ArrayList<>();
for (String p : processors) {
final Processor processor = this.processorMap.get(p);
processor.execute()
....
}
}
}
you can just use getBeansOfType(Processor.class):
Returns a Map with the matching beans, containing the bean names as keys and the corresponding bean instances as values
#Bean
public Map<String, Processor> processorMap(ApplicationContext context) {
return context.getBeansOfType(Processor.class);
}
Yes, you can - spring has this feature enabled by default. Namely, you can define inject a Map<String, Processor> into the spring bean.
This will instruct spring to find all beans which are implementations of Processor interface and these will be values of the map, the corresponding keys will be bean names.
So the code presented in the question should work.
Check the documentation of well-known #Autowired annotation.
In the section "Autowiring Arrays, Collections, and Maps" it states the following:
In case of an array, Collection, or Map dependency type, the container autowires all beans matching the declared value type. For such purposes, the map keys must be declared as type String which will be resolved to the corresponding bean names. Such a container-provided collection will be ordered, taking into account Ordered and #Order values of the target components, otherwise following their registration order in the container. Alternatively, a single matching target bean may also be a generally typed Collection or Map itself, getting injected as such.
See This example - the relevant part of it is where the map is injected into the test.
A better and elegant way to do the same is
Define a Service locator pattern using below code
#Configuration
public class ProcessorConfig {
#Bean("processorFactory")
public FactoryBean<?> serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
factoryBean.setServiceLocatorInterface(ProcessorFactory.class);
return factoryBean;
}
}
public interface ProcessorFactory {
Processor getProcessor(ProcessorTypes processorTypes);
}
then
public interface Processor {
Map<String, String> execute();
}
#Component(ProcessorTypes.ProcessorConstants.BUCKET_PROCESSOR)
#Slf4j
public class BucketProcessor implements Processor {
#Override
public Map<String, String> execute() {
return Collections.singletonMap("processor","BUCKET_PROCESSOR");
}
}
#Component(ProcessorTypes.ProcessorConstants.QUERY_PROCESSOR)
#Slf4j
public class QueryProcessor implements Processor {
#Override
public Map<String, String> execute() {
return Collections.singletonMap("processor","QUERY_PROCESSOR");
}
}
#Component(ProcessorTypes.ProcessorConstants.SQL_PROCESSOR)
#Slf4j
public class SqlProcessor implements Processor {
#Override
public Map<String, String> execute() {
return Collections.singletonMap("processor","SQL_PROCESSOR");
}
}
Now define your service injecting the factory
#Service
#RequiredArgsConstructor
#Slf4j
public class ProcessorService {
private final ProcessorFactory processorFactory;
public void parseIndividual(ProcessorTypes processorTypes) {
processorFactory
.getProcessor(processorTypes)
.execute();
}
public void parseAll(List<ProcessorTypes> processorTypes) {
processorTypes.forEach(this::parseIndividual);
}
}
In client, you can execute in below way
processorService.parseAll(Arrays.asList(ProcessorTypes.SQL, ProcessorTypes.BUCKET, ProcessorTypes.QUERY));
processorService.parseIndividual(ProcessorTypes.BUCKET);
If you want to expose as REST API you can do it in below way
#RestController
#RequestMapping("/processors")
#RequiredArgsConstructor
#Validated
public class ProcessorController {
private final ProcessorService processorService;
#GetMapping("/process")
public ResponseEntity<?> parseContent(#RequestParam("processorType") #Valid ProcessorTypes processorTypes) {
processorService.parseIndividual(ProcessorTypes.BUCKET);
return ResponseEntity.status(HttpStatus.OK).body("ok");
}
#GetMapping("/process-all")
public ResponseEntity<?> parseContent() {
processorService.parseAll(Arrays.asList(ProcessorTypes.SQL, ProcessorTypes.BUCKET, ProcessorTypes.QUERY));
return ResponseEntity.status(HttpStatus.OK).body("ok");
}
}
Hope your problem gets resolved by the solution
I think this will help you , add bean configuration into configuration file
#Bean(name = "mapBean")
public Map<String, Processor > mapBean() {
Map<String, Processor > map = new HashMap<>();
//populate the map here
return map;
}
in your service
#Service
public class MyAysncClient {
#Autowired
#Qualifier("mapBean")
private Map<String, Processor> processorMap;
public void execute(List<String> processors) {
List<Future> tasks = new ArrayList<>();
for (String p : processors) {
final Processor processor = this.processorMap.get(p);
processor.execute()
....
}
}
}
by the way if you dont need name of the beans (according your example) so define a list , spring will inject all bean defined as service on the same interface
#Autowired
private List<Processor> processors; // include all defined beans
after that iterate each of them and call execute method.
Yes, you can, but it needs some improvements to your current code in order to make it work in this way.
First of all you have to add the getProcessorName method to the Processor interface:
public interface Processor {
Map<String, String> execute();
String getProcessorName();
}
When you implement it, you should set it's name in returning of getProcessorName method
#Service
public class QueryProcessor implements Processor {
//...
#Override
public String getProcessorName() {
return "QUERY_PROCESSOR";
}
}
Then you must create a spring configuration or add bean creation to the existing one
#Configuration
public class MyShinyProcessorsConfiguration {
#Bean
#Qualifier("processorsMap")
public Map<String, Processor> processorsMap(List<Processor> processors) {
Map<String, Processor > procMap = new HashMap<>();
processors.forEach(processor -> procMap.put(processor.getProcessorName(), processor);
return procMap;
}
}
...and then you can simply add your processors map to any component
#Service
public class MyAysncClient {
#Autowired
#Qualifier("processorsMap")
private Map<String, Processor> processorsMap;
}
I set environment variables
MY_APP_MY_MAP_A1=a
MY_APP_MY_MAP_A2=b
MY_APP_JUSTMAP_A1=a
MY_APP_JUSTMAP_A2=b
to configure my Spring Boot (2.1.7.RELEASE) application via #ConfigurationProperties:
#SpringBootApplication
#EnableConfigurationProperties(MyApp.MyProperties.class)
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
#Bean
public CommandLineRunner cmd(MyProperties props) {
return args -> {
System.out.println("myMap: " + props.getMyMap());
System.out.println("justmap: " + props.getJustmap());
};
}
#ConfigurationProperties(prefix = "my.app")
#Getter
#Setter
static class MyProperties {
private Map<String, String> myMap;
private Map<String, String> justmap;
}
}
Setting a Map<String,String> doesn't work when the variable name contains an upper letter (is in camelcase), otherwise everything works fine:
myMap: null
justmap: {a1=a, a2=b}
Is there a way how to do it?
If you have following env. variables passed
MY_APP_MYMAP_A1=a
MY_APP_MYMAP_A2=b
MY_APP_JUSTMAP_A1=a
MY_APP_JUSTMAP_A2=b
The below code prints what you are expecting
#SpringBootApplication
#EnableConfigurationProperties(TestSpringBootApplication.MyProperties.class)
public class TestSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringBootApplication.class, args);
}
#Bean
public CommandLineRunner cmd(MyProperties props) {
return args -> {
System.out.println("myMap: " + props.getMyMap());
System.out.println("justmap: " + props.getJustmap());
};
}
#ConfigurationProperties(prefix = "my.app")
static class MyProperties {
private Map<String, String> myMap;
private Map<String, String> justmap;
public Map<String, String> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public Map<String, String> getJustmap() {
return justmap;
}
public void setJustmap(Map<String, String> justmap) {
this.justmap = justmap;
}
}
}
The output is below
2019-09-04 16:00:07.336 INFO 21204 --- [ main] c.e.demo.TestSpringBootApplication : Started TestSpringBootApplication in 1.012 seconds (JVM running for 2.219)
myMap: {a1=a, a2=b}
justmap: {a1=a, a2=b}
For more details on the rules around this relaxed binding check the documentation here
Elaborating on the Shailendra's answer here's a relevant quote from docs:
To convert a property name in the canonical-form to an environment variable name you can follow these rules:
Replace dots (.) with underscores (_).
Remove any dashes (-).
Convert to uppercase.
I need to read all properties in application.properties file in a map
In the code below the property test has the respective value but the map is empty. How can I fill "map" with the values in the application.properties file without adding a prefix to the properties.
This is my application.properties file
AAPL=25
GDDY=65
test=22
I'm using #ConfigurationProperties like this
#Configuration
#ConfigurationProperties("")
#PropertySource("classpath:application.properties")
public class InitialConfiguration {
private HashMap<String, BigInteger> map = new HashMap<>();
private String test;
public HashMap<String, BigInteger> getMap() {
return map;
}
public void setMap(HashMap<String, BigInteger> map) {
this.map = map;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
This can be achieved using the PropertiesLoaderUtils and #PostConstruct
Please check the sample below:
#Configuration
public class HelloConfiguration {
private Map<String, String> valueMap = new HashMap<>();
#PostConstruct
public void doInit() throws IOException {
Properties properties = PropertiesLoaderUtils.loadAllProperties("application.properties");
properties.keySet().forEach(key -> {
valueMap.put((String) key, properties.getProperty((String) key));
});
System.err.println("valueMap -> "+valueMap);
}
public Map<String, String> getValueMap() {
return valueMap;
}
public void setValueMap(Map<String, String> valueMap) {
this.valueMap = valueMap;
}
}
You can't do it with #ConfigurationProperties as far as I'm aware, those require a prefix to be able to load those properties within the bean.
However, if your goal is to obtain "value Y" for "property X" programmatically, you can always inject Environment and use the getProperty() method to find certain property, for example:
#Configuration
public class InitialConfiguration {
#Autowired
private Environment environment;
#PostConstruct
public void test() {
Integer aapl = environment.getProperty("AAPL", Integer.class); // 25
Integer gddy = environment.getProperty("GDDY", Integer.class); // 65
Integer test = environment.getProperty("test", Integer.class); // 22
}
}
In spring boot, if you need to get a single value from the application.proprties, you just need to use the #Value annotation with the given name
So to get AAPL value just add a class level property like this
#Value("${AAPL}")
private String aapl;
And if you need to load a full properties file as a map, I'm using the ResourceLoader to load the full file as a stream and then parse it as follows
#Autowired
public loadResources(ResourceLoader resourceLoader) throws Exception {
Resource resource = resourceLoader.getResource("classpath:myProperties.properties"));
BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String line;
int pos = 0;
Map<String, String> map = new HashMap<>();
while ((line = br.readLine()) != null) {
pos = line.indexOf("=");
map.put(line.substring(0, pos), line.substring( pos + 1));
}
}
Indeed you can use #ConfigurationProperties without prefix to get entire properties known to Spring application i.e. application, system and environment properties etc.
Following example creates a fully populated map as a Spring bean. Then wire / inject this bean wherever you need it.
#Configuration
class YetAnotherConfiguration {
#ConfigurationProperties /* or #ConfigurationProperties("") */
#Bean
Map<String, String> allProperties() {
return new LinkedHashMap<>();
}
}
#Autowire
void test(Map<String, String> allProperties) {
System.out.println(allProperties.get("AAPL")); // 25
...
}
#PropertySource("classpath:config.properties")
public class GlobalConfig {
public static String AAPL;
#Value("${AAPL}")
private void setDatabaseUrl(String value) {
AAPL = value;
}
}
You have to use #Value to get value from application.properties file
I'm experiencing really strange behavior from Spring. I have a #Bean which returns a Map. However when the Bean is #Autowired in, the key for the map is different than what was assigned in the #Bean method. my #Bean has two input parameters which are also Spring Beans from another configuration class. Once #Autowired The Keys of my map are changed to match the name of the #Bean methods passed in as dependencies im my Map returning Bean. The #Beanin question is located in an #ConfigurationProperties class where I am extracting some values from my application.yml file which are all returning correctly.
#Component
#ConfigurationProperties(prefix = "channel-broker")
#EnableConfigurationProperties
public class ChannelLookupConfig {
private String messageDeliveryChannelKey;
private String otherDeliveryChannelKey;
public String getMessageDeliveryChannelKey() {
return messageDeliveryChannelKey;
}
public void setMessageDeliveryChannelKey(String messageDeliveryChannelKey) {
this.messageDeliveryChannelKey = messageDeliveryChannelKey;
}
public String getOtherDeliveryChannelKey() {
return otherDeliveryChannelKey;
}
public void setOtherDeliveryChannelKey(String OtherDeliveryChannelKey) {
this.otherDeliveryChannelKey = OtherDeliveryChannelKey;
}
#Bean
public Map<String, MessageDeliveryClient> channelCallerLookup(MessageDeliveryClient MessageDispatcherClient, MessageDeliveryClient otherDeliveryClient) {
Map<String, MessageDeliveryClient> channelCallerLookup = new HashMap<>();
channelCallerLookup.put(messageDeliveryChannelKey, MessageDispatcherClient);
channelCallerLookup.put(otherDeliveryChannelKey, otherDeliveryClient);
return channelCallerLookup;
}
}
My second config file
#Configuration
public class Config {
#Bean
public MessageDeliveryClient MessageDispatcherClient() {
MessageDeliveryClient client = MessageDeliveryClient.builder()
.awsAccessKey(destinationSqsAccessKey)
.awsSecretKey(destinationSqsSecretKey)
.awsRegion(destinationSqsRegion)
.destinationQueueName(destinationSqsName)
.build();
return client;
}
#Bean
public MessageDeliveryClient otherPickerDeliveryClient() {
MessageDeliveryClient client = MessageDeliveryClient.builder()
.awsAccessKey(destinationSqsAccessKey)
.awsSecretKey(destinationSqsSecretKey)
.awsRegion(destinationSqsRegion)
.destinationQueueName(destinationOtherPickerSqsName)
.build();
return client;
}
}
Autowired in for use as such:
public class SimpleCustomerMessageDeliveryBrokerImpl implements CustomerMessageDeliveryBroker {
private Map<String, MessageDeliveryClient> channelCallerLookup = new HashMap<>();
#Autowired
public void setBrokerConfiguration(BrokerConfiguration brokerConfiguration) {
this.brokerConfiguration = brokerConfiguration;
}
}
the Map should contain 2 elements the first with a key equal to the value in String messageDeliveryChannelKey and the second with a key equal to the value in String otherDeliveryChannelKey. However the keys are always set equal to the name of the #Beans methods which are passed into my score. Even if I change the method names to nonsense the map's keys will equal that value.
How can I prevent this behavior from happening
This was occurring because of default Spring behavior. To work around this I applied a Wrapper around the return Map.
Changed my Bean to this
#Bean
public ChannelCallerLookup channelCallerLookup(MessageDeliveryClient messageDispatcherClient, MessageDeliveryClient otherPickerDeliveryClient) {
HashMap<String, MessageDeliveryClient> channelCallerLookup = new HashMap<>();
channelCallerLookup.put(CHANNEL1_KEY, messageDispatcherClient);
channelCallerLookup.put(CHANNEL1_KEY2, otherPickerDeliveryClient);
ChannelCallerLookup callerLookup = new ChannelCallerLookup(channelCallerLookup);
return callerLookup;
}
Created This Wrapper Class
public class ChannelCallerLookup {
Map<String, MessageDeliveryClient> lookupMap;
public ChannelCallerLookup(Map<String, MessageDeliveryClient> lookupMap) {
this.lookupMap = lookupMap;
}
public Map<String, MessageDeliveryClient> getLookupMap() {
return lookupMap;
}
public MessageDeliveryClient get(String key){
return lookupMap.get(key);
}
}
I’d like to print the consolidated list of properties set in our application on startup. What is the best way to do this?
Thanks
This is my implementation:
public class CustomPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer implements InitializingBean{
public void afterPropertiesSet(){
try{
Properties loadedProperties = this.mergeProperties();
for(Entry<Object, Object> singleProperty : loadedProperties.entrySet()){
logger.info("LoadedProperty: "+singleProperty.getKey()+"="+singleProperty.getValue());
}
}catch(Exception ex){
ex.printStackTrace();
}
}
}
Use a custom PropertyPlaceholderConfigurer implementation that overrides the resolve... methods and logs the placeholder name. You may also need/want to override the convert... methods, but resolve... should handle it.
Here is a concrete example of printing all properties :
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.context.support.StandardServletEnvironment;
public class PropertiesLoaderConfigurer
extends PropertySourcesPlaceholderConfigurer {
private static final String ENVIRONMENT_PROPERTIES = "environmentProperties";
#Override
public void postProcessBeanFactory(
final ConfigurableListableBeanFactory beanFactory)
throws BeansException {
super.postProcessBeanFactory(beanFactory);
final StandardServletEnvironment propertySources =
(StandardServletEnvironment) super.getAppliedPropertySources().get(ENVIRONMENT_PROPERTIES).getSource();
propertySources.getPropertySources().forEach(propertySource -> {
if (propertySource.getSource() instanceof Map) {
// it will print systemProperties, systemEnvironment, application.properties and other overrides of
// application.properties
System.out.println("#######" + propertySource.getName() + "#######");
final Map<String, String> properties = mapValueAsString((Map<String, Object>) propertySource.getSource());
System.out.println(properties);
}
});
}
private Map<String, String> mapValueAsString(
final Map<String, Object> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey(), entry -> toString(entry.getValue())));
}
private String toString(
final Object object) {
return Optional.ofNullable(object).map(value -> value.toString()).orElse(null);
}
}