初始化类之后的Java调用方法

时间:2015-02-27 20:35:59

标签: java reflection static initialization

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

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()方法,该方法被称为类的静态初始化的最后一步。或者任何其他方法来防止AssertionError发生。

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

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

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

3 个答案:

答案 0 :(得分:1)

如果你需要处理很多反思,我可以推荐reflections libary

您可以做到:

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

答案 1 :(得分:1)

这将是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中所描述的内容,但它也会遇到你提到的弱点(对于其他类加载应用程序(如OSGI,Reflections,AOP,Spring)会很脆弱。)

要安装此类加载器,您可以使用此类加载器加载Main类,并使用Reflection调用其上的main方法。您可以在网上找到其他更优雅的解决方案来设置类加载器。

答案 2 :(得分:0)

  

X需要知道运行时存在的每个子类型。

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

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]