Digital Developer Conference: Hybrid Cloud 2021. On Sep 21, gain free hybrid cloud skills from experts and partners. Register now

OSGi demystified, Part 3: Avoiding dynamic class loading

The OSGi demystified article series addresses common OSGi issues in CICS — offering insight into OSGi, discussing best practices, and providing setup and configuration advice. This third article addresses dynamic class loading, one of the most common causes of a ClassNotFoundException and often inflicted upon you by third-party applications. If you encounter this problem, chin up! All may not be lost.

What is dynamic class loading?

Dynamic class loading is the act of programmatically loading classes at runtime. This avoids having a dependency on a class at compile time, but why would you do that? Hiding a dependency from the compiler and running the risk of it not being found at runtime — that’s the craft of mad men, right? Well, yes and no.

There are distinct advantages to dynamic class loading. For example:

  • Allowing an application to choose which classes to load can help keep it lightweight and relevant.
  • Determining which classes to load can be based on configuration, or perhaps user input.

Let’s say your application needs to connect to a database, and your aim is to support many of the common databases. With JDBC, you can abstract away the specific database — but you’ll still need an appropriate JDBC driver. Typically, the user will configure your application for their specific database, and your application will need to load the relevant JDBC driver from where the user installed it. By dynamically loading the configured driver, you can avoid the overhead of loading all possible drivers and you’ll be able to load a driver that you haven’t supplied or installed with your software.

Another example of dynamic class loading is evident in many Java extension mechanisms: Allowing an extender to provide additional function (a plug-in) to your application provides greater flexibility and customization. A plug-in is typically configured by a text/XML file and then loaded dynamically. As the application developer, you won’t know or care what the extender’s classes are called — but using Class.forName(), the extender’s implementation can be located, loaded, and run.

As powerful as dynamic class loading is, alarm bells should have started ringing for you when I said that dependencies are hidden from the compiler. In a normal Java environment, as long as the class is somewhere on the application class path, you only run the risk of runtime failures if you omit the class from the class path. However, with OSGi and its thou-must-be-explicit approach, your application is much more sensitive to hidden dependencies. There are more boundaries across which you must declare those dependencies and more scope for mistakes, because the tooling cannot detect these dependencies.

Hidden dependencies in code lead straight to missing dependencies in configuration, which leads straight to ClassNotFoundExceptions!

Issues to anticipate

To visualize the problems caused by dynamic class loading in OSGi, let’s walk through some details. Each OSGi bundle has its own class loader. The bundle’s class path is essentially everything inside the bundle, plus anything on the Bundle-ClassPath — all of which is complimented by any package that is explicitly referenced by the Import-Package statement in the MANIFEST.MF. Now if the code in your bundle dynamically loads a class from another package and you don’t know in advance what that package will be (because you can’t predict what will go into the configuration file you are reading), then how do you configure your Import-Package statement to include it? The short answer is you can’t.

It can be equally confounding if you try to second-guess and declare all the possible packages in advance. By declaring those dependencies, the OSGi framework will insist that they be found and resolved before your bundle can be started. If they are not present — and by definition, they are dynamically configured and wouldn’t typically be present — your bundle doesn’t get to join the party. The choice of demise is yours.

Enterprise OSGi applications with dynamism: OSGi services and declarative services

There are many benefits to OSGi, including:

  • Efficient and quicker class loading
  • Componentization of the application
  • The ability to run multiple versions of a package/class within the same JVM
  • Updating a component without JVM restart
  • Missing dependencies are detected at install time, thus avoiding runtime failures

However, you may notice that dynamic class loading is not on its list of strengths.

The crux of the problem for OSGi is that when dependencies (classes/packages) are not known at application deployment time, and are instead loaded dynamically, the OSGi framework cannot know to wire between the relevant class loaders, and so ClassNotFoundExceptions occur.

The good news is that OSGi provides a far better alternative with its dynamic service model. Backed by the OSGi service repository, OSGi service implementations declared with this model can come and go — giving you the same benefits of dynamic class loading without the pain, and without playing class loader roulette. Not only will you reap the aforementioned benefits, but updates and improvements will be bound to your application dynamically. Indeed, the use of OSGi services is generally encouraged because it provides flexible and genuinely dynamic application updates. It is a common misconception of OSGi that bundle-wiring provides the dynamic update capability — it does not. The OSGi service model does.

