使用Spring IoC设置枚举值

时间:2009-04-02 16:02:32

标签: java enums spring

有没有办法在构建时通过Spring IoC设置这样的枚举值?

我想要做的是在课程加载时注入下面代码片段中硬编码的值:

public enum Car
{
        NANO ("Very Cheap", "India"),
        MERCEDES ("Expensive", "Germany"),
        FERRARI ("Very Expensive", "Italy");

        public final String cost;
        public final String madeIn;

        Car(String cost, String madeIn)
        {
                this.cost= cost;
                this.madeIn= madeIn;
        }

}

让我们说应用程序必须部署在德国,Nanos是“几乎免费”,或者在印度,法拉利是“负担不起”。在这两个国家,只有三辆汽车(确定性集),不多也不少,因此是枚举,但它们的“内在”值可能不同。因此,这是一个不可变的上下文初始化的情况。

13 个答案:

答案 0 :(得分:14)

您的意思是设置enum本身吗?

我认为这不可行。您无法实例化枚举,因为它们具有static性质。所以我认为Spring IoC也不能创建 enums

另一方面,如果您需要使用enum设置初始化内容,请查看Spring IoC chapter。 (搜索枚举)您可以使用一个简单的示例。

答案 1 :(得分:10)

我认为不能从Spring的ApplicationContext配置中完成。但是,你真的需要Spring完成它,或者你可以使用ResourceBundle解决简单的外化问题;像这样:

public enum Car
{
    NANO,
    MERCEDES,
    FERRARI;

    public final String cost;
    public final String madeIn;

    Car()
    {
            this.cost = BUNDLE.getString("Car." + name() + ".cost");
            this.madeIn = BUNDLE.getString("Car." + name() + ".madeIn");
    }

    private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(...);

}

在属性文件中,每个特定区域设置一个,输入描述可能的内部枚举值的键:

Car.NANO.cost=Very cheap
Car.NANO.madeIn=India
Car.MERCEDES.cost=Expensive
...

这种方法的唯一缺点是必须在Java代码中将枚举字段(cost,madeIn)的名称重复为字符串。 编辑:从好的方面来说,您可以将所有枚举的所有属性堆叠到每个语言/区域设置的一个属性文件中。

答案 2 :(得分:6)

好的,它非常繁琐,但可以完成。

Spring确实无法实例化枚举。但这不是问题 - Spring也可以使用工厂方法。

这是关键组成部分:

