6

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>
AvrDragon
  • 7,139
  • 4
  • 27
  • 42
  • 2
    just interested, is it really an issue? it won't be changed so often I suppose, I do not find any ways to do this :| – vladtkachuk Jun 24 '22 at 12:27
  • @vladtkachuk well, yes, in my real project prefix.to.the.class.exact.class.my.legacy.code.some.prefix is pretty long. I do not really like to copypaste it multiple times. It is not a big deal of course, but would be nice to have. – AvrDragon Jun 27 '22 at 13:51
  • Is there a reason why you are using xml based configuration instead of java based @Configuration? – lance-java Jun 28 '22 at 20:48
  • @lance-java yep, the reason is called Hybris and it is pretty solid reason – AvrDragon Jun 29 '22 at 13:35

6 Answers6

1

Try defining your prefix in properties file and use it in your beans.xml as shown here:

Best ways to deal with properties values in XML file in Spring, Maven and Eclipses

and here

Using Variable Substitution from Configuration Files in Spring

Another solution is using SpEL

<property name="userCountry" value="#{'India'}" />

Spring Expression Language (SpEL) Example

Eskandar Abedini
  • 2,090
  • 2
  • 13
  • Well it is not a bad solution, but one downside: polluting the global scope with some variable, that is needed only in one xml place as a string-prefix. Is it somehow possible to define it directly in XML file? Ideally with scope only inside that list. – AvrDragon Jun 27 '22 at 13:56
  • Edited answer please checkout – Eskandar Abedini Jun 27 '22 at 15:28
1

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>
Arun Sai Mustyala
  • 1,736
  • 1
  • 11
  • 27
1

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" />
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
0

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]
Olivier
  • 13,283
  • 1
  • 8
  • 24
0

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>
Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • Now if only Spring would ship with that already... https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/Constants.html – M. Deinum Jun 28 '22 at 09:44
0

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.

Ashish Patil
  • 4,428
  • 1
  • 15
  • 36