Подтвердить что ты не робот

Использование 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;
        }

}

Скажем, что приложение должно быть развернуто в Германии, где Нанос "почти свободен" или в Индии, где Феррари "недоступны". В обеих странах есть только три автомобиля (детерминированный набор), не более, не менее, следовательно, перечисление, но их "внутренние" значения могут отличаться. Итак, это случай контекстной инициализации неизменяемых.

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

Хорошо, это довольно сложно, но это может быть сделано.

Верно, что Spring не может создавать экземпляры. Но это не проблема - Spring также может использовать методы factory.

Это ключевой компонент:

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

Хорошо, это немного сложно, но вы можете найти способ его интегрировать. Перечисления не должны меняться во время выполнения, так что это взлом. К сожалению, у меня нет части реализации Spring, но вы можете просто создать bean, чтобы взять класс или объект enum, а также другое поле, которое будет новым значением или значениями.

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

Это взято из этого сайта.

Ответ 11

Вот решение, к которому я пришел (спасибо 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 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 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, что означает, что свойства Car нельзя установить при загрузке Car (следовательно, IllegalStateException брошен, чтобы, по крайней мере, сделать его сбоями предсказуемым и документально оформленным способом). carInitializer bean Свойство lazy-init установлено на явный false, чтобы подчеркнуть необходимость его загрузки как можно скорее. Я бы сказал, что это может быть полезно в простом приложении, где вы можете легко догадаться, где будет сделан первый вызов Car. Для более крупного, вероятно, будет такой беспорядок, что я не призывал его использовать его.

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

Ответ 12

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

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