Spring - How do you set Enum keys in a Map with annotations - spring

I've an Enum class
public enum MyEnum{
ABC;
}
than my 'Mick' class has this property
private Map<MyEnum, OtherObj> myMap;
I've this spring xml configuration.
<util:map id="myMap">
<entry key="ABC" value-ref="myObj" />
</util:map>
<bean id="mick" class="com.x.Mick">
<property name="myMap" ref="myMap" />
</bean>
and this is fine.
I'd like to replace this xml configuration with Spring annotations.
Do you have any idea on how to autowire the map?
The problem here is that if I switch from xml config to the #Autowired annotation (on the myMap attribute of the Mick class) Spring is throwing this exception
nested exception is org.springframework.beans.FatalBeanException: Key type [class com.MyEnum] of map [java.util.Map] must be assignable to [java.lang.String]
Spring is no more able to recognize the string ABC as a MyEnum.ABC object.
Any idea?
Thanks

This worked for me...
My Spring application context:
<util:map id="myMap">
<entry key="#{T(com.acme.MyEnum).ELEM1}" value="value1" />
<entry key="#{T(com.acme.MyEnum).ELEM2}" value="value2" />
</util:map>
My class where the Map gets injected:
public class MyClass {
private #Resource Map<MyEnum, String> myMap;
}
The important things to note are that in the Spring context I used SpEL (Spring Expression Language) which is only available since version 3.0. And in my class I used #Resource, neither #Inject (it didn't work for me) nor #Autowired (I didn't try this). The only difference I'm aware of between #Resource and #Autowired, is that the former auto-inject by bean name while the later does it by bean type.
Enjoy!

This one gave me fits but I was able to piece it together using David's answer and some other links (below).
do not change the names of the properties in the MapFactoryBean declaration.
ensure that key-type attribute points to the enum that you want to use as a key in the map.
Class
#Component
public class MyClass {
private Map<MyEnum, ValueObjectInterface> valueMap;
#Autowired
public void setValueMap(final Map<MyEnum, ValueObjectInterface> valueMap) {
this.valueMap= valueMap;
}
}
Enum
public enum MyEnum{
FOO ("FOO"),
BAR ("BAR"),
BAZ ("BAZ");
}
XML Config file:
<bean id="valueMap" class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="targetMapClass">
<value>java.util.HashMap</value>
</property>
<property name="sourceMap">
<map key-type="com.company.packagepath.MyEnum">
<entry key="FOO" value-ref="valueObject1" />
<entry key="BAR" value-ref="valueObject2" />
<entry key="BAZ" value-ref="valueObject3" />
</map>
</property>
</bean>
<bean id="valueObject1" class="com.company.packagepath.ValueObject1" />
<bean id="valueObject2" class="com.company.packagepath.ValueObject2" />
<bean id="valueObject3" class="com.company.packagepath.ValueObject3" />
LINKS
Code Ranch
Spring MapFactoryBean example at mkyong.com
How assign bean's property an Enum value in Spring config file?

Application context
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd ">
<bean id="myProvider" class="com.project.MapProvider">
<property name="myMap" ref="myMap"/>
</bean>
<util:map id="myMap" key-type="com.project.MyEnum" value-type="com.project.ValueObject">
<entry>
<key><value type="com.project.MyEnum">FOO</value></key>
<ref bean="objectValue1"/>
</entry>
</util:map>
</beans>
Java class
package com.project;
public class MapProvider {
private Map<MyEnum, ValueObject> myMap;
public void setMyMap(Map<MyEnum, ValueObject> myMap) {
this.myMap = myMap;
}
}

Should be:
public class Mick {
private Map<MyEnum, OtherObj> myMap;
#Autowired
public void setMyMap(Map<MyEnum, OtherObj> myMap) {
this.myMap = myMap;
}
}
Have a look at http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#beans-annotation-config
Updated
The problem is that according to the util schema, you cannot specify the key or value types. You can however to implement a MapFactoryBean of your own (just inherit from org.springframework.beans.factory.config.MapFactoryBean). One ceveat - notice that the generic definition (even thought erased in runtime) doesn't get in the way.

