Java 在 class 初始化后调用方法

Java call method after class is initialized

我有一个 class X,它由各种 classes 扩展。 X 需要知道它在运行时存在的每个子类型。所以我的想法是在 X 中创建一个以 Class 作为参数的静态方法 "static init()"。问题是 X 的 subclass 需要在调用 init() 之前通过其静态初始化块进行初始化。例如,X 可能会发现 subclass 的某些静态字段,这些字段可能是任何已声明的类型。所以以这段代码为例:

class X {
    X() {
        /*This would be one idea but does not work
          in this concrete example.*/
        init(getClass());
    }
    static void init(Class<? extends X> c) {
         if(c.getDeclaredField("a").get(null) == null) {
             throw new AssertionError();
         }
    }
}

class Y extends X {
    static X a = new X();
    static {
        /*Another idea. Works totally fine but
          I dont want to trust subclasses that
          they do that correctly.*/
        init(Y.class);
    }
}

所以我正在寻找一种方法,以某种方式让我的 init() 方法被称为 class 静态初始化的最后一步。或任何其他防止 AssertionError 发生的方法。

编辑:因为这导致评论中的误解,我其实想做第二件事。我希望在 subclass 是我的 class 的任何 class 的静态初始化中调用我的方法(直接或间接)。我不知道存在哪些 subclasses,我对它们的结构一无所知,所有断言都必须在运行时检查(通过反射)。我的 class 和子 class 甚至可能不是同一个人在同一台机器上由同一编译器编译的 and/or。

Edit2:也许我可以构建一个 ClassLoader,它是 System-ClassLoader 的简单代理,并用它替换 System-ClassLoader。如果加载的 class 是我的 class 的子 class,我可以初始化它并调用我的方法。不幸的是,我对 ClassLoaders 知之甚少(目前)并且网上没有太多关于此主题的教程或帮助。那么任何有自定义 ClassLoaders 经验的人都可以告诉我这是否可能?如果我的 class 路径中的某些其他库在任何时间点也安装了自定义 ClassLoader 会怎样?

Edit3:我不想做的 - 如果有任何其他选择 - 是直接修改字节码。

X needs to know each subtype that exists of it at runtime.

这可以通过以下代码实现:

import java.util.HashSet;
import java.util.Set;

public class Main {

    public static class X {
        private static Set<Class<? extends X>> classes = new HashSet<Class<? extends X>>();

        public X() {
            classes.add(getClass());
            // and here you can check for fields of subclasses
        }
    }

    public static class Y extends X {}

    public static void main(String[] args) {
        new Y(); new X();
        System.out.println(X.classes);
    }
}

如果你 运行 main 方法,它会打印如下内容:

[class Main$X, class Main$Y]

如果你必须处理很多反射的东西我可以推荐reflections libary

你可以用来做什么:

    Reflections r = new Reflections("package.name");
    Set<Class<? extends X>> childs = r.getSubTypesOf(X.class);

这将是您的 Edit2 的示例:

public class ConstraintClassLoader extends URLClassLoader {

public ConstraintClassLoader(URL[] urls, ClassLoader parent) {
    super(urls, parent);
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                c = findClass(name);
            } catch (ClassNotFoundException e) {
                c = super.loadClass(name, resolve);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    try {
        System.out.println("find " + name);
        Class<?> c = super.findClass(name);
        Class<?> parent = c.getSuperclass();
        while (parent != null)  {
            if (parent.getName().contains("X")) {
                break;
            }
            parent = parent.getSuperclass();
        }
        if (parent == null) {
            return c;
        }
        Field declaredField = c.getDeclaredField("a");
        declaredField.setAccessible(true);
        if (declaredField.get(null) == null) {
            throw new AssertionError();
        }
        return c;
    } catch (NullPointerException | IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException | AssertionError e) {
        throw new RuntimeException(e);
    }
}
}

我认为它可以实现您在 Edit2 中描述的功能,但它也存在您提到的弱点(与其他 class 加载应用程序(如 OSGI、Reflections、AOP、Spring 一起使用时会很脆弱) .

要安装此 class 加载程序,您可以使用此 class 加载程序加载 Main class 并使用反射调用其上的主要方法。您可以在网络上找到其他更优雅的设置 classloader 的解决方案。