Defining additional placeholder/property only in beans.xml

Viewed 272

I have a list of strings, that i would like to define in beans.xml.

<util:list id="myFractions" value-type="java.lang.String">
    <value>#{ T(com.myapp.longname.verylong.WelcomeController).RED_FRACTION }</value>
    <value>#{ T(com.myapp.longname.verylong.WelcomeController).BLUE_FRACTION }</value>
    <value>#{ T(${my.prefix}).GREEN_FRACTION }</value>
</util:list>

It works fine, but each time I need to write the full qualified constant's name com.myapp.longname.verylong.WelcomeController. I would like to write it only once. One solution I have found is to replace it with a property like my.prefix so I can write only my short prefix instead of the real full path. But then I will need to pollute the global "namespace" with property that is only needed once. I would like to define a placeholder only for this list or at least only for this beans.xml file. I have already tried to define a property directly in beans.xml with PropertyPlaceholderConfigurer and it works, but then all my inital properties are not available anymore.

So how can I avoid to writing com.myapp.longname.verylong.WelcomeController each time in a list as a prefix and only define it once? Ideally something like

<util:list id="myFractions" value-type="java.lang.String">
    <define-local-placeholder name="my.prefix" value="com.myapp.longname.verylong.WelcomeController" />
    <value>#{ T(${my.prefix}).RED_FRACTION }</value>
    <value>#{ T(${my.prefix}).BLUE_FRACTION }</value>
    <value>#{ T(${my.prefix}).GREEN_FRACTION }</value>
</util:list>
6 Answers

Please give a try on this

<context:property-placeholder properties-ref="shorthandHelperConstants"/>

<util:properties id="shorthandHelperConstants">
    <prop key="my.prefix">com.myapp.longname.verylong.WelcomeController</prop>
</util:properties>

<util:list id="myFractions" value-type="java.lang.String">
    <value>#{ T(${shorthandHelperConstants['my.prefix']}).RED_FRACTION }</value>
    <value>#{ T(${shorthandHelperConstants['my.prefix']}).BLUE_FRACTION }</value>
    <value>#{ T(${shorthandHelperConstants['my.prefix']}).GREEN_FRACTION }</value>
</util:list>

Checkout out Constants. Which allows you to extract constants and values from classes, this way you only need to define your class onetime instead for each property.

<bean id="someConstants" class="org.springframework.core.Constants">
  <constructor-arg value="your-class-name=here" />
</bean>

<bean id="myFractions" factory-bean="someConstants" factory-method="getValuesForSuffix">
  <constructor-arg value="FRACTION" />
</bean>

This will expose all of your constants which end with FRACTION as a Set in your context. Without you needing to define each and every constant. You could also place this into a FactoryBean making it a little easier

public class FractionExporter extends AbstractFactoryBean<List<String>> {

  @Override
  public Class<List> getObjectType() {
        return List.class;
  }

  protected List<String> createInstance() {
    Constants constants = new Constants(YourClass.class);
    return new ArrayList(constants.getValuesForSuffix("FRACTION"));
  }
}

You could now define this FactoryBean in XML

<bean id="myFractions" class="FractionExporter" />

A solution is to implement a FactoryBean, by extending the AbstractFactoryBean class:

package test;

import java.util.*;
import org.springframework.beans.factory.config.AbstractFactoryBean;

public class ConstantListFactoryBean extends AbstractFactoryBean<List<Object>>
{
    private Class<?> targetClass;
    private List<String> constantNames;

    public void setTargetClass(Class<?> targetClass)
    {
        this.targetClass = targetClass;
    }

    public void setConstantNames(List<String> constantNames)
    {
        this.constantNames = constantNames;
    }

    @Override
    public Class<List> getObjectType()
    {
        return List.class;
    }

    @Override
    protected List<Object> createInstance() throws Exception
    {
        ArrayList<Object> list = new ArrayList<Object>();
        for(String name : constantNames)
            list.add(targetClass.getField(name).get(null));
        return list;
    }
}

Here is a sample beans.xml that uses the ConstantListFactoryBean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="colors" class="test.ConstantListFactoryBean">
        <property name="targetClass" value="test.Colors"/>
        <property name="constantNames">
            <list>
                <value>RED</value>
                <value>BLUE</value>
                <value>GREEN</value>
            </list>
        </property>
    </bean>
</beans>

And the sample class that holds the constants:

package test;

public class Colors
{
    public static final String RED = "red";
    public static final String BLUE = "blue";
    public static final String GREEN = "green";
}

And finally, some code that shows that it works:

import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test
{
    public static void main(String[] args)
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        List colors = (List)context.getBean("colors");
        System.out.println(colors);
    }
}

Output:

[red, blue, green]

Another simpler way is to create an object that wrap a class instance (e.g call ClassReflector). Then define a method on it to return the value of the constant field.

For example :

import org.springframework.util.ReflectionUtils;

public class ClassReflector {

    private Class<?> clazz;

    public ClassReflector(String className) throws Exception {
        this.clazz = Class.forName(className);
    }

    public String getFieldVal(String fieldName) throws Exception {
        return (String) ReflectionUtils.getField(clazz.getField(fieldName), null);
    }

}

Then in the XML, use this method to get the value of different constant fields.

<bean id="cf" class="com.myapp.longname.verylong.ClassReflector">
    <constructor-arg value="com.myapp.longname.verylong.WelcomeController" />
</bean>


<util:list id="myFractions" value-type="java.lang.String">
    <value>#{cf.getFieldVal('RED_FRACTION')}</value>
    <value>#{cf.getFieldVal('BLUE_FRACTION')}</value>
    <value>#{cf.getFieldVal('GREEN_FRACTION')}</value>
</util:list>

As per comments & question, as your requirement is quite minimal , I would define one general purpose map & put key-value there & then access it inside your list:

<bean id="map" class="java.util.HashMap">
    <constructor-arg>
        <map key-type="java.lang.String" value-type="java.lang.String">
            <entry key="my.prefix" value="com.myapp.longname.verylong.WelcomeController" />
        </map>
    </constructor-arg>
</bean>


<util:list id="myFractions" value-type="java.lang.String">
    <value>#{ T(${map['my.prefix']}).RED_FRACTION }</value>
    <value>#{ T(${map['my.prefix']}).BLUE_FRACTION }</value>
    <value>#{ T(${map['my.prefix']}).GREEN_FRACTION }</value>
</util:list>

Benefit I can see here is you can put as many key-values in this map if you have multiple prefixes / properties of this kind & you want to access it in different beans without defining any global scoped beans.

Please let me know if this is of any help.

Related