public class EnumAutowiringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    private final List<Class<? extends Enum>> enumClasses = new ArrayList<>();

    public EnumAutowiringBeanFactoryPostProcessor(Class<? extends Enum>... enumClasses) {
        Collections.addAll(this.enumClasses, enumClasses);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (Class<? extends Enum> enumClass : enumClasses) {
            for (Enum enumVal : enumClass.getEnumConstants()) {
                BeanDefinition def = new AnnotatedGenericBeanDefinition(enumClass);
                def.setBeanClassName(enumClass.getName());
                def.setFactoryMethodName("valueOf");
                def.getConstructorArgumentValues().addGenericArgumentValue(enumVal.name());
                ((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(enumClass.getName() + "." + enumVal.name(), def);
            }
        }
    }
}

然后以下测试类显示它有效:

@Test
public class AutowiringEnumTest {

    public void shouldAutowireEnum() {
        new AnnotationConfigApplicationContext(MyConig.class);

        assertEquals(AutowiredEnum.ONE.myClass.field, "fooBar");
        assertEquals(AutowiredEnum.TWO.myClass.field, "fooBar");
        assertEquals(AutowiredEnum.THREE.myClass.field, "fooBar");
    }

    @Configuration
    public static class MyConig {

        @Bean
        public MyClass myObject() {
            return new MyClass("fooBar");
        }

        @Bean
        public BeanFactoryPostProcessor postProcessor() {
            return new EnumAutowiringBeanFactoryPostProcessor(AutowiredEnum.class);
        }
    }

    public enum AutowiredEnum {
        ONE,
        TWO,
        THREE;

        @Resource
        private MyClass myClass;

    }

    public static class MyClass {

        private final String field;

        public MyClass(String field) {
            this.field = field;
        }
   }

}

答案 3 :(得分:4)

您不能通过Spring创建新的枚举值,它们必须在类中声明。但是,由于枚举值无论如何都是单例(由JVM创建),因此可以通过在枚举类中调用静态方法来完成应该设置的任何配置或要注入的服务:

http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/beans/factory/config/MethodInvokingFactoryBean.html

答案 4 :(得分:3)

为什么不提供一个带有String的setter(或构造函数参数),只需调用Enum.valueOf(String s)即可将String转换为枚举。请注意,如果失败,将抛出异常,并且您的Spring初始化将会失败。

答案 5 :(得分:3)

我是按照以下方式完成的:

@Component
public class MessageSourceHelper {
    @Inject
    public MessageSource injectedMessageSource;

    public static MessageSource messageSource;

    public static String getMessage(String messageKey, Object[] arguments, Locale locale) {
        return messageSource.getMessage(messageKey, arguments, locale);
    }

    @PostConstruct
    public void postConstruct() {
        messageSource = injectedMessageSource;
    }

}

通过这种方式,您可以轻松地在枚举中使用它来以下列方式获取消息:

MessageSourceHelper.getMessage(key, arguments, locale);

答案 6 :(得分:1)

您需要设置什么?这些值是在类加载时创建的,因为它是枚举,所以不能创建其他值(除非您将它们添加到源并重新编译)。

这是枚举的要点,能够将一个类型限制为显式范围的常量,不可变值。现在,在代码中的任何位置,您都可以引用Car类型或其值,Car.NANO,Car.MERCEDES等。

另一方面,如果您有一组不是显式范围的值,并且您希望能够创建此类型的任意对象,则可以使用与帖子中相同的ctor,但作为一个常规,而不是枚举类。然后Spring提供各种帮助程序来从一些源(XML文件,配置文件,等等)读取值,并创建该类型的列表。

答案 7 :(得分:1)

<bean id="car" class="Foo">
    <property name="carString" value="NANO" />
</bean>

然后在你的班级Foo,你会有这个二传手:

public void setCar(String carString) {
    this.carString = Car.valueOf(carString);
}

答案 8 :(得分:1)

尝试改变Enum是非常愚蠢的,完全违背了他们的设计目标。枚举中的枚举表示组内的不同值。如果您需要更多/更少的值,则需要更新源。虽然您可以通过添加setter(毕竟它们只是对象)来更改枚举状态,但是你会破坏系统。

答案 9 :(得分:0)

好吧,这有点复杂,但你可能会找到一种方法来整合它。枚举不是要在运行时更改,所以这是一个反射黑客。对不起,我没有Spring实现部分,但您可以构建一个bean来接受枚举类或对象,以及另一个可能是新值或值的字段。

Constructor con = MyEnum.class.getDeclaredConstructors()[0];
Method[] methods = con.getClass().getDeclaredMethods();
for (Method m : methods) {
  if (m.getName().equals("acquireConstructorAccessor")) {
    m.setAccessible(true);
    m.invoke(con, new Object[0]);
  }
}
Field[] fields = con.getClass().getDeclaredFields();
Object ca = null;
for (Field f : fields) {
  if (f.getName().equals("constructorAccessor")) {
    f.setAccessible(true);
    ca = f.get(con);
  }
}
Method m = ca.getClass().getMethod(
  "newInstance", new Class[] { Object[].class });
m.setAccessible(true);
MyEnum v = (MyEnum) m.invoke(ca, new Object[] { 
  new Object[] { "MY_NEW_ENUM_VALUE", Integer.MAX_VALUE } });
  System.out.println(v.getClass() + ":" + v.name() + ":" + v.ordinal());

这取自this site

答案 10 :(得分:0)

这是我遇到的解决方案(感谢Javashlook的回答让我顺利进行)。它有效,但它很可能不是一种生产级的方式。

但是,千言万语,这是代码,我会让你自己判断。

让我们来看看修订后的Car枚举:

public enum Car {
    NANO(CarEnumerationInitializer.getNANO()), MERCEDES(
            CarEnumerationInitializer.getMERCEDES()), FERRARI(
            CarEnumerationInitializer.getFERRARI());

    public final String cost;
    public final String madeIn;

    Car(ICarProperties properties) {
        this.cost = properties.getCost();
        this.madeIn = properties.getMadeIn();
    }
}

以下是“探索”课程:

//Car's properties placeholder interface ...
public interface ICarProperties {
    public String getMadeIn();
    public String getCost();
}
//... and its implementation
public class CarProperties implements ICarProperties {
    public final String cost;
    public final String madeIn;

    public CarProperties(String cost, String madeIn) {
        this.cost = cost;
        this.madeIn = madeIn;
    }
    @Override
    public String getCost() {
        return this.cost;
    }
    @Override
    public String getMadeIn() {
        return this.madeIn;
    }
}

//Singleton that will be provide Car's properties, that will be defined at applicationContext loading.
public final class CarEnumerationInitializer {
    private static CarEnumerationInitializer INSTANCE;
    private static ICarProperties NANO;
    private static ICarProperties MERCEDES;
    private static ICarProperties FERRARI;

    private CarEnumerationInitializer(ICarProperties nano,
            ICarProperties mercedes, ICarProperties ferrari) {
        CarEnumerationInitializer.NANO = nano;
        CarEnumerationInitializer.MERCEDES = mercedes;
        CarEnumerationInitializer.FERRARI = ferrari;
    }

    public static void forbidInvocationOnUnsetInitializer() {
        if (CarEnumerationInitializer.INSTANCE == null) {
            throw new IllegalStateException(CarEnumerationInitializer.class
                    .getName()
                    + " unset.");
        }
    }

    public static CarEnumerationInitializer build(CarProperties nano,
            CarProperties mercedes, CarProperties ferrari) {
        if (CarEnumerationInitializer.INSTANCE == null) {
            CarEnumerationInitializer.INSTANCE = new CarEnumerationInitializer(
                    nano, mercedes, ferrari);
        }
        return CarEnumerationInitializer.INSTANCE;
    }

    public static ICarProperties getNANO() {
            forbidInvocationOnUnsetInitializer();
        return NANO;
    }

    public static ICarProperties getMERCEDES() {
            forbidInvocationOnUnsetInitializer();
        return MERCEDES;
    }

    public static ICarProperties getFERRARI() {
            forbidInvocationOnUnsetInitializer();
        return FERRARI;
    }
}

最后,applicationContext定义:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="nano" class="be.vinkolat.poc.core.car.CarProperties">
        <constructor-arg type="java.lang.String" value="Cheap"></constructor-arg>
        <constructor-arg type="java.lang.String" value="India"></constructor-arg>
    </bean>
    <bean id="mercedes"
        class="be.vinkolat.poc.core.car.CarProperties">
        <constructor-arg type="java.lang.String" value="Expensive"></constructor-arg>
        <constructor-arg type="java.lang.String" value="Germany"></constructor-arg>
    </bean>
    <bean id="ferrari" class="be.vinkolat.poc.core.car.CarProperties">
        <constructor-arg type="java.lang.String"
            value="Very Expensive">
        </constructor-arg>
        <constructor-arg type="java.lang.String" value="Italy"></constructor-arg>
    </bean>
    <bean id="carInitializer"
        class="be.vinkolat.poc.core.car.CarEnumerationInitializer"
        factory-method="build" lazy-init="false">
        <constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
            ref="nano" />
        <constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
            ref="mercedes" />
        <constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
            ref="ferrari" />
    </bean>
</beans>

它有效,但有一个主要缺点:CarEnumerationInitializer必须在对Car枚举进行任何引用之前进行实例化,否则CarProperties为null,这意味着在{时{1}不能设置Car的属性{1}}被加载(因此抛出Car,至少以可预测和文档化的方式使其崩溃)。 IllegalStateException bean的属性carInitializer设置为显式lazy-init,以强调需要尽快加载它。 我想说它可能在一个简单的应用程序中很有用,你可以轻松猜出第一次调用false的位置。对于较大的一个,它可能是一个混乱,我不鼓励你使用它。

希望这个帮助,评论和投票(上下)非常欢迎:)我会等几天让这个答案接受,让你做出反应。