The <util:map> element has key-type, resp. value-type attributes, that represents the class of the keys, resp. the values. If you specify the fully qualified class of your enum in the key-type attribute, the keys are then parsed into that enum when creating the map.
Spring verifies during injection that the map's key and value types -as declared in the class containing the map- are assignment-compatible with the key and value types of the map bean. This is actually where you get the exception from.

You just need to use concrete Map class as HashMap and not abstract or interface:
public class Mick {
private HashMap<MyEnum, OtherObj> myMap;
#Autowired
public void setMyMap(HashMap<MyEnum, OtherObj> myMap) {
this.myMap = myMap;
}
}
public class AppConfig
{
#Bean
public HashMap<MyEnum, OtherObj> myMap() { .. }
}

If you have a Map with an Enum values as keys, then consider using Java's EnumMap implementation:
https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/EnumMap.html
Here you also have a Baeldung post with some examples on how to use it:
https://www.baeldung.com/java-enum-map

Related

Spring Injecting util:map in Kotlin with type safety

I have the several bean definitions in XML to store my SQL externally. I want to inject them in Kotlin as Map<String,String> however the only way I've been able to make it work so far it injecting it as Map<Any,Any>. Is there a way to ensure type safety here. Injecting it as Map<Any,Any> feels ghetto.
When I try Map<String,String> or even Map<String,Any> I get not qualifying beans found...
Example XML
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<util:map id="brandSql" key-type="java.lang.String" value-type="java.lang.String">
<entry key="selectBrands">
<value type="java.lang.String">
<![CDATA[
SELECT
ID,
NAME
FROM BRAND
]]>
</value>
</entry>
</util:map>
</beans>
Injection into service
#Service
open class JdbcBrandService #Autowired constructor(
private val namedJdbcTemplate: NamedParameterJdbcTemplate
): BrandService {
companion object {
val logger = LoggerFactory.getLogger(JdbcBrandService::class.java)
}
#Autowired
#Qualifier(value = "brandSql")
private lateinit var queries: Map<Any,Any>
/// methods and what not go here
}
In Java I can get away with doing something like the following, but Kotlin's type system being more strict seems to prevent this.
#RestController
public class JavaBrandController {
private final Map<String, String> brandSql;
#Autowired
public JavaBrandController(#Qualifier("brandSql") Map sql) {
this.brandSql = sql;
}
#GetMapping("/javaBrands")
public Map getBrandSql() {
return this.brandSql;
}
}
Personally, it feels strange to have beans floating around that are of such generic types as Map<String, String>. It would create confusion when you need other maps.
What I would do is create a container class that holds a reference to the map, so you have a proper type to reference.
Example:
class SqlConfig(val map: Map<String, String>)
Then create a bean of this type in your xml:
<util:map id="brandSql" key-type="java.lang.String" value-type="java.lang.String">
<entry key="selectBrands">
<value type="java.lang.String">
<![CDATA[
SELECT
ID,
NAME
FROM BRAND
]]>
</value>
</entry>
</util:map>
<bean id = "sqlConfig" class = "test.package.SqlConfig">
<constructor-arg ref = "brandSql"/>
</bean>
Now you can autowire it as needed with the proper type in a non-guetto way:
#Autowired
#Qualifier(value = "sqlConfig") // <-- the qualifier is no longer needed
private lateinit var queries: SqlConfig

Autowiring is not working when configured through XML

