Injecting a Map<String, Double> using Spring Boot and application.yml - java

I know, there are many questions that ask similar thing in SO, but I am not able to get out of this situation using them.
I have a Spring Boot application.
#SpringBootApplication
#EnableConfigurationProperties
public class Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
}
Then I have the following class.
#Component
#ConfigurationProperties(prefix = "somePrefix")
public class AClass {
private final AnotherClass anotherClass;
private final Map<String, Double> aMap;
#Autowired
public AffinityChecks(AnotherClass anotherClass,
Map<String, Double> aMap) {
this.anotherClass = anotherClass;
this.aMap = aMap;
}
// Omissis
Finally I have the following application.yml configuration file.
somePrefix:
aMap:
key1: 0.6
key2: 0.2
key3: 0.2
All I want is Spring to inject the map into object of typeAClass during building process. The error I obtain is the following.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.util.Map<java.lang.String, java.lang.Double>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
What the hell is going on?
Thanks in advance.

Spring Boot does not support constructor injection for elements that are bound to the environment. You can of course inject actual beans the usual way.
You need to define each property you want to bind as a regular Javabean property (i.e. with getter/setter). There is one exception to this rule: Maps and non-scalar value (i.e. nested content) only need a getter.
Concretely if AnotherClass is a bean and FooClass some pojo with nested properties.
#Component
#ConfigurationProperties(prefix = "somePrefix")
public class AClass {
private final AnotherClass anotherClass;
private final Map<String, Double> aMap = new HashMap<>();
private final FooClass foo = new FooClass();
public AClass(AnotherClass anotherClass) { ...}
public Map<String, Double> getaMap() { ... }
public FooClass getFoo() { ... }
}
(Note that the getter is the thing that infers the name of the property. In the example above you could map somePrefix.foo.xyz if you had a getXyz() on FooClass).
There is an example in the documentation with additional details.
(A good hint that your code is wrong is that the map is not a bean so #Autowiring isn't the proper semantic for what you were trying to achieve).

Related

How to dynamically call a method on a qualified service?

I have an interface as following:
public interface Solver {
void execute(final String definition);
}
I have several solver implementations. For example:
#Qualifier("naive")
#Service
public class NaiveSolver implements Solver {
#Override
public void execute(final String definition) {
}
}
#Qualifier("optimal")
#Service
public class OptimalSolver implements Solver {
#Override
public void execute(final String definition) {
}
}
Now, in my database I have the data for these solvers as following:
type: textual - examples: simple, naive
definition: textual
When fetching this data, I need to map the type column to the actual service instance, so it can solve the provided definition.
So, my question boils down to this: how can I, given a type string, get the instance of the corresponding Solver service instance of which the qualifier is equal to that type? I believe that #Autowired cannot be used here, as I need to find beans dynamically at runtime.
Since Spring 4 you can autowire multiple implemetations into a Map where bean qualifier is a key, and the bean itself is a value
#Autowired
private Map<String, Solver> solvers;
void doStuff() {
String type = ... // obtain type
Solver solver = solvers.get(type);
solver.execute(...)
}
Update
Correct way of naming a bean is not
#Qualifier("naive")
#Service
but
#Service("naive")
#Qualifier is used along with #Autowired to ensure the correct bean is injected
#Autowired
#Qualifier("naive")
private Solver naiveSolver;
You can create configuration which will just hold mapping to your solvers:
#Autowired
#Qualifier("optimal")
private Solver naiveSolver;
#Bean
public Map<String, Solver> mapSolver() {
Map<String, Solver> map = new HashMap<>();
map.put("naive", naiveSolver);
return map;
}
Or even going further you can follow factory pattern which will provide you different instances of solvers.
Another way you can dynamically get those beans from application context.
You could merge the following solutions:
How to inject dependencies into a self-instantiated object in Spring?
Creating an instance from String in Java
private #Autowired AutowireCapableBeanFactory beanFactory;
public void doStuff() {
Class c= Class.forName(className);
MyBean obj = c.newInstance();
beanFactory.autowireBean(obj);
// obj will now have its dependencies autowired.
}

Spring init failing when bean constructor using parameters

SpringBoot app fails while trying to initialize this class:
#Component
public class Weather {
private Map<Integer,Double> maxRainyDays;
private Map<Integer, WeatherDays> totalDays;
public Weather(Map<Integer, WeatherDays> totalDays, Map<Integer,Double> maxRainyDays){
this.setMaxRainyDays(maxRainyDays);
this.setTotalDays(totalDays);
}
The error:
Parameter 0 of constructor in SolarSystem.Models.Weather required a
bean of type 'SolarSystem.Utilities.WeatherDays' that could not be
found.
The mentioned bean is already defined (in the same basepackage):
public enum WeatherDays {
RAINY,
MILD,
DRY,
MAX_RAIN}
Workaround:
When I changed to Weather() constructor I solved the issue.Of course I had to use setters to set the properties of the object.
But I need to understand the reasons why happened
Because the collections (Map here) you are injecting via constructor parameter aren't registered as Spring beans. This is why you would annotate class with #service, #repository, etc and have them autowired into other classes. To fix, you can set up a config class like such:
#Configuration
public class BeanConfig {
#Bean
public Map<Integer, WeatherDays> totalDays() {
Map<Integer, WeatherDays> map = new HashMap<>();
map.put(1, WeatherDays.DRY);
return map;
}
#Bean
public Map<Integer, Double> maxRainyDays() {
Map<Integer, Double> map = new HashMap<>();
map.put(1, 0.2);
return map;
}
}

How to #Autowired a List<Integer> in spring framework

I have a configuration class as below:
#Configuration
public class ListConfiguration {
#Bean
public List<Integer> list() {
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
ints.add(3);
return ints;
}
#Bean
public int number() {
return 4;
}
}
I also have a test class as below
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = ListConfiguration.class)
public class ListTest {
#Autowired
List<Integer> ints;
#Test
public void print() {
System.out.println(ints.size());
System.out.println(ints);
}
}
But the output of the print method is 1 and [4], why not 3 and [1,2,3]? Thank you very much for any help!
You've got a bean of type Integer and a bean of type List<Integer> in your application context.
Now obviously the bean you want to autowire is of type List<Integer>, which does qualify as a candidate for autowiring. To discover how Spring actually autowires fields I had to dive deep into the AutowiredAnnotationBeanPostProcessor class.
TL;DR of my investigation is that Spring will prefer to autowire objects in the following order:
Default Value using #Value
Multiple beans using a type parameter.
Individual beans that match the field type.
That means that if you're autowiring a List<Integer> Spring will attempt to autowire multiple Integer beans into the list before it will attempt to autowire a single List<Integer> bean.
You can see this behaviour in the DefaultListableBeanFactory class.
Relevant snippet below:
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
Class<?> type = descriptor.getDependencyType();
//Searches for an #Value annotation and
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
//Handle finding, building and returning default value
}
/*
* Check for multiple beans of given type. Because a bean is returned here,
* Spring autowires the Integer bean instance.
*/
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
// Do more stuff here to try and narrow down to a single instance to autowire.
}
}
Hopefully this explains why you do need to use an #Qualifer annotation when trying to autowire a list of a type when you've got individual beans of that type in your application context.
EDIT:
It's worth noting that this is not good practice. Creating a collection of primitives or primitive wrappers and registering it as a bean is going to cause issues. The best way to do this is with #Value and define your list of primitives in a properties file, that Spring picks up.
Example:
application.properties file
list=1,2,3,4
In your config class declare the following bean:
#Bean
public ConversionService conversionService() {
return new DefaultConversionService();
}
The default conversion service is used to convert comma separated values declared in a properties file into a collection of objects with type safety.
Class to use it:
#Value("${list}")
private List<Integer> anotherList;
anotherList will contain 1,2,3 & 4 as elements in the list.
May be Spring is injecting all the Integer type beans into a List instead of Autowiring List<Integer> bean that you declared.
Probably if you add #Qualifier("list") at your injection point in your Test then it will provide the behavior you are expecting.