答案 11 :(得分:0)

您可以将枚举类用作工厂bean 。示例:使用枚举值设置 serializationInclusion 字段:

            <property name="serializationInclusion">
                <bean class="org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion" factory-method="valueOf">
                    <constructor-arg>   
                        <value>NON_NULL</value>
                    </constructor-arg>
                </bean>
            </property>

但实际上(Spring 3.1)一个更简单的解决方案是有效的:你只需编写枚举值,Spring就会认识到该怎么做:

<property name="serializationInclusion" value="NON_NULL"/>

答案 12 :(得分:0)

在将枚举标签本地化为不同语言环境时,我遇到了同样的问题。

枚举代码:

public enum Type {
   SINGLE("type.single_entry"),
   MULTIPLE("type.multiple_entry"),
   String label;

   Type(String label) {
     this.label = label;
   }

   public String getLabel() {
     String translatedString = I18NTranslator.getI18NValue(getLocale(), label);
     return StringUtils.isEmpty(translatedString) ? label : translatedString;
   }
}

我的I18NTranslator类,该类基本上加载消息源以获取本地化的内容。如果您不写,I18Ntransalator类将取决于springContext,您可能会遇到一个特殊的错误。一段时间可能会遇到相关的依赖关系,这将导致空指针异常。为了解决这个问题,我付出了很多努力。

