使用Spring IoC设置枚举值

有没有办法在构建时通过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是“几乎免费”,或者在印度,法拉利是“负担不起”。 在这两个国家,只有三辆汽车(确定性集),不多也不少,因此是枚举,但它们的“内在”值可能不同。 因此,这是不可变的上下文初始化的情况。

你的意思是设置enum本身?

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

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

我认为不能从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)的名称重复为字符串。 编辑 :从好的方面来说,您可以将所有枚举的所有属性堆叠到每个语言/区域设置的一个属性文件中。

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

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

这是关键组成部分:

 public class EnumAutowiringBeanFactoryPostProcessor implements BeanFactoryPostProcessor { private final List> enumClasses = new ArrayList<>(); public EnumAutowiringBeanFactoryPostProcessor(Class... enumClasses) { Collections.addAll(this.enumClasses, enumClasses); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { for (Class 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; } } } 

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

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

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

我是通过以下方式完成的:

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

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

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

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

    

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

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

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

好吧,这有点复杂,但您可能会找到一种方法来集成它。 枚举并不意味着在运行时更改,因此这是一个reflection黑客。 对不起,我没有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()); 

这取自本网站 。

这是我遇到的解决方案(感谢Javashlook,他的回答让我走上正轨)。 它有效,但它很可能不是一种生产级的方式。

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

让我们来看看修改后的Car enum:

 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定义:

                      

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

希望这个帮助,评论和投票(上下)非常欢迎:)我会等待几天让这一个成为公认的答案,让你做出反应。

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

     NON_NULL    

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