简体   繁体   中英

Dynamic enum with OpenJDK 11 using reflection

I'm working on a project running with JDK8 and we want to migrate it to OpenJDK11.

But, there is legacy code that creates enums dynamically at runtime (using reflection and sun.reflect.* packages):

public class EnumUtil {
    static Object makeEnum(...) {
        ...
        enumClass.cast(sun.reflect.ReflectionFactory.getReflectionFactory() .newConstructorAccessor(constructor).newInstance(params));
    }
}

Or

    // before, field is made accessible, the modifier too
    sun.reflect.FieldAccessor fieldAccessor = sun.reflect.ReflectionFactory.getReflectionFactory().newFieldAccessor(field, false);
    field.set(target, value);

For example, let's say we have the enum AEnum :

public enum AEnum {
    ; // no values at compile time

    private String label;

    private AEnum (String label) {
        this.label = label;
    }

Then, we add enum values like this:

EnumUtil.addEnum(MyEnum.class, "TEST", "labelTest");

Finally, we have, at runtime, a value AEnum.TEST (not with that direct call, but with Enum.valueOf ) with the label = labelTest.

Unfortunately, sun.reflect.* classes are no longer available in OpenJDK11.

I've tried using jdk.internal.reflect.ConstructorAccessor but I'm getting the error java: package jdk.internal.reflect does not exist . And I don't think it's a good idea to rely on jdk.internal.* classes.

Is there any OpenJDK11 alternative to create enums at runtime?

This answer contains a working approach which uses only the standard API and still works, even with JDK 17.

Since it uses a JDK type for the example, which requires an --add-opens java.base/java.lang=… argument at startup time, here's an example using its own enum type which does not require any modifications to the environment.

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.util.EnumSet;

class EnumHack {
    public static void main(String[] args) throws Throwable {
        System.out.println(Runtime.version());
        Constructor<Example> c
            = Example.class.getDeclaredConstructor(String.class, int.class);
        c.setAccessible(true);
        MethodHandle h = MethodHandles.lookup().unreflectConstructor(c);
        Example baz = (Example) h.invokeExact("BAZ", 42);
        System.out.println("created Example " + baz + "(" + baz.ordinal() + ')');
        EnumSet<Example> set = EnumSet.allOf(Example.class);
        System.out.println(set.contains(baz));
        set.add(baz);
        System.out.println(set);
    }

    enum Example {
        FOO, BAR
    }
}

Since it doesn't require special setup, it can be demonstrated on Ideone

12.0.1+12
created Example BAZ(42)
false

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 42 out of bounds for length 2
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:105)
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:78)
    at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:472)
    at java.base/java.lang.String.valueOf(String.java:3042)
    at java.base/java.io.PrintStream.println(PrintStream.java:897)
    at EnumHack.main(Main.java:18)

This not only shows that the hack does its job, but also some of the problems cause by this. The set supposed to contain all elements doesn't contain the new constant and after adding it manually, the resulting inconsistent state produces exceptions on subsequent operations.

So, such enum constant must not be used with the standard tools for enum types, which matches what has been said in the question's comments, it loses the advantage of being an enum type. In fact, it's worse than that.

So the approach shown above should only be used as a temporary work-around, or not at all.

As you all mentionned in the comments' section, adding enum values at runtime is a very bad idea, breaking the contract of an enum.

So, I've modified all the cases into a POJO object, maintaining a map.

In a simplified format, AEnum becomes:

public class AEnum extends DynamicEnum { // DynamicEnum has utility methods in order to act as close as a real enum.
    @Getter
    private String label;

    private static final Map<String, AEnum> map = new LinkedHashMap<>();

    protected AEnum (String name, String label) {
        this.name = name;
        this.label= label;
    }

    public static AEnum addInMap(String name, String label) {
        AEnum value = new DossierSousType(name, label);
        map.put(name, value);
        return value;
    }
}

We read the dynamic values from the database, so I've made an utility class to load everything.

public static <T extends DynamicEnum> T addEnum(final Class<T> type, final String name, final String label) throws TechnicalException {
    try {
        Method method = type.getDeclaredMethod("addInMap", String.class, String.class);
        return (T) method.invoke(null, name, label);
    } catch (... e) {
        // ...
    }
}

Then:

addEnum(AEnum.class, "TEST", "labelTest");
addEnum(AEnum.class, "TEST2", "labelTest2");
AEnum.getAll() // returns a list with the two entries

Moreover, if we use this "false enum" inside a persisted Entity , we have a converter to manage conversion between String and AEnum .

@Entity
@Table(name = TABLE_NAME)
...
public class MyEntity {
    @Column(name = COLUMN_TYPE)
    @Convert(converter = AEnumConverter.class)
    private AEnum type;

AEnumConverter implements javax.persistence.AttributeConverter :

@Converter
public class AEnumConverter implements AttributeConverter<AEnum , String> {

    @Override
    public String convertToDatabaseColumn(AEnum type) {
        return type != null ? type.getName() : null;
    }

    @Override
    public AEnum convertToEntityAttribute(String type) {
        return AEnum .getEnum(type);
    }
}

With this mechanism, everything works perfectly, and we no longer need sun.reflect.* !

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM