Question on a specific use case on spring <util:map> - spring

I'm just trying to explore one use case of using object as a value in a spring map. Here's my example
<util:map id="someSourceMap" map-class="java.util.HashMap">
<entry key="source1" value="testLine"/>
<entry key="source2" value="testLine2"/>
</util:map>
<bean id="testLine1" class="com.test.ProductLineMetadata" scope="prototype">
<constructor-arg value="PRODUCT_LINE_1"></constructor-arg>
<constructor-arg value="TYPE_1"></constructor-arg>
</bean>
<bean id="testLine2" class="com.test.ProductLineMetadata"scope="prototype">
<constructor-arg value="PRODUCT_LINE_2"></constructor-arg>
<constructor-arg value="TYPE_2"></constructor-arg>
</bean>
What I'm trying to achieve is to create a map in which the value will be a new instance of ProductLineMetadata object with different parameters set through constructor argument. I don't want to create a separate bean entry for each key with the desired constructor values. Is there a better way of doing this by somehow specifying the parameters inside the map declaration itself?
Any pointer will be highly appreciated.
Thanks

You mean something like this?
<util:map id="someSourceMap" map-class="java.util.HashMap">
<entry key="source1">
<bean class="com.test.ProductLineMetadata">
<constructor-arg value="PRODUCT_LINE_1"/>
<constructor-arg value="TYPE_1"/>
</bean>
</entry>
<entry key="source2">
<bean class="com.test.ProductLineMetadata">
<constructor-arg value="PRODUCT_LINE_2"/>
<constructor-arg value="TYPE_2"/>
</bean>
</entry>
</util:map>

If your testLines are just test data rather than regular beans you may use more lightweight approach to declare them, for example, Spring Expression Language (since Spring 3):
<util:map id="someSourceMap" map-class="java.util.HashMap">
<entry key="source1"
value="#{new com.test.ProductLineMetadata('PRODUCT_LINE_1', 'TYPE_1')}"/>
<entry key="source2"
value="#{new com.test.ProductLineMetadata('PRODUCT_LINE_2', 'TYPE_2')}"/>
</util:map>

Related

How Spring manages map-merge='true' in case of same key in parent map?

I have following 2 configs :
config-1.xml
---- import config-2.xml -----
<bean id="map1" class="java.util.HashMap" parent="map2">
<constructor-arg>
<map merge="true">
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</constructor-arg>
</bean>
config-2.xml
<bean id="map2" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="key1" value="new_value1"/>
<entry key="key3" value="value3"/>
</map>
</constructor-arg>
</bean>
How spring manages this merge ?
I will load bean through config-1.xml ( It has config-2.xml as import).
So what I want is key1=new_value1
Please note that I cant touch config-1.xml as its used by other code so I should load new value via config-2.xml which is specific to my code.
Wherever I refer map1 , it should have following for my code :
key1=new_value1
key2=value2
key3=value3
Looking at the Spring docs for Bean Definition Inheritance, it seems to me to pretty clearly say that anything specified in both child and parent will cause the child definition to override the parent definition:
https://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch03s07.html
Maybe it could be a little more clear, but what the docs say seems to confirm what you're saying and what the testing you've cone confirms. Take this example:
<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>
The fact that nothing is said about what value the name property will take, but its value for the example is chosen as override, seems to pretty clearly suggest that override will be the resulting value of the child's name property. Further, the fact that it is mentioned explicitly that the age property will be inherited from the parent strongly suggests that the name property will not be, meaning that it will instead be overridden by the child definition. This seems like nothing but obvious behavior to me.

Spring <util:map> shorthand as a bean property?

I've searched quite a bit but haven't found an example or statement about this.
Is it possible to use the Spring (4.3 at this point) XML <util:map> shorthand to assign a property within a bean?
Specifically, I'd like to simplify/shorten this:
<bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
<property name="attributes">
<map>
<entry key="properties" value-ref="properties"/>
</map>
</property>
</bean>
Xml-wise that looks about as terse as it's going to get! The DTD doesn't support anything else.
Since you need refs, nothing inline is going to be any shorter either.
Sure.Here is an example for you :
<bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
<property name="attributes">
<util:map>
<entry key="key1" value="strValue" /> <!-- value is string -->
<entry key="key2" value="1234" value-type="java.lang.Integer" /> <!-- Use value-type to explicitly specify the type -->
<entry key="key3" value-ref="fooBean"/> <!-- Use value-ref to reference to other bean -->
</util:map>
</property>
</bean>
Shortest I've found is this, which is 2-3 lines shorter, depending on formatting. And assuming you already have the necessary namespaces defined:
<util:map id="attributes">
<entry key="properties" value-ref="properties"/>
</util:map>
<bean class="org.springframework.web.context.support.ServletContextAttributeExporter"
p:attributes-ref="attributes"/>
But arguably not worth the additional indirection and (IMO) lower readability.

How to avoid hardcoded names in DelimitedLineTokenizer names?