class ConfigurationDetails {
private #Resource String esHostURL;
private #Resource int maxMessageCounter;
private #Resource String queueName;
// Assume : This class has all getter and setter methods and a default constructor
}
Another Class
public class SpringMessageListener implements MessageListener {
#Resource ConfigurationDetails configDetails; // With getter and setter method for this
............
..........
And in my XML
<bean id="aListener" class="com.vzw.es.cosumer.SpringMessageListener" autowire="byName"/>
<bean id="configDetails" class="com.vzw.es.pojo.ConfigurationDetails" autowire="byName">
<property name="esHostURL" value="http://obsgracstg-db0.odc.vzwcorp.com:9200"/>
<property name="maxMessageCounter" value="500"/>
<property name="queueName" value="ES_queue"/>
</bean>
Now the bean with id configDetails is not getting autowired meaning when I debug the code and see the configDetails in class SpringMessageListerner it is showing null. But when I explicitly do the appContext.getBean("configDetails") it gives me the not null Object.
Why is the Autowiring is not working? Am I missing anything?
Spring doesn't, by default, look for #Autowired, #Resource, or #Inject annotations to autowire your beans. You need to tell it to look for them with
<context:component-scan base-package="com.yourpackage.some" />
// or, in this case, <context:annotation-config />
with this, Spring will scan the classes in the package and inject beans for which an #Autowired or #Resource exists.
Don't forget to add the namespace declarations
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
More importantly
class ConfigurationDetails {
private #Resource String esHostURL;
private #Resource int maxMessageCounter;
private #Resource String queueName;
// Assume : This class has all getter and setter methods and a default constructor
}
Although you can autowire String and int types, this is usually considered bad practice. Instead remove the #Resource annotation here and add getters and setters for each of the fields.
The property element in the <bean> declaration takes care of setting those fields.

Injecting spring bean to an abstract class

I'm trying to inject (autowire) a spring bean to an abstract class but it doesn't seem to work.
public abstract class BaseEntity {
#Autowired(required = true)
protected SecurityProvider securityService;
public BaseEntity() {
}
}
And the injected class:
#Component
public class SecurityService extends SecurityProviderImpl implements SecurityProvider {
#Autowired
public SecurityService(ICipherDescriptor cipherDescriptor) {
super(cipherDescriptor);
}
}
The SecurityService gets initialized just fine (I can see it while debugging) but the class that inherit from BaseEntity cannot use the injected SecurityService since it is null (doesn't get injected for some reason).
I tried doing it via XML as well, defining the BaseEntity as abstract:
<bean id="baseEntity" abstract="true" class="com.bs.dal.domain.BaseEntity">
<property name="securityService" ref="securityService"/>
</bean>
<bean id="securityService" class="com.bs.dal.secure.SecurityService">
<constructor-arg ref="cipherDescriptor" />
</bean>
but still with no success.
Where am I going wrong?
I think I know what's wrong here. I'm trying to inject a spring bean to an entity - which is impossible (unless you use aspectJ weaving) since the entities are not instantiated/managed by Spring. Make sense, isn't it?
If your BaseEntity is also instantiated by spring, you just need to add a parent attribute to the bean definition to link it to your BaseEntity definition like so:
<bean id="baseEntity" abstract="true" class="com.bs.dal.domain.BaseEntity">
<property name="securityService" ref="securityService"/>
</bean>
<bean id="derivedEntity" parent="baseEntity" class="com.bs.dal.domain.DerivedEntity"/>
<bean id="securityService" class="com.bs.dal.secure.SecurityService">
<constructor-arg ref="cipherDescriptor" />
</bean>
In short, the two key parts to such a definition are the abstract attribute on the parent class and the parent attribute on the subclass.

Can't use #Qualifier along with #Inject and autowire="byType"

I have the bean config:
<bean id="PostLoginUpdater" class="xyz.auth.PostLoginUpdater" autowire="byType" />
and
public class PostLoginUpdater implements PostLoginStatePersonalizer {
//#Qualifier("CustomerManager")
#Inject
//#Resource(name = "CustomerManager")
private CustomerManager customerManager;
public void setCustomerManager(CustomerManager customerManager)
{
this.customerManager = customerManager;
}
}
Because there are two beans that implement CustomerManager I get this error:
No unique bean of type [CustomerManager] is defined: expected single
matching bean but found 2
As you can see, I'v tried several combinations (#Inject along with #Qualifier, #Ressource, only #Qualifier) But I don't get rid of this error message.
According to Spring In Depth, #Qualifier can be used along with #Inject. Can't I used them together if I defined autowire="byType" in bean config?
And I don't use <context:annotation-config /> or <context:component-scan />
You should be able to use a combination of '#Inject' and '#Qualifier', if you have multiple beans of the same type. Here is how to configure it -
<bean id="PostLoginUpdater" class="xyz.auth.PostLoginUpdater" autowire="byType" />
<bean id="firstManager" class="xyz.manager.CustomerManager" autowire="byType" >
<qualifier>first</qualifier>
</bean>
<bean id="secondManager" class="xyz.manager.CustomerManager" autowire="byType" >
<qualifier>second</qualifier>
</bean>
If you had two beans of type 'CustomerManager' as shown above, you could use the snippet shown below for injection -
public class PostLoginUpdater implements PostLoginStatePersonalizer {
#Inject
#Qualifier("first")
private CustomerManager customerManager;
public void setCustomerManager(CustomerManager customerManager)
{
this.customerManager = customerManager;
}
}
Also, on a side note -
If you keep using one of the beans more often than another you could use the 'primary' attribute.
For example, in the above example, if you always tend to use 'firstManager', you could mark it as primary as shown below.
<bean id="PostLoginUpdater" class="xyz.auth.PostLoginUpdater" autowire="byType" />
<bean id="firstManager" class="xyz.manager.CustomerManager" autowire="byType" primary="true" >
</bean>
<bean id="secondManager" class="xyz.manager.CustomerManager" autowire="byType" >
<qualifier>second</qualifier>
</bean>
If you have above configuration, the following code will always injects 'firstManager' when no qualifier is used -
public class PostLoginUpdater implements PostLoginStatePersonalizer {
#Inject
private CustomerManager customerManager;
public void setCustomerManager(CustomerManager customerManager)
{
this.customerManager = customerManager;
}
}
It doesn't make any sense to try to autowire by type, and simultaneously specify a name. Choose one approach or the other.
I have also had trouble in the past trying to use #Qualifier with #Inject. One thing to note is that there are two annotations with that name, one in Spring and one in Java:
org.springframework.beans.factory.annotation.Qualifier
javax.inject.Qualifier
However, if using the spring framework one, then the suggested usage is to explicitly name your component via #Component or #Named [#Component("beanName")] (if annotated), or in the <bean> definition. Be aware that autowiring wants the bean name, not the Class name as in your example (i.e. do not use "CustomerManager"). For example, if you have two bean definitions like in Sashi's example:
<bean id="firstManager" class="xyz.manager.CustomerManager" autowire="byType" >
<qualifier>first</qualifier>
</bean>
<bean id="secondManager" class="xyz.manager.CustomerManager" autowire="byType" >
<qualifier>second</qualifier>
</bean>
then declare the field like this:
#Inject
#Qualifier("firstManager")
private CustomerManager customerManager;

Spring's overriding bean

Can we have duplicate names for the same bean id that is mentioned in the XML?
If not, then how do we override the bean in Spring?
Any given Spring context can only have one bean for any given id or name. In the case of the XML id attribute, this is enforced by the schema validation. In the case of the name attribute, this is enforced by Spring's logic.
However, if a context is constructed from two different XML descriptor files, and an id is used by both files, then one will "override" the other. The exact behaviour depends on the ordering of the files when they get loaded by the context.
So while it's possible, it's not recommended. It's error-prone and fragile, and you'll get no help from Spring if you change the ID of one but not the other.
I will add that if your need is just to override a property used by your bean, the id approach works too like skaffman explained :
In your first called XML configuration file :
<bean id="myBeanId" class="com.blabla">
<property name="myList" ref="myList"/>
</bean>
<util:list id="myList">
<value>3</value>
<value>4</value>
</util:list>
In your second called XML configuration file :
<util:list id="myList">
<value>6</value>
</util:list>
Then your bean "myBeanId" will be instantiated with a "myList" property of one element which is 6.
Not sure if that's exactly what you need, but we are using profiles to define the environment we are running at and specific bean for each environment, so it's something like that:
<bean name="myBean" class="myClass">
<constructor-arg name="name" value="originalValue" />
</bean>
<beans profile="DEV, default">
<!-- Specific DEV configurations, also default if no profile defined -->
<bean name="myBean" class="myClass">
<constructor-arg name="name" value="overrideValue" />
</bean>
</beans>
<beans profile="CI, UAT">
<!-- Specific CI / UAT configurations -->
</beans>
<beans profile="PROD">
<!-- Specific PROD configurations -->
</beans>
So in this case, if I don't define a profile or if I define it as "DEV" myBean will get "overrideValue" for it's name argument. But if I set the profile to "CI", "UAT" or "PROD" it will get "originalValue" as the value.
An example from official spring manual:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
Is that what you was looking for?
Updated link
Since Spring 3.0 you can use #Primary annotation. As per documentation:
Indicates that a bean should be given preference when multiple
candidates are qualified to autowire a single-valued dependency. If
exactly one 'primary' bean exists among the candidates, it will be the
autowired value. This annotation is semantically equivalent to the
element's primary attribute in Spring XML.
You should use it on Bean definition like this:
#Bean
#Primary
public ExampleBean exampleBean(#Autowired EntityManager em) {
return new ExampleBeanImpl(em);
}
or like this:
#Primary
#Service
public class ExampleService implements BaseServive {
}
Another good approach not mentioned in other posts is to use PropertyOverrideConfigurer in case you just want to override properties of some beans.
For example if you want to override the datasource for testing (i.e. use an in-memory database) in another xml config, you just need to use <context:property-override ..."/> in new config and a .properties file containing key-values taking the format beanName.property=newvalue overriding the main props.
application-mainConfig.xml:
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="org.postgresql.Driver"
p:url="jdbc:postgresql://localhost:5432/MyAppDB"
p:username="myusername"
p:password="mypassword"
destroy-method="close" />
application-testConfig.xml:
<import resource="classpath:path/to/file/application-mainConfig.xml"/>
<!-- override bean props -->
<context:property-override location="classpath:path/to/file/beanOverride.properties"/>
beanOverride.properties:
dataSource.driverClassName=org.h2.Driver
dataSource.url=jdbc:h2:mem:MyTestDB
Whether can we declare the same bean id in other xml for other reference e.x.
Servlet-Initialize.xml
<bean id="inheritedTestBean" class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
Other xml (Document.xml)
<bean id="inheritedTestBean" class="org.springframework.beans.Document">
<property name="name" value="document"/>
<property name="age" value="1"/>
</bean>
Question was more about XML but as annotation are more popular nowadays and it works similarly I'll show by example.
Let's create class Foo:
public class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and two Configuration files (you can't create one):
#Configuration
public class Configuration1 {
#Bean
public Foo foo() {
Foo foo = new Foo();
foo.setName("configuration1");
return foo;
}
}
and
#Configuration
public class Configuration2 {
#Bean
public Foo foo() {
Foo foo = new Foo();
foo.setName("configuration2");
return foo;
}
}
and let's see what happens when calling foo.getName():
#SpringBootApplication
public class OverridingBeanDefinitionsApplication {
public static void main(String[] args) {
SpringApplication.run(OverridingBeanDefinitionsApplication.class, args);
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(
Configuration1.class, Configuration2.class);
Foo foo = applicationContext.getBean(Foo.class);
System.out.println(foo.getName());
}
}
in this example result is: configuration2.
The Spring Container gets all configuration metadata sources and merges bean definitions in those sources. In this example there are two #Beans. Order in which they are fed into ApplicationContext decide. You can flip new AnnotationConfigApplicationContext(Configuration2.class, Configuration1.class); and result will be configuration1.

Resources