【问题标题】:Java call method after class is initialized类初始化后的Java调用方法
【发布时间】:2015-02-27 20:35:58
【问题描述】:

我有一个由各种类扩展的类 X。 X 需要知道它在运行时存在的每个子类型。所以我的想法是在 X 中创建一个以 Class 作为参数的静态方法“static init()”。问题在于,在调用 init() 之前,需要通过其静态初始化块初始化 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 发生的方法。

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

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

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

【问题讨论】:

  • 你想在这里实现什么?为什么超类应该知道它的子类?
  • 第一个解决方案“init(getClass());”应该可以正常工作,如果您不在 Y 的静态初始化程序中调用 X 的构造函数(a 的类型不应该是 X 的子类型)。解决方案必须捕获“静态 X a = new X();”的情况
  • 您的意图是每个子类都应该履行特定的合同,我对吗?在这种情况下,它应该包含一个不为空的字段。
  • 是的,斯特凡·曼德尔,你是对的。然而,子类必须满足的规则非常复杂。它们涉及扩展类的结构、各种注释以及静态和非静态字段。是的,我专门构建了这个案例来展示一个有意义的案例,其中没有创建子类的实例。这就是为什么第一个解决方案不起作用的原因。

标签: java reflection static initialization


【解决方案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 类,并使用反射调用 main 方法。您可以找到其他更优雅的解决方案来在 Web 上设置类加载器。

【讨论】:

    【解决方案2】:

    如果你必须处理很多反射问题,我可以推荐reflections libary

    你可以这样做:

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

    【讨论】:

    • 谢谢,但我不太喜欢 Google,也不信任他们。所以我更喜欢没有一些令人毛骨悚然的 lib 的纯 Java 解决方案。
    • 据我了解,这只适用于一个包。那么如果我的 code-sn-p 是公共图书馆的一部分呢?没有机会知道子类在哪个包中。
    • Reflections 不是谷歌。你可以将它用于完整的类路径(但是让它工作有点棘手)。 guava Classpath 提供了类似的功能(但这是 google),它不限于包。您当然可以使用asm (only german) 以自己的方式进行操作。
    • 您可以在 cmets 中阅读下面的帖子,基本上,如果您有大量的课程,则相反的论点是性能。
    • 很确定这是谷歌,但我错了,它的开源而不是谷歌,我经常使用它,对我来说很好。其他人已经列出了一些纯 Java 解决方案
    【解决方案3】:

    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]
    

    【讨论】:

    • 正如我已经说过的,我不能保证 Y 的任何实例都会被创建。我还希望在静态初始化完成后调用我的代码。在施工时,情况并非如此。
    • 我有点误解了你的问题。尝试查看整个类路径并测试从基类扩展的每个类怎么样?这基本上是@Fish013 所建议的。您还应该阅读similar 问题。
    • 嗯...在我的类路径中有大约 100k 个类,这可能需要一段时间。还有另一个问题:我什么时候应该这样做?每毫秒?对于我选择的任何间隔,我要么最终表现不佳,要么我不能保证在执行我的代码之前没有使用任何对象和类型为 Y 的静态字段。第二件事很关键,因为如果我不能保证,这可能会产生巨大而不受控制的影响。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 2016-08-13
    • 2011-04-01
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多