Class Loading

When the Jinx add-in starts, it reads the jinx.ini file described in the Jinx Configuration section of the user guide.

The config file contains a list of classes to load, and the class paths to use to look for those classes. The Jinx add-in loads all the listed classes and scans them for annotations to expose any methods to Excel.

For example:

[JINX]
classes =
    com.mycompany.myproject.MyClassA
    com.mycompany.myproject.MyClassB
    com.mycompany.myproject.MyClassC

[JAVA]
path =
    C:\MyProject\target\*.jar

This is suitable in many different situations, but for some cases simply having a static list of classes to load is not sufficient.

Jinx provides a few other ways to load classes to cover those situations.

Object Instantiation and Closing

When Jinx scans a class for annoations, under certain conditions it will instantiate an instance of that class.

Object instantiation occurs if:

  • A non-static method is annotated with any of the Jinx annotations (e.g. @ExcelFunction).
  • The class has a public constructor taking a single @ExcelAddIn argument.

Any objects created are kept alive until they are no longer needed. Typically this is either when the Jinx add-in is reloaded, or Excel is closed.

If your class implements the AutoClosable interface, close will be called when the object is no longer required when Jinx is reloading. You should not use this as a way to tell when Excel is closing, as although it is reliably closed when reloading, it may not be called when Excel is closing.

Listing Classes in a Resource

Larger projects may include multiple components, and keeping track of all the classes to be loaded from each component can become tedious.

Instead of listing all the classes in the jinx.ini file, each JAR can include a resource named jinx-classes.txt containing the classes to load. As long as the JAR is on the classpath, any classes listed in that resource will be loaded.

Comments can be included in the file by using # at the start of any line.

For example, you could build a JAR containing all the classes from the jinx.ini file in the previous section and add a jinx-classes.txt resource as follows.

# List of classes to load when Jinx starts
com.mycompany.myproject.MyClassA
com.mycompany.myproject.MyClassB
com.mycompany.myproject.MyClassC

Now as long as that JAR is on the classpath, that resource will be read and those classes will be loaded and scanned for Jinx annotations.

Loading Classes Dynamically

Classes can be loaded dynamically and scanned for annotations to add new Excel features, like UDFs or macros, at runtime without changing the config or reloading.

A typical use case for this is to load additional features when a user logs in to a system, depending on their permissions or settings in that system. This could be when they call a worksheet function (UDF), macro or a menu function, for example. It could also simply be when the Jinx add-in starts up.

Classes can be loaded using Class.forName or any other valid way of loading classed using reflection. Once loaded, they are scanned using ExcelAddIn.scan(java.lang.Class) or any of its overloads. Finally, the bindings between the JVM and Excel need to be updated using ExcelAddIn.rebind().

import com.exceljava.jinx.ExcelAddIn;
import com.exceljava.jinx.ExcelMenu;

public class DynamicClassLoading {
    private final ExcelAddIn xl;

    public DynamicClassLoading(ExcelAddIn xl) {
        this.xl = xl;
    }

    @ExcelMenu("Load Class")
    public void loadClass() throws ClassNotFoundException {
        // Find or load the class
        Class cls = Class.forName("com.mycompany.myproject.MyClassX");

        // Scan the class for Jinx annotations
        xl.scan(cls);

        // Rebuild the Java/Excel bindings
        xl.rebind();
    }
}

Using a Custom Class Loader

For complete control over how classes are loaded, you can use your own custom class loader.

Reasons for using your own class loader could include fetching class files from a remote server, or for isolating different sets of classes.

When using a custom class loader, you would typically have a single class that gets loaded automatically by Jinx (via the config) that is responsible for creating your class loader and loading any classes. If this class has a constructor taking an @ExcelAddIn then it will be constructed when Jinx starts, and if it implements AutoClosable it will be closed when Jinx reloads.

Jinx uses two related class loaders. The main one is used for loading the classes specified in the config, and is normally the one you will want to use as the parent to your class loader. However, when Jinx is reloaded, this class loader is closed and replaced with a new one. There is a second non-reloadable class loader. This one is not replaced when Jinx is reloaded, and is used for classes that are excluded from being reloaded.

You can get the parent class loader you will need for your own custom class loader using ExcelAddIn.getClassLoader().

Once you have constructed your class loader, you can use ExcelAddIn.scan(java.lang.Class, java.lang.ClassLoader) and its overloaded methods to scan your classes and finally rebind with ExcelAddIn.rebind().

The following is an incomplete example to illustrate how a custom class loader can be used.

import com.exceljava.jinx.ExcelAddIn;

public class BootstrapClass implements AutoCloseable {
    private final MyCustomClassLoader classLoader;
    private final ExcelAddIn xl;


    public BootstrapClass(ExcelAddIn xl) {
        this.xl = xl;

        // Create the custom class loader (not implemented)
        this.classLoader = createClassLoader();

        // Get all the classes to scan (not implemented)
        List<Class> classes = loadAllClasses();

        // Scan the classes
        for (Class cls: classes) {
            xl.scan(cls, classLoader);
        }

        // Could also look for a resource instead of loading the classes manually
        xl.scan(classLoader, "custom-jinx-classes.txt");

        // Rebuild the Java/Excel bindings
        xl.rebind();
    }

    public void close() {
        // Close any resources used by the custom class loader
        classLoader.close();
    }
}