Java call method after class is initialized

2019-09-20 17:39发布

问题:

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.

回答1:

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.



回答2:

If you have to deal with a lot of reflection stuff i can recommend reflections libary

With which you can do:

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


回答3:

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

This can be achieved with following code:

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

If you run main method, it will print something like:

[class Main$X, class Main$Y]