Using .properties File with Custom Bean Parser

I have a custom implementation of AbstractSingleBeanDefinitionParser that allows me to define 3D vectors in my spring configuration with less ... ceremony than it would otherwise.

<rbf:vector3d id="test_vector" delimeter=";" value="45;46;47"/> 

This works great and I have been using it for several months without any problems. Yesterday, I tried to determine the value in the .properties file as follows:

In test.properties I have:

 vector3d.value=1,2,3 

And in the xml file I have:

 <context:property-placeholder location="test.properties"/> <rbf:vector3d id="test_vector_with_properties" delimeter="," value="${vector3d.value}"/> 

When I try to run my unit test, it crashes and I get this exception:

 Caused by: java.lang.NumberFormatException: For input string: "${vector3d.value}" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1222) at java.lang.Double.parseDouble(Double.java:510) at scala.collection.immutable.StringLike$class.toDouble(StringLike.scala:234) at scala.collection.immutable.StringOps.toDouble(StringOps.scala:31) at rb.foundation.spring.xml.Vector3DBeanDefinitionParser$$anonfun$1.apply(Vector3DBeanDefinitionParser.scala:25) 

When I use the .properties file for regular beans, it works fine, which leads me to believe that there is a subtlety that I overlooked in my implementation of my analyzer. It is written in scala, but you must follow it:

 class Vector3DBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { override def getBeanClass(element : Element) = classOf[Vector3D] override def doParse(element: Element, builder: BeanDefinitionBuilder) { val delim = element.getAttribute("delimeter") val value = element.getAttribute("value") val values = value.split(delim).map(_.toDouble) builder.addConstructorArgValue(values(0)) builder.addConstructorArgValue(values(1)) builder.addConstructorArgValue(values(2)) } } 

I am happy to add a key replacement, if necessary, I just need to know where / how to do it.

Ideas?

+4
source share
1 answer

So the reason this doesn't work is because your BeanDefinitionParser works much earlier than placeholders will be eliminated. A simple overview, as I understand it:

  • BeanDefinitionParsers parse XML in BeanDefinition objects in memory
  • BeanDefinitions are then loaded into BeanFactory
  • BeanFactoryPostProcessors (including property placeholder configurators) run in bean definitions
  • beans are created from bean definitions

(Of course, other things happen along the way, but these are important steps here.)

So, in order to get the allowed value of the property in your Vector3D object, I think you will have to defer the arguments to the Vector3D constructor until BeanFactoryPostProcessors are started. One way that arises for me is for your BeanDefinitionParser to build a bean definition for Spring FactoryBean instead of Vector3D itself. Then the splitting of the vector value that you have on your Vector3DBeanDefinitionParser should be in the FactoryBean implementation instead.

Sorry, I'm not very familiar with Scala, so this will be in Java.

The FactoryBean class will look something like this:

 import org.springframework.beans.factory.FactoryBean; public class Vector3DFactoryBean implements FactoryBean<Vector3D> { private String delimiter; private String value; private transient Vector3D instance; public String getDelimiter() { return delimiter; } public void setDelimiter(String delimiter) { this.delimiter = delimiter; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public Vector3D getObject() { if (instance == null) { String[] values = value.split(delimiter); instance = new Vector3D( Double.parseDouble(values[0]), Double.parseDouble(values[1]), Double.parseDouble(values[2]) ); } return instance; } @Override public Class<?> getObjectType() { return Vector3D.class; } @Override public boolean isSingleton() { return true; } } 

Then your Vector3DBeanDefinitionParser will simply pass delimiter and value intact to the definition of the Vector3DFactoryBean bean:

 class Vector3DBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { override def getBeanClass(element : Element) = classOf[Vector3DFactoryBean] override def doParse(element: Element, builder: BeanDefinitionBuilder) { val delim = element.getAttribute("delimeter") val value = element.getAttribute("value") builder.addPropertyValue("delimiter", delim) builder.addPropertyValue("value", value) } } 

Then, when the placeholder properties configurator starts, it must resolve the property values ​​in the Vector3DFactoryBean bean definition. When beans are finally created from bean definitions, Vector3DFactoryBean will parse the vector values ​​and create a Vector3D object.

+4
source

Source: https://habr.com/ru/post/1440308/


All Articles