構築時に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が「ほぼ無料」であるドイツ、またはフェラーリが「手ごろな価格」であるインドに展開する必要があるとしましょう。どちらの国でも、車は3つ(決定論的なセット)しかなく、それ以上ではないため、列挙型ですが、「内部」の値は異なる場合があります。したがって、これは不変のコンテキスト初期化の場合です。
enum
自体を設定するということですか?
それは可能ではないと思います。列挙型はstatic
の性質を持つため、インスタンス化できません。したがって、Spring IoCはcreateenums
もできないと思います。
一方、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(...);
}
プロパティファイルで、特定のロケールごとに1つ、可能な内部列挙値を説明するキーを入力します。
Car.NANO.cost=Very cheap
Car.NANO.madeIn=India
Car.MERCEDES.cost=Expensive
...
このアプローチの唯一の欠点は、列挙型フィールド(cost、madeIn)の名前をJava文字列としてコード化)で繰り返す必要があることですEdit:Andプラス面では、すべての列挙型のすべてのプロパティを、言語/ロケールごとに1つのプロパティファイルにスタックできます。
OK、それはかなり厄介ですが、それは可能です。
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;
}
}
}
Springを介して新しい列挙値を作成することはできません。それらはクラスで宣言する必要があります。ただし、列挙値はいずれにしても(JVMによって作成された)シングルトンになるため、設定する必要のある構成や注入するサービスは、列挙クラスの静的メソッドを呼び出すことで実行できます。
文字列を受け取るセッター(またはコンストラクター引数)を提供せず、単純に Enum.valueOf(String s) を呼び出して、文字列から列挙型に変換します。これが失敗すると例外がスローされ、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);
<bean id="car" class="Foo">
<property name="carString" value="NANO" />
</bean>
そして、あなたのクラスFooには、このセッターがあります:
public void setCar(String carString) {
this.carString = Car.valueOf(carString);
}
何をセットアップする必要がありますか?値はクラスのロード時に作成され、列挙型であるため、他の値は作成できません(ソースに追加して再コンパイルしない限り)。
それが列挙型のポイントであり、型を定数で不変の値の明示的な範囲に制限できるようにします。これで、コードのどこでも、Carタイプまたはその値、Car.NANO、Car.MERCEDESなどを参照できます。
一方、明示的な範囲ではない一連の値があり、このタイプの任意のオブジェクトを作成できるようにするには、投稿と同じctorを使用しますが、列挙型クラスではなく、通常。次に、Springはさまざまなヘルパークラスを提供して、いくつかのソース(XMLファイル、構成ファイルなど)から値を読み取り、そのタイプのリストを作成します。
Enumを変更しようとするのはばかげており、設計目標に完全に反しています。列挙型は、定義により、グループ内の個別の値を表します。より多くの/より少ない値が必要な場合は、ソースを更新する必要があります。セッターを追加することで列挙型の状態を変更できますが(結局のところ、それらは単なるオブジェクトです)、システムをハッキングします。
これが私がたどった解決策です(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定義:
<?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>
これは機能しますが、1つの大きな弱点があります。CarEnumerationInitializer
列挙を参照する前にCar
をインスタンス化する必要があります。それ以外の場合、CarPropertiesはnullです。つまり、Car
が読み込まれます(少なくともIllegalStateException
がスローされ、予測可能かつ文書化された方法でクラッシュします)。 carInitializer
Beanのプロパティlazy-init
明示的にfalse
に設定して、できるだけ早くロードする必要があることを強調します。 Car
への最初の呼び出しがどこで行われるかを簡単に推測できる単純なアプリケーションでは、これが役立つと思います。より大きなものについては、おそらくあなたがそれを使用することを勧めなかったほど乱雑になるでしょう。
このヘルプ、コメント、投票(上下)を歓迎します:)これを受け入れられる答えにするために、数日待ってから、反応させます。
わかりました、これは少し複雑ですが、統合する方法が見つかるかもしれません。列挙型は実行時に変更することを意図していないため、これはリフレクションハックです。申し訳ありませんが、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 から取得されます。
Enumクラスをfactory beanとして使用できます。例:enum値を持つ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"/>