类初始化后的Java调用方法 [英] Java call method after class is initialized

查看:255
本文介绍了类初始化后的Java调用方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个X类,该类可以通过各种类进行扩展. X需要在运行时知道它存在的每个子类型.所以我的想法是在X中创建一个静态方法"static init()",该方法将Class作为参数.问题是X的子类需要在调用init()之前通过其static-init-blocks进行初始化.例如,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的了解还不多,关于这个主题,网上也没有太多的教程或帮助.那么任何有使用自定义ClassLoader经验的人都可以告诉我是否可行?如果我的类路径中的某些其他库也随时安装了自定义的ClassLoader,会发生什么情况?

Edit3:如果有其他选择,我不想做的就是直接修改字节码.

解决方案

这将是您的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方法.您可以找到其他更优雅的解决方案来在网络上设置类加载器.

I have a class X which is extended by various classes. X needs to know each subtype that exists of it at runtime. So my idea was to create a static method "static init()" in X that takes a Class as parameter. The problem is that a subclass of X is required to be initialized via its static-init-blocks before the call to init() happens. X for example may discover some of the static fields of the subclass which may be of any declared type. So take this code for example:

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);
    }
}

So what I am looking for is a way to somehow get my init()-method called as the last step in the static initialization of a class. Or any other way to prevent the AssertionError from happening.

Edit: Because this lead to misunderstanding in the comments, I actually want to do the second thing. I want my method to be called within the static initialization of any class that subclasses my class (either directly or indirectly). I don't know which subclasses exist, I don't know anything about their structure and all assertions must be checked at runtime (via reflection). My class and the subclasses may not even be compiled by the same compiler on the same machine and/or by the same people.

Edit2: Maybe I can build a ClassLoader that is a simple proxy to the System-ClassLoader and replace the System-ClassLoader with it. If the loaded class is then a subclass of my class I can initialize it and call my method. Unfortunately I don't know much about ClassLoaders (yet) and there are not much tutorials or help on the net regarding this topic. So can anyone having experience with custom ClassLoaders tell me if this is possible? What happens if some other library in my classpath does also install a custom ClassLoader at any point in time?

Edit3: What I don't want to do - if there is any other option - is direct byte-code modifications.

解决方案

This would be an example for your 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);
    }
}
}

I think it does what you describe in Edit2, but it also suffers the weaknesses you mention (will be fragile with other class loading applications such as OSGI, Reflections, AOP, Spring).

To install this class loader you could load the Main class with this class loader and call the main Method on it with Reflection. You can find other more elegant solutions for setting a classloader on the web.

这篇关于类初始化后的Java调用方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