I am using DelimitedLineTokenizer to read from a txt file using FlatFileItemReader. However, is there a way to avoid hardcoding the "names" property of the fields ? Instead can we use any bean ?
<bean id="employeeReader" class="org.springframework.batch.item.file.FlatFileItemReader"
scope="step">
<property name="resource" value="#{jobParameters['input.file.name']}" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="empId,empName,empAge" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="targetType" value="com.example.Employee" />
</bean>
</property>
</bean>
</property>
</bean>
Currently there is not because the result of the LineTokenizer's work is a FieldSet. A FieldSet is like a ResultSet for a file. So just like in a ResultSet, we need to reference each "column" by something which in this case is the name. The LineTokenizer has no insight into where the data is ending up (what the object looks like) so we have no way to introspect it. If you wanted to take a more dynamic approach, you'd want to implement your own LineMapper that combines the two functions together allowing for that type of introspection.
As always, pull requests are appreciated!
In addition to Michael Minella's answer, here's what you can do :
You can use a value such as #{jobParameter['key']}, #{jobExecutionContext['key']} or #{stepExecutionContext['key']} in the <property name="names"> tag.
This means that you can have a step or a listener which does your business logic and save the result at any time in the ExecutionContext :
stepExecution.getJobExecution().getExecutionContext().put(key, value);
Keep in mind though, that the field names of a DelimitedLineTokenizer needs a String (well not really, but close enough), not a bean.

Injecting additional values after initial bean initialization

I have a situation where a bean is originally defined by a project to be used in a specific runtime environment. However, this project is also used in a different runtime environment that would either
like to redefine a specific bean
or (better) inject some additional values into the already initialized bean.
I hate overwriting beans, can get very confusing, so I want to try the second option.
A more concrete example:
base.xml might include
<bean id="xxx" class="yyy">
<constructor-arg>
<map>
<entry key="key1" value="val1"/>
</map>
</constructor-arg>
</bean>
This bean is used all over the place in project A.
Project B uses project A, loads all its spring configuration, but needs to add
another value to the bean's map.
Something like.. I dunno
<bean id="xxx1" class="yyy" parent="xxx">
<property name="additionalMapValues">
<map>
<entry key="key1" value="val1"/>
</map>
</property>
</bean>
Don't really care about the new bean, I want to affect the original bean. Any way to do this?
Thanks.
If it's just additional map properties (or other collection properties) that you need in the new bean, you can use Spring's collection merging functionality. For example - similar to what you'd already shown, project A might declare:
<bean id="xxx" class="yyy">
<property name="mapValues">
<map>
<entry key="key1" value="val1"/>
</map>
</property>
</bean>
And then in project B, you can create a new bean definition that extends this - and merge in any new properties using the merge="true" attribute.
<bean id="xxx1" parent="xxx">
<property name="mapValues">
<map merge="true">
<entry key="key2" value="val2"/>
</map>
</property>
</bean>
Note that both beans would be concrete in this case. So parent bean xxx would only include key1 whereas child bean xxx1 would just include both key1 and key2.
More info here and another example here

injecting a spring bean property different values according to its context

I have a spring bean my_bean with a property my_map, and I want to inject it with the value "X" or with the value "Y". The bean:
<bean id="my_bean">
<property name="my_map">
<map>
<entry key="p" value="X" />
</map>
</property>
</bean>
It's referenced in a very deep hierarchy by the bean root_a:
<bean id="root_a">
<ref bean="root_a_a"/>
</bean>
<bean id="root_a_a">
<ref bean="root_a_a_a"/>
</bean>
<bean id="root_a_a_a">
<ref bean="my_bean"/>
</bean>
and this entire deep hierarchy is referenced again from the bean root_b. In the ref of my_bean from this hierarchy I would the property to be injected with the value "Y", but I would not like to duplicate the entire hierarchy twice.
<bean id="root_b">
<ref bean="root_a_a"/>
</bean>
How do I do this in the spring XML? can you think of a clever spring EL solution? something else? I prefer all my configuration to be done in the XML and no Java code...
By default Spring beans are singletons, which means that once bean="my_bean" is created it is shared between other components e.g. shared between A => bean id="root_a_a_a" and B => bean id="root_b_b_b"
The answer to your question depends on what exactly you are trying to achieve.
Two Beans
If bean="my_bean" does not need to be shared between A and B, then create two beans:
inject this one to A
<bean id="myBeanX" class="My">
<property name="culprit" value="X"/>
</bean>
and this one to B
<bean id="myBeanY" class="My">
<property name="culprit" value="Y"/>
</bean>
notice they both are instances of the same class.
You can also inline them into collaborators (A / B) if you don't need them for anything else:
<bean id="root_a_a_a">
<constructor-arg>
<bean class="My">
<property name="culprit" value="X"/>
</bean>
</constructor-arg>
</bean>
You can also have a factory bean that creates root_a_a_a given the property for a class My, but that would be an overkill.
Single Bean
In case A and B needs to share the exact same reference to bean="my_bean", the question is: are you ok with A and B changing my_bean's state after my_bean is created? Probably not.
If you are, which would be 0.41172% chance, you can change my_bean's value to whatever you need in A's or B's constructors => would not recommend
Hence you either would go with the Two Bean approach (which is most likely what you want), or you would need to refactor a property for "X" and "Y" into another e.g. myConfig component.
EDIT after the question was edited
If root_a and root_b will not be used together in the same instance of the context,you can use Spring Profiles (example), or SpEL / Property Based solutions (example)
e.g.
<bean id="my_bean">
<property name="my_map">
<map>
<entry key="p" value="${ENV_SYSTEM:X}" />
</map>
</property>
</bean>
It will set it to X by default, unless a ENV_SYSTEM system variable is set (e.g. to Y).

Resources