AspectJ - 是否可以扩展枚举值?

时间:2014-08-18 03:28:46

标签: reflection enums aspectj

说我有一个枚举

public enum E {A,B,C}

是否可以通过AspectJ添加另一个值,比如D

在谷歌搜索后,似乎有一种方法来破解私有静态字段$VALUES,然后通过反射调用构造函数(String,int),但似乎不再使用1.7了。

以下是几个链接: http://www.javaspecialists.eu/archive/Issue161.html(由@WimDeblauwe提供)

并且:http://www.jroller.com/VelkaVrana/entry/modify_enum_with_reflection

1 个答案:

答案 0 :(得分:3)

实际上,我建议您重构源代码,可能会为每个枚举值添加一组有效的区域ID。如果您使用Git而不是像SVN这样的老式SCM工具,那么这对于后续合并应该是直截了当的。

如果很明显将来命令列表是动态的,那么使用动态数据结构而不是enum甚至是有意义的。但那应该进入上游代码库。我确信如果干净利落的话,开发者会接受一个很好的补丁或拉取请求。

记住:试图避免重构通常是一种难闻的气味,是疾病的症状,而不是解决方案。我更喜欢解决方案来解决症状问题。清洁的代码规则和软件工艺态度要求。


说完上面的话,现在就是你能做的。它应该在JDK 7/8下工作,我在Jérôme Kehrli's blog上找到它(请务必添加文章下面的一条评论中提到的错误修复)。

枚举扩展程序实用程序:

package de.scrum_master.util;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;

public class DynamicEnumExtender {

    private static ReflectionFactory reflectionFactory =
        ReflectionFactory.getReflectionFactory();

    private static void setFailsafeFieldValue(Field field, Object target, Object value)
        throws NoSuchFieldException, IllegalAccessException
    {
        // let's make the field accessible
        field.setAccessible(true);

        // next we change the modifier in the Field instance to
        // not be final anymore, thus tricking reflection into
        // letting us modify the static final field
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        int modifiers = modifiersField.getInt(field);

        // blank out the final bit in the modifiers int
        modifiers &= ~Modifier.FINAL;
        modifiersField.setInt(field, modifiers);

        FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
        fa.set(target, value);
    }

    private static void blankField(Class<?> enumClass, String fieldName)
        throws NoSuchFieldException, IllegalAccessException
    {
        for (Field field : Class.class.getDeclaredFields()) {
            if (field.getName().contains(fieldName)) {
                AccessibleObject.setAccessible(new Field[] { field }, true);
                setFailsafeFieldValue(field, enumClass, null);
                break;
            }
        }
    }

    private static void cleanEnumCache(Class<?> enumClass)
        throws NoSuchFieldException, IllegalAccessException
    {
        blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
        blankField(enumClass, "enumConstants"); // IBM JDK
    }

    private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes)
        throws NoSuchMethodException
    {
        Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
        parameterTypes[0] = String.class;
        parameterTypes[1] = int.class;
        System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
        return reflectionFactory.newConstructorAccessor(enumClass .getDeclaredConstructor(parameterTypes));
    }

    private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues)
        throws Exception
    {
        Object[] parms = new Object[additionalValues.length + 2];
        parms[0] = value;
        parms[1] = Integer.valueOf(ordinal);
        System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
        return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
    }

    /**
     * Add an enum instance to the enum class given as argument
     *
     * @param <T> the type of the enum (implicit)
     * @param enumType the class of the enum to be modified
     * @param enumName the name of the new enum instance to be added to the class
     */
    @SuppressWarnings("unchecked")
    public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) {
        // 0. Sanity checks
        if (!Enum.class.isAssignableFrom(enumType))
            throw new RuntimeException("class " + enumType + " is not an instance of Enum");

        // 1. Lookup "$VALUES" holder in enum class and get previous enum
        // instances
        Field valuesField = null;
        Field[] fields = enumType.getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().contains("$VALUES")) {
                valuesField = field;
                break;
            }
        }
        AccessibleObject.setAccessible(new Field[] { valuesField }, true);

        try {

            // 2. Copy it
            T[] previousValues = (T[]) valuesField.get(enumType);
            List<T> values = new ArrayList<T>(Arrays.asList(previousValues));

            // 3. build new enum
            T newValue = (T) makeEnum(
                enumType,                         // The target enum class
                enumName,                         // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
                values.size(), new Class<?>[] {}, // could be used to pass values to the enum constuctor if needed
                new Object[] {}                   // could be used to pass values to the enum constuctor if needed
            );

            // 4. add new value
            values.add(newValue);

            // 5. Set new values field
            setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));

            // 6. Clean enum cache
            cleanEnumCache(enumType);

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}

示例应用程序&amp;枚举:

package de.scrum_master.app;

/** In honour of "The Secret of Monkey Island"... ;-) */
public enum Command {
    OPEN, CLOSE, PUSH, PULL, WALK_TO, PICK_UP, TALK_TO, GIVE, USE, LOOK_AT, TURN_ON, TURN_OFF
}
package de.scrum_master.app;

public class Server {
    public void executeCommand(Command command) {
        System.out.println("Executing command " + command);
    }
}
package de.scrum_master.app;

public class Client {
    private Server server;

    public Client(Server server) {
        this.server = server;
    }

    public void issueCommand(String command) {
        server.executeCommand(
            Command.valueOf(
                command.toUpperCase().replace(' ', '_')
            )
        );
    }

    public static void main(String[] args) {
        Client client = new Client(new Server());
        client.issueCommand("use");
        client.issueCommand("walk to");
        client.issueCommand("undress");
        client.issueCommand("sleep");
    }
}

原始枚举的控制台输出:

Executing command USE
Executing command WALK_TO
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant de.scrum_master.app.Command.UNDRESS
    at java.lang.Enum.valueOf(Enum.java:236)
    at de.scrum_master.app.Command.valueOf(Command.java:1)
    at de.scrum_master.app.Client.issueCommand(Client.java:12)
    at de.scrum_master.app.Client.main(Client.java:22)

现在,您可以在加载枚举类之后添加一个带有建议的方面,或者在首次使用扩展枚举值之前在应用程序中手动调用此方法。在这里,我将展示如何在一个方面完成它。

枚举扩展器方面:

package de.scrum_master.aspect;

import de.scrum_master.app.Command;
import de.scrum_master.util.DynamicEnumExtender;

public aspect CommandExtender {
    after() : staticinitialization(Command) {
        System.out.println(thisJoinPoint);
        DynamicEnumExtender.addEnum(Command.class, "UNDRESS");
        DynamicEnumExtender.addEnum(Command.class, "SLEEP");
        DynamicEnumExtender.addEnum(Command.class, "WAKE_UP");
        DynamicEnumExtender.addEnum(Command.class, "DRESS");
    }
}

扩展枚举的控制台输出:

staticinitialization(de.scrum_master.app.Command.<clinit>)
Executing command USE
Executing command WALK_TO
Executing command UNDRESS
Executing command SLEEP

Etvoilà! ;-)