2

Asume the following code:

public class Main {

    public static final List<Object> configuration = new ArrayList<>();

    public static void main(String[] args) {
        System.out.println(configuration);
    }
}

I now want to be able, to provide "self-configuring" classes. This means, they should be able to simply provide something like a static block, that will get called automatically like this:

public class Custom {
    static {
        Main.configuration.add(Custom.class);
    }
}

If you execute this code, the configuration list is empty (because of the way static blocks are executed). The class is "reachable", but not "loaded". You could add the following to the Main class before the System.out

Class.forName("Custom");

and the list would now contain the Custom class object (since the class is not initialized yet, this call initializes it). But because the control should be inverse (Custom should know Main and not the other way around), this is not a usable approach. Custom should never be called directly from Main or any class, that is associated with Main.

What would be possible though is the following: You could add an Annotation to the class and collect all classes with said annotation, using something like the ClassGraph framework and call Class.forName on each of them.

TL;DR

Is there a way, to automatically call the static block without the need to analyze all classes and the need of knowing the concrete, "self configuring" class? Perfect would be an approach, that, upon starting the application, automatically initializes a classes (if they are annotated with a certain annotation). I thought about custom ClassLoaders, but from what i understand, they are lazy and therefor not usable for this approach.

The background of this is, that i want to incorporate it into an annotation processor, which creates "self configuring code".

Example (warning: design-talk and in depth)

To make this a little less abstract, imagine the following:

You develop a Framework. Let's call it Foo. Foo has the classes GlobalRepository and Repository. GlobalRepository follows the Singleton design pattern (only static methods). The Repository as well as the GlobalRepository have a method "void add(Object)" and " T get(Class)". If you call get on the Repository and the Class cannot be found, it calls GlobalRepository.get(Class).

For convenience, you want to provide an Annotation called @Add. This Annotation can be placed on Type-Declarations (aka Classes). An annotation-processor creates some configurations, which automatically add all annotated classes to the GlobalRepository and therefor reduce boilerplate code. It should only (in all cases) happen once. Therefor the generated code has a static initializer, in which the GlobalRepository is filled, just like you would do with the local repository. Because your Configurations have names that are designed to be as unique as possible and for some reason even contain the date of creation (this is a bit arbitrary, but stay with me), they are nearly impossible to guess.

So, you also add an annotation to those Configurations, which is called @AutoLoad. You require the using developer to call GlobalRepository.load(), after which all classes are analyzed and all classes with this annotation are initialized, and therefor their respective static-blocks are called.

This is a not very scalable approach. The bigger the application, the bigger the realm to search, the longer the time and so on. A better approach would be, that upon starting the application, all classes are automatically initialized. Like through a ClassLoader. Something like this is what i am looking for.

Thorben Kuck
  • 1,092
  • 12
  • 25
  • What kind of logic do you want to put in your static blocks ? If you want to initialize mandatory services, it is often much more clearer to manually call their creations - as you can control the order. Otherwise, for optional systems, it is often good to wait and see if they are used. – Kineolyan Oct 14 '18 at 12:07
  • "`Custom` should know `Main` and not the other way around": `Main` also knows `Custom` if it stores a reference to it, doesn't it? – Andrew Tobilko Oct 14 '18 at 12:09
  • 1
    Imagine the following: You have a class, that you annotate with an annotation. An Annotation Processor creates custom classes, which should hook themself up to existing parts of the application. Since those classes are generated at compile time and always different, the existing part (Main) provides a Framework for those classes, but should not know these (since it is nearly impossible to do so). Is this clearer? I am bad at explaining.. sorry – Thorben Kuck Oct 14 '18 at 12:13
  • @ThorbenKuck where are you going to start that "Annotation Processor"? will it be in the `main` before `System.out.println(configuration);`? – Andrew Tobilko Oct 14 '18 at 12:19
  • @AndrewTobilko Once you compile the Application; Just like any other Annotation Processor – Thorben Kuck Oct 14 '18 at 12:57
  • @AndrewTobilko i edited the post and added an example. Maybe this is a bit more clear now – Thorben Kuck Oct 14 '18 at 14:29
  • Holger's answer is the "correct" pure-Java way to do this. As you pointed out though, using ClassGraph to detect classes with a given annotation (or implementing some initialization function) is an alternative way to accomplish the same thing at runtime. (I am the author of ClassGraph.) For an example of this, I used this in the `serverx` web framework ( https://github.com/lukehutch/serverx ) to detect and pre-render HTML template classes, and to detect and pre-register HTTP route handlers: https://github.com/lukehutch/serverx/blob/master/src/main/java/serverx/route/RouteInfo.java#L543 – Luke Hutchison Aug 22 '19 at 08:55

