Java枚举是否可以代理

枚举的本质

JVM编译器背地里是这样处理枚举的

  • 定义一个继承自Enum类的类,类是用final修饰的。
  • 为每个枚举实例对应创建一个类对象,这些类对象是用public static final修饰的。同时生成一个数组,用于保存全部的类对象。
  • 生成一个静态代码块,用于初始化类对象和类对象数组。
  • 生成一个构造函数,构造函数包含自定义参数和两个默认参数。
  • 生成一个静态的values()方法,用于返回所有的类对象。
  • 生成一个静态的valueOf()方法,根据name参数返回对应的类实例。

在Enum源代码中,几个值得关注的点

  1. Enum类有两个成员变量:name和ordinal。其中,name用于记录枚举常量的名字。ordinal用于记录枚举常量在声明时的顺序(从0开始)。
  2. Enum类有一个构造函数,它有两个入参,分别为name和ordianl赋值。
  3. Enum类重写了toString()方法,返回枚举常量的name值。
  4. Enum类重写了equals()方法,直接用等号比较。
  5. Enum类不允许克隆,clone()方法直接抛出异常。(保证枚举永远是单例的)
  6. Enum类实现了Comparable接口,直接比较枚举常量的ordinal的值。
  7. Enum类有一个静态的valueOf()方法,可以根据枚举类型以及name返回对应的枚举常量。
  8. Enum类不允许反序列化,为了保证枚举永远是单例的。

总结

  • 枚举不允许继承类。JVM在生成枚举时已经继承了Enum类,由于Java语言是单继承,不支持再继承额外的类(唯一的继承名额被JVM用了)。
  • 枚举允许实现接口。因为枚举本身就是一个类,类是可以实现多个接口的。
  • 枚举可以用等号比较。JVM会为每个枚举实例对应生成一个类对象,这个类对象是用public static final修饰的,在static代码块中初始化,是一个单例。
  • 不可以继承枚举。因为JVM在生成枚举类时,将它声明为final。
  • 枚举本身就是一种对单例设计模式友好的形式,它是实现单例模式的一种很好的方式。
  • 枚举类型的compareTo()方法比较的是枚举类对象的ordinal的值。
  • 枚举类型的equals()方法比较的是枚举类对象的内存地址,作用与等号等价。

如何对枚举实现代理

枚举肯定是不能用cglib的动态代理了,因为cglib是利用hancer获取代理对象的子类,然而枚举并没有办法被继承,因此此方法行不通。
但枚举可以实现接口,也可以使用JDK的动态代理。
例子:

public enum Month implements IMonthBase {
    JANUARY(1, "一月");

    private Integer value;
    private String name;

    Month(Integer value, String name) {
        this.value = value;
        this.name = name;
    }

    @Override
    public String now() {
        return "now";
    }

    public Integer getValue() {
        return value;
    }

    public String getName() {
        return name;
    }

    public String getValueByKey(Integer value) {
        if (value == null) {
            return "";
        }
        for (Month m: Month.values()) {
            if (m.getValue().equals(value)) {
                return m.getName();
            }
        }
        return "";
    }
}
public class MonthProxy implements InvocationHandler {
    private Object target;

    public Object getProxyTarget(Object obj) {
        this.target = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理前");
        Object invoke = method.invoke(target, args);
        System.out.println("代理后");
        return "pre-" + invoke;
    }

    public static void main(String[] args) {
        IMonthBase month = (IMonthBase) new MonthProxy().getProxyTarget(Month.JANUARY);
        System.out.println(month.now());
    }
}

能用注解的方式对枚举方法实现代理吗

在使用代理时,我们往往通过注解来定义一些信息,通过method.isAnnotationPresent(Prefix.class) || method.getDeclaringClass().isAnnotationPresent(Prefix.class)是无法获取注解信息的。但可以通过如下方式获取:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
    if (method.isAnnotationPresent(Prefix.class) || method.getDeclaringClass().isAnnotationPresent(Prefix.class)
        || target.getClass().isAnnotationPresent(Prefix.class) || targetMethod.isAnnotationPresent(Prefix.class)) {
        Prefix prefix = targetMethod.getAnnotation(Prefix.class);
        System.out.println(prefix.preName() + method.invoke(target, args));
    }
    return "";
}

所以枚举方法也能通过注解实现代理。

你可能感兴趣的:(Java枚举是否可以代理)