@Component
@DependsOn({"springContext"})
public class I18NTranslator {

    private static MessageSource i18nMessageSource;

    public static String getI18NValue(Locale locale, String key) {
        if (i18nMessageSource != null)
            return i18nMessageSource.getMessage(key, null, locale);
        return key;
    }

    @PostConstruct
    public void initialize() {
        i18nMessageSource = SpringContext.getBean("i18nMessageSource", MessageSource.class);
    }
}

我们必须设置spring上下文

@Component
@Slf4j
public class SpringContext implements ApplicationContextAware {

    private static ApplicationContext context;

    public static <T extends Object> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }

    public static <T extends Object> T getBean(String beanClassName, Class<T> beanClass) {
        return context.getBean(beanClassName, beanClass);
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        SpringContext.context = context;
    }
}

现在是时候为I18NMessageSource定义bean了。

@Configuration
public class LocaleConfiguration implements WebMvcConfigurer {

    @Bean(name = "i18nMessageSource")
    public MessageSource getMessageResource() {
        ReloadableResourceBundleMessageSource messageResource = new ReloadableResourceBundleMessageSource();
        messageResource.setBasename("classpath:i18n/messages");
        messageResource.setCacheSeconds(3600);
        messageResource.setDefaultEncoding("UTF-8");
        return messageResource;
    }

    @Bean(name = "localeResolver")
    public LocaleResolver getLocaleResolver() {
        return new UrlLocaleResolver();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //UrlLocalInterceptor is custom locale resolver based on header paramter.
        UrlLocaleInterceptor localeInterceptor = new UrlLocaleInterceptor();
        registry.addInterceptor(localeInterceptor);
    }
}

PS:如果您需要自定义拦截器代码,我可以在评论中分享。 定义资源/ i18n文件夹内的所有本地属性文件,其消息前缀为messages_en.properties(英语)和messages_fr.properties(法语)。