1 Answers1

4

First, don’t hold Class objects in your registry. These Class objects would require you to use Reflection to get the actual operation, like instantiating them or invoking certain methods, whose signature you need to know before-hand anyway.

The standard approach is to use an interface to describe the operations which the dynamic components ought to support. Then, have a registry of implementation instances. These still allow to defer expensive operations, if you separate them into the operational interface and a factory interface.

E.g. a CharsetProvider is not the actual Charset implementation, but provides access to them on demand. So the existing registry of providers does not consume much memory as long as only common charsets are used.

Once you have defined such a service interface, you may use the standard service discovery mechanism. In case of jar files or directories containing class files, you create a subdirectory META-INF/services/ containing a file name as the qualified name of the interface containing qualified names of implementation classes. Each class path entry may have such a resource.

In case of Java modules, you can declare such an implementation even more robust, using

provides service.interface.name with actual.implementation.class;

statements in your module declaration.

Then, the main class may lookup the implementations, only knowing the interface, as

List<MyService> registered = new ArrayList<>();
for(Iterator<MyService> i = ServiceLoader.load(MyService.class); i.hasNext();) {
    registered.add(i.next());
}

or, starting with Java 9

List<MyService> registered = ServiceLoader.load(MyService.class)
    .stream().collect(Collectors.toList());

The class documentation of ServiceLoader contains a lot more details about this architecture. When you go through the package list of the standard API looking for packages have a name ending with .spi, you get an idea, how often this mechanism is already used within the JDK itself. The interfaces are not required to be in packages with such names though, e.g. implementations of java.sql.Driver are also searched through this mechanism.

Starting with Java 9, you could even use this to do something like “finding the Class objects for all classes having a certain annotation”, e.g.

List<Class<?>> configuration = ServiceLoader.load(MyService.class)
    .stream()
    .map(ServiceLoader.Provider::type)
    .filter(c -> c.isAnnotationPresent(MyAnnotation.class))
    .collect(Collectors.toList());

but since this still requires the classes to implement a service interface and being declared as implementations of the interface, it’s preferable to use the methods declared by the interface for interacting with the modules.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • This sounds like a great solution. But wouldn't this be a hen egg problem? If those instances of interfaces would be created on compile time, we would need to modify the module-info to contain the new instance. We would need to modify the module-info.java file which appears to not be possible using annotation processors, so we would need a class that modifies it (hence the hen egg problem). Java <= 8 would be even harder, wouldn't it? – Thorben Kuck Oct 18 '18 at 14:38
  • Instances are never created at compile-time. What you declare, is the existence of an implementation class for a service, which you can do right when creating that implementation class. It only has to be done once. Since the module containing that implementation class doesn’t need to be the same as the module containing the `Main` class, they are still decoupled. The `ServiceLoader` takes care of collecting all implementations available at runtime and creating instances of them. – Holger Oct 18 '18 at 14:48
  • My wording was bad there, english is my second language, sorry. by _created at compile time_ i meant that the actual class-definition (aka the .java file) is created at compile time. Aka the class doesn't exit before the annotation processor is run. If i only let the annotation processor run and try to call ServiceLoader.load(MyService.class), nothing is returned. – Thorben Kuck Oct 18 '18 at 15:07
  • Which class does not exist before the annotation processor runs? – Holger Oct 18 '18 at 15:10
  • The instance of the MyService interface. If i am thinking correctly we would use **provides MyService with MyConcreteService1, ...** to declare the instance(s) to use for the ServiceLoader (in java > 9). Since MyConcreteService1 is defined at compile time, we would need to create this entry at compile time. – Thorben Kuck Oct 18 '18 at 15:15
  • You are again using wrong words; an *instance* is an object at runtime. You are talking about an *implementation class*. This implementation class `MyConcreteService1` exists at compile-time and so does the entry in the module declaration. If you are trying to say that the entire implementation class `MyConcreteService1` is generated by an annotation processor, I would like to know which caused its creation, i.e. what has the annotation. – Holger Oct 18 '18 at 15:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/182099/discussion-between-thorben-kuck-and-holger). – Thorben Kuck Oct 18 '18 at 15:22
  • After working a bit, i got it working for java <= 8. Thanks a lot! I missed the ServiceLoader. This is quite an elegant solution! – Thorben Kuck Oct 18 '18 at 16:19