Spring #Autowire annotation

I have a question on Spring Autowire Annotation.The scenario is like this : Iam using #Autowire on class A and using it in 2 places -Class B and Class C like below:
public class B
{
#Autowired
private A a;
......
Map<String, Map<String,String>> map1=a.getNameValues();
Map<String, Map<String, String>> map2 = a.get("key");
if (map2!=null)
map1.putAll(map2);
and also in other class C as shown below:
public class C
{
#Autowired
private A a;
......
Map<String, Map<String,String>> map1=a.getNameValues();
Map<String, Map<String, String>> map2 = a.get("key");
if (map2!=null)
map1.putAll(map2);
}
The program control flows from Class B to class C. So since the class A is autowired in both the places. so when the control comes first to class B ,map2 is retrieved and put in map1 . when the control comes to Class C , map1 already has map2 values. What are the possible ways to control this kind of scenarios? As i want both classes to work independently and use the Autowired class. Let me know your thoughts.
#Autowire will automagically inject a spring bean into the given property.
It sounds like your question is actually related to the scope of the bean being injected. So assuming your A class looks like this:
#Component
public class A {
....
}
Then what will happen is spring will create a single instance (aka a Singleton) of A (in the given application context) and inject this into both B and C
Question - Is this the problem you are trying to solve? When you say you want both classes to act independently you mean the fact that the A object in B and C are the exact same object?
To get spring to wire a new instance of A you can simply change the scope of A to be prototype.
#Component
#Scope(value = "prototype")
public class A {
....
}
or in xml
<bean id="a" class="A" scope="prototype"/>

How can I select Spring bean instance at runtime

Based on parameters passed to a method, I need to select from one of many Spring beans that are implementations of the same class, but configured with different parameters.
E.g. if user A invokes the method, I need to call dooFoo() on bean A, but if it's user B then I need to call the very same method, only on bean B.
Is there a 'Springier' way of doing this other than sticking all the beans in a map, and deriving a key from the parameters passed to my method?
We face that issue in our project, and we solve it through a Factory-Like class. The client class -the one that needed the bean at runtime- had an instance of the factory, that was injected through Spring:
#Component
public class ImTheClient{
#Autowired
private ImTheFactory factory;
public void doSomething(
Parameters parameters) throws Exception{
IWantThis theInstance = factory.getInstance(parameters);
}
}
So, the IWantThis instance depends on the runtime value of the parameters parameter. The Factory implementation goes like this:
#Component
public class ImTheFactoryImpl implements
ImTheFactory {
#Autowired
private IWantThisBadly anInstance;
#Autowired
private IAlsoWantThis anotherInstance;
#Override
public IWantThis getInstance(Parameters parameters) {
if (parameters.equals(Parameters.THIS)) {
return anInstance;
}
if (parameters.equals(Parameters.THAT)) {
return anotherInstance;
}
return null;
}
}
So, the factory instance holds reference to both of the posible values of the IWantThis class, being IWantThisBadly and IAlsoWantThis both implementations of IWantThis.
Seems like do you want a ServiceLocator using the application context as registry.
See ServiceLocatorFactoryBean support class for creating ServiceLocators mapping keys to bean names without coupling client code to Spring.
Other option is to use a naming convention or annotation based configuration.
for example, assuming that you annotate Services with #ExampleAnnotation("someId"), you can use something like the following Service Locator to retrieve them.
public class AnnotationServiceLocator implements ServiceLocator {
#Autowired
private ApplicationContext context;
private Map<String, Service> services;
public Service getService(String id) {
checkServices();
return services.get(id);
}
private void checkServices() {
if (services == null) {
services = new HashMap<String, Service>();
Map<String, Object> beans = context.getBeansWithAnnotation(ExampleAnnotation.class);
for (Object bean : beans.values()) {
ExampleAnnotation ann = bean.getClass().getAnnotation(ExampleAnnotation.class);
services.put(ann.value(), (Service) bean);
}
}
}
}
Sticking them in a map sounds fine. If it's a Spring-managed map (using util:map, or in Java config), that's better than creating it somewhere else, because then Spring owns all the object references and can manage their lifecycle properly.
If the beans (A, B) you are talking about are SessionScope its no problem at all, they will be selected correctly.
public class BusinessLogic {
private BaseClassOfBeanAandB bean;
public void methodCalledByUserAorB() {
bean.doFoo();
}
}

Categories

Resources