在beans.xml中定义额外的占位符/属性

6

我有一个字符串列表,想在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>

功能正常,但每次需要编写完整的限定常量名称com.myapp.longname.verylong.WelcomeController。我想只写一次。我找到的一个解决办法是将其替换为属性如my.prefix,因此我可以仅编写我的短前缀而不是真实的完整路径。但是,我将需要在全局“命名空间”中添加一个只需要一次的属性。我希望仅为此列表或至少仅为此beans.xml文件定义占位符。我已经尝试直接在beans.xml中使用PropertyPlaceholderConfigurer定义属性,并且它起作用了,但然后我所有的初始属性都不再可用。

那么,我该如何避免每次在列表中编写com.myapp.longname.verylong.WelcomeController作为前缀,并仅定义一次?理想情况下应该像这样

<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>

2
只是好奇,这真的是个问题吗?我想它不会经常改变,我找不到任何解决方法 :| - vladtkachuk
@vladtkachuk 嗯,是的,在我的真实项目中,prefix.to.the.class.exact.class.my.legacy.code.some.prefix 相当长。我不太喜欢多次复制粘贴它。当然这不是什么大问题,但如果有更好的解决方法会很好。 - AvrDragon
你使用基于 XML 的配置而不是基于 Java 的 @Configuration,有什么原因吗? - lance-java
@lance-java 是的,原因叫做Hybris,它非常可靠。 - AvrDragon
6个回答

1

这并不是一个坏的解决方案,但有一个缺点:在全局范围内引入一些变量,这些变量仅作为字符串前缀在一个XML位置中需要。是否可以直接在XML文件中定义它?最好只在该列表内部使用。 - AvrDragon
Edited answer please checkout - Eskandar Abedini

1

看一下Constants,它允许你从类中提取常量和值,这样你只需要定义一个类而不是为每个属性定义类。

<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>

这将在您的上下文中公开所有以FRACTION结尾的常量作为一个Set。而无需定义每个常量。您还可以将其放入FactoryBean中,使其更加简单。

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"));
  }
}

您现在可以在XML中定义这个FactoryBean
<bean id="myFractions" class="FractionExporter" />

该方法名为createInstance(),而非createObject() - Olivier
这就是你从脑海中随便打出的结果:)谢谢,已经修复了。 - M. Deinum

1
请试着尝试这个。
<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>

看起来不错,但是...它就是不能用。我得到了:表达式解析失败;嵌套异常是org.springframework.expression.spel.SpelEvaluationException: EL1005E:找不到类型“my.prefix”。否则它将是我所需要的。你测试过你的答案吗? - AvrDragon
不,让我测试一下然后再回复。 - Arun Sai Mustyala
请问您现在可以尝试一下吗? - Arun Sai Mustyala

0
一个解决方案是实现一个FactoryBean,通过扩展AbstractFactoryBean类来实现:
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;
    }
}

这里是一个使用ConstantListFactoryBean的样例beans.xml:

<?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>

以及保存常量的示例类:

package test;

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

最后,这里有一些代码展示它是如何工作的:
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);
    }
}

输出:

[red, blue, green]

1
在我看来,这就像是为了去买杂货而建造火箭:D 但是知道这个也很好。 - vladtkachuk

0

另一种更简单的方法是创建一个对象来包装类实例(例如调用ClassReflector)。然后在其上定义一个方法,以返回常量字段的值。

例如:

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);
    }

}

然后在 XML 中,使用此方法来获取不同常量字段的值。

<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>

现在如果Spring已经随附了那个,那就好了... https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/Constants.html - M. Deinum

0
根据评论和问题,由于您的要求非常简单,我将定义一个通用映射并将键值放在其中,然后在列表内访问它:
<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>

我能看到的好处是,如果您有多个前缀/属性,并且希望在不定义任何全局作用域bean的情况下在不同的bean中访问它,则可以将尽可能多的键值放入此映射中。

如果这对您有所帮助,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接