Использование Spring IoC для установки значений перечисления - PullRequest
27 голосов
/ 02 апреля 2009

Есть ли способ установить такие значения перечисления через 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 "почти свободны", или в Индии, где Ferrari "недоступны". В обеих странах есть только три автомобиля (детерминированный набор), не больше, не меньше, отсюда и enum, но их «внутренние» значения могут отличаться. Итак, это случай контекстной инициализации неизменяемых.

Ответы [ 12 ]

14 голосов
/ 02 апреля 2009

Вы имеете в виду настройку самого enum?

Я не думаю, что это возможно. Вы не можете создавать экземпляры перечислений, потому что они имеют static природу. Поэтому я думаю, что Spring IoC не может создать enums.

С другой стороны, если вам нужно что-то инициализировать с помощью enum, пожалуйста, ознакомьтесь с главой Spring IoC . (поиск по enum) Есть простой пример, который вы можете использовать.

10 голосов
/ 02 апреля 2009

Не думаю, что это можно сделать из конфигурации ApplicationContext Spring. Но действительно ли вам это нужно сделать в 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
...

Единственным недостатком этого подхода является необходимость повторять имя полей перечисления (cost, madeIn) в коде Java в виде строк. Редактировать : Кроме того, вы можете объединить все свойства всех перечислений в один файл свойств для каждого языка / локали.

6 голосов
/ 24 мая 2013

ОК, это довольно неудобно, но МОЖЕТ быть сделано.

Это правда, что 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;
        }
   }

}
4 голосов
/ 05 августа 2011

Вы не можете создавать новые значения перечисления через Spring, они должны быть объявлены в классе. Однако, поскольку значения перечисления в любом случае будут одиночными (созданными JVM), любые конфигурации, которые должны быть установлены, или службы, которые будут внедрены, могут быть выполнены посредством вызова статических методов в классе перечисления:

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

3 голосов
/ 19 марта 2015

Я сделал это следующим образом:

@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);
3 голосов
/ 02 апреля 2009

Почему бы не предоставить установщик (или аргумент конструктора), который принимает строку, и просто вызвать Enum.valueOf (String s) для преобразования строки в перечисление. Обратите внимание, что в случае сбоя будет сгенерировано исключение, и ваша инициализация Spring выйдет из строя.

1 голос
/ 05 сентября 2009

Попытка изменить Enum - это глупо и полностью противоречит их целям. Перечисление по определению представляет отдельное значение в группе. Если вам когда-либо понадобится больше / меньше значений, вам нужно обновить источник. Хотя вы можете изменить состояние перечисления, добавив сеттеры (в конце концов, они просто объекты), вы взломали систему.

1 голос
/ 02 апреля 2009
<bean id="car" class="Foo">
    <property name="carString" value="NANO" />
</bean>

И тогда в вашем классе Foo у вас будет такой сеттер:

public void setCar(String carString) {
    this.carString = Car.valueOf(carString);
}
1 голос
/ 02 апреля 2009

Что нужно настроить? Значения создаются при загрузке класса и, поскольку это перечисление, никакие другие значения не могут быть созданы (если вы не добавите их в исходный код и не перекомпилируете).

В этом суть перечисления, чтобы иметь возможность ограничить тип явным диапазоном постоянных неизменных значений. Теперь в любом месте вашего кода вы можете ссылаться на тип Car или его значения, Car.NANO, Car.MERCEDES и т. Д.

Если, с другой стороны, у вас есть набор значений, который не является явным диапазоном, и вы хотите иметь возможность создавать произвольные объекты этого типа, вы бы использовали тот же ctor, что и в вашем посте, но как обычный, а не enum класс. Затем Spring предоставляет различные вспомогательные предложения для чтения значений из некоторого источника (XML-файл, файл конфигурации и т. Д.) И создания списков этого типа.

0 голосов
/ 18 мая 2012

Вы можете использовать Enum class как factory 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) работает более простое решение: вы просто записываете значение enum, и Spring распознает, что делать:

<property name="serializationInclusion" value="NON_NULL"/>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...