If that is not enough to persuade you to use OSGi services, then let’s talk declarative services. Declarative services are a way to define your services and the components that use them, and to have those services automatically injected/removed as they become available. Declarative services remove the need to write service tracking code yourself. This is a very powerful, convenient way to architect your OSGi applications for the provision of dynamic updates. Ultimately OSGi services provide a far cleaner solution and offer a wealth of benefits beyond any class loader-based approach.

Here are some sites you can visit for further reading on this topic:

Workarounds for exploiting OSGi services effectively

Despite all the benefits, there are real-world reasons why you may not be able to exploit OSGi services. If you have third-party code outside of your control, or if re-structuring an existing app isn’t possible, then you won’t be able to make the necessary code changes. For those unfortunate enough to have to work with this sort of code, there are workarounds — but you should expect to be compromised. The following approaches are potential workarounds — in the absence of better application architecture.

Swap out third-party JARs for third-party bundles

On the face of it, this is a no-brainer. Many vendors and open-source communities offer OSGi-aware versions of their libraries. Sometimes it can be as simple as tracking down an OSGi-friendly bundled version of the library and swapping the old JAR for the equivalent bundle.

DynamicImport-Package

If you know that the dynamically loaded classes will belong to a specific package, then you can use DynamicImport-Package: com.mycompany.package in the MANIFEST.MF of the bundle that’s doing the class loading. This allows OSGi to look up the classes to load on the fly, but it also negates some of the benefits of using OSGi. If the problem is confined to a very small number of stable classes, then this may be a pragmatic solution to keep disruption minimal, but use it with care.

If you don’t know the package from which the classes will be dynamically loaded, you can use the more generic DynamicImport-Package: *". Doing so effectively turns your OSGi framework into one big classpath, negates all the benefits of OSGi, and causes potentially CPU-intensive operations every time the bundle attempts to load classes. This should be avoided whenever possible.

Put simply, DynamicImport-Package and buddy class loading are not a viable alternative.

Further reading:

Thread context class loader (TCCL)

Another potential solution is to set the thread context class loader (TCCL) to the bundle’s class loader. Many legacy and/or third-party libraries attempt to load application classes by name at runtime: For example, Hibernate reads class names from hbm.xml files and then creates instances of those classes for each database record. When you move such a library to a modular environment, it breaks because the name of a class is not sufficient to uniquely identify it. The identity of a class consists of its fully qualified name and the class loader that defined it, which in OSGi usually corresponds to the bundle that contains it. So in addition to the name, you need to know the class loader from which to load the class. Due to the wide variety of class loading environments created by various application servers, many libraries attempt to solve this problem with a set of heuristics. Consulting the TCCL is usually one of these heuristics, along with checking the library’s own class loader or the JRE extension class loader.

If you want to gamble and take the TCCL heuristic approach, you would do something like this:

  1. Save away the current TCCL.

  2. Set the current TCCL to be the class loader of the bundle.

  3. Call the third-party code that attempts to load the class.

    Don’t forget to set the TCCL back to the original on the way out. Here’s the pseudo-code:

     ClassLoader tccl = Thread.currentThread().getContextClassLoader();
     Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
     try
     {
         // execute third-party code
     }
     finally
     {
         Thread.currentThread().setContextClassLoader(tccl);
     }
    

Further reading:

Leverage the OSGi framework’s packageAdmin utilities

Rather than using Class.forName() and hoping that your current class loader has the right visibility to the target package/class, you can invoke the OSGi packageAdmin class by supplying the package name from which your class is exported. If there is a bundle in the system that exports the class you require from the intended package, this convenience call finds and loads the class on your behalf.

For example:

Class clazz = packageAdmin.getExportedPackage(packageName).getExportingBundle().loadClass(className);`

Centralize

If you really need class loaders, then centralize and isolate your code in a different package. Make sure your class-loading strategies are not visible in your application code. Module systems provide an API that allows you to correctly load classes from the appropriate module, so when you move to a module system you only have to port one piece of code and not change your whole code base.

Summary

This article has discussed dynamic class loading, the problems it causes, and how to achieve the same flexibility with OSGi services without the pain. I’ve also provided a set of real-world workarounds for convenience, but these are no substitute for well-designed applications.

For more on this topic, check out the links under Resources in the right-hand column. To explore other OSGi gotchas, explore the other articles in the OSGi demystified series.