Spring Framework : Populating a Map<Enum,Object> with util:map
I have this factory class, which i want to wire via spring for runtime configuration of the map. The map contains an enum object and standard pojo.
public class GenericEntityFactoryImpl implements GenericEntityFactory
{
private Map<IndexType,IEntity> indexEntityMap = null;
@Override
public IEntity getIndexEntity(IndexType index) {
return indexEntityMap.get(index);
}
public Map<IndexType, IEn开发者_开发问答tity> getIndexEntityMap() {
return indexEntityMap;
}
public void setIndexEntityMap(Map<IndexType, IEntity> indexEntityMap) {
this.indexEntityMap = indexEntityMap;
}
}
I'm having trouble with my spring util:map wiring, since i'm not sure how to correctly reference a specific enum type when definiting the key value. The bean ref for the map value is easy. All the examples of spring map wiring seem to assume that the key is a string!
<!-- the value object bean -->
<bean id="cell" class="com.xx.xx.common.index.entity.CellEntity"/>
<bean id="genericEntityFactory" class="com.xx.xx.common.index.GenericEntityFactoryImpl">
<util:map
id="indexEntityMap"
map-class="java.util.HashMap"
key-type="com.xx.xx.common.index.IndexType"
value-type="com.xx.xx.common.index.GenericEntityFactoryImpl">
<entry key="CELL">
<ref bean="cell"/>
</entry>
</util:map>
</bean>
Edit
So i refactored the mapping
<bean id="genericEntityFactory" class="com.xx.xx.common.index.GenericEntityFactoryImpl" >
<property name="indexEntityMap">
<map >
<entry key="com.xx.xx.common.index.CELL"><ref bean="cell"/></entry>
</map>
</property>
</bean>
but assumption that spring will be smart fails...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'genericEntityFactory' defined in class path resource [com/xx/xx/common/index/index-application-context.xml]: Initialization of bean failed; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert property value of type [java.util.LinkedHashMap] to required type [java.util.Map] for property 'indexEntityMap'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [com.xx.xx.common.index.IndexType] for property 'indexEntityMap[com.xx.xx.common.index.CELL]': no matching editors or conversion strategy found
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:480)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:221)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:429)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:729)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:381)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:84)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:42)
at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:173)
at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:197)
... 17 more
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert property value of type [java.util.LinkedHashMap] to required type [java.util.Map] for property 'indexEntityMap'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [com.xx.xx.common.index.IndexType] for property 'indexEntityMap[com.xx.xx.common.index.CELL]': no matching editors or conversion strategy found
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:391)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1288)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1249)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1010)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:472)
... 32 more
Caused by: java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [com.xx.xx.common.index.IndexType] for property 'indexEntityMap[com.xx.xx.common.index.CELL]': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:219)
at org.springframework.beans.TypeConverterDelegate.convertToTypedMap(TypeConverterDelegate.java:508)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:194)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:138)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:386)
... 36 more
I found a workaround by defining each enum I plan to add to the map as a separate bean - thanks to creating-spring-bean-from-java-5-enum
<bean id="CELL" class="com.xx.xx.common.index.IndexType" factory-method="valueOf">
<constructor-arg>
<value>CELL</value>
</constructor-arg>
</bean>
<bean id="APN" class="com.xx.xx.common.index.IndexType" factory-method="valueOf">
<constructor-arg>
<value>APN</value>
</constructor-arg>
</bean>
Having defined the enums I can them key-ref them in the map
<bean id="genericEntityFactory" class="com.xx.xx.common.index.GenericEntityFactoryImpl" >
<property name="indexEntityMap">
<map>
<entry key-ref="CELL"><ref bean="cell"/></entry>
<entry key-ref="APN"><ref bean="apn"/></entry>
</map>
</property>
</bean>
Here's an alternate and shorter format:
<bean id="versionService" class="my.service.VersionService"
p:animalDAOMap-ref="animalDAOMap"/>
<util:map id="p:animalDAOMap">
<entry key="chicken" value-ref="chickenDAO"/>
<entry key="monkey" value-ref="monkeyDAO"/>
<entry key="pig" value-ref="pigDAO"/>
</util:map>
Make sure to include the namespace
xmlns:util="http://www.springframework.org/schema/util"
And the schema
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
By the way I'm using Spring 3.0+ here
Got the same error as you do. I was able to avoid it by moving my enum to standalone file and make my enum public.
So my
public enum EventType { INFO, ERROR }
Are placed in
EventType.java
Also I have only one package if that matters somehow. I'm injecting dependency that way (through xml by constructor arg):
spring.xml:
<constructor-arg>
<map>
<entry key="INFO" value-ref="consoleEventLogger"></entry>
<entry key="ERROR" value-ref="combinedEventLogger"></entry>
</map>
</constructor-arg>
That's working for me with spring-core 4.3.6
I believe there is an explanation, that depends on reflection and internal Spring logic. But I don't has much Java experience, and I can't provide that.
I think this is what you need. Note i don't think you need to specify the key-type and value-type attributes. Spring should be able to work that out.
<bean id="genericEntityFactory" class="com.xx.xx.common.index.GenericEntityFactoryImpl">
<property name="indexEntityMap" ref="indexEntityMapBean"/>
</bean>
<util:map id="indexEntityMapBean"
map-class="java.util.HashMap"
key-type="com.xx.xx.common.index.IndexType"
value-type="com.xx.xx.common.index.GenericEntityFactoryImpl">
<entry key="com.xx.xx.common.index.IndexType.CELL">
<ref bean="cell"/>
</entry>
</util:map>
The only reason you would use <util:map
instead of the cleaner annonomous <map>
(see section 3.3.3.3) is if you wanted to wire the same map into multiple places or you wanted to use a different underlying map implementation eg ConcurrentHashMap.
精彩评论