Categories
Java RS Library

The Class.forName() problem

In earlier Java days, it was easy to get a Class object when you had its name only. You simply called:

1
2
3
4
5
   // Without parametrized
   Class c = Class.forName("java.lang.String");
 
   // With parameters
   Class<String> = (Class<String>)Class.forName("java.lang.String");

This worked as long as there have been only one class loader. Long time ago :). The world kept spinning and concepts of separated applications in a single Java engine appeared. And so did OSGI. OSGI specified its container in a way that you can have a class being loaded in multiple versions at the same time. Furthermore, an OSGI bundle (effectively a JAR) can describe what other bundles can see their classes and what not. OSGI containers implement these behaviour through different ClassLoader instances responsible for the respective bundles.

However, this introduces a problem on Class.forName(String) calls. The method uses the ClassLoader of the “caller”. That means that it will be restricted to the bundle’s definition. As long as you use this method within bundle A to find a class in bundle A, everything works fine. It will even find a class in bundle B when B exported correctly and A defined B as a pre-requisite.

However, modular code encapsulates common functions. That’s why it can happen that a Class.forName(String) call is implemented in bundle C and doesn’t know about bundle A at all. When bundle A now calls bundle C which in turn calls Class.forName(String) to load a class from bundle A, then it fails. The reason is that ClassLoader C is not allowed to access classes in bundle A.

Several solutions are possible. The OSGI solution for this problem is the friend. You declare bundle C as friend of bundle A and class loading will succeed. However, this works only as long as you don’t try to load a class from bundle B. Now B would be required to declare C as a friend. If B is a 3rd party bundle then this solution is out of reach.

Another solution is the iteration on all OSGI bundles and trying to find the class via their class loaders. However, this is error-prone. Unless you don’t know exactly what bundle contains your required class you might get in trouble. Several versions of the same class can be present (or just define a class with the same name). You would need some heuristics to identify the correct class.

There is a better and most-likely the best solution. Each thread in Java can have a Context Class Loader. This will allow us to resolve all the dependency problems in OSGI. We will define the method in bundle C as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
	/**
	 * Load a class by first checking the thread's class loader and then the caller's class loader.
	 * @param className name of class to be loaded
	 * @return the class found
	 * @throws ClassNotFoundException when the class cannot be found
	 */
	public static Class<?> forName(String className) throws ClassNotFoundException {
		return forName(className, null);
	}
 
	/**
	 * Load a class by directly specifying a class loader.
	 * @param className name of class to be loaded
	 * @param classLoader the class loader to be used - if null the thread's class loader will be  used first
	 * @return the class found
	 * @throws ClassNotFoundException when the class cannot be found
	 */
	public static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {
		if (classLoader == null) try {
			// Check the thread's class loader
			classLoader = Thread.currentThread().getContextClassLoader();
			if (classLoader != null) {
				return Class.forName(className, true, classLoader);
			}
		} catch (ClassNotFoundException e) {
			// not found, use the class' loader
			classLoader = null;
		}
		if (classLoader != null) {
			return Class.forName(className, true, classLoader);
		}
		return Class.forName(className);
	}

The method will (if no specific class loader was given) first test the Context Class Loader (lines 21-24). Only if this fails or is (for whatever reasons) not available, then the original Class.forName(String) method will be asked (line 30).

As the Context Class Loader is defined in the beginning of the executing thread, it is usually the class loader of Bundle A which has access to all other dependent bundles. Therefore we will be able to load classes from bundle A and B with code written in bundle C.

1
2
3
4
5
6
7
8
   // Code in bundle C
   public Class<?> getClass(String name) {
      return BundleC.forName(name);
   }
 
   // Code in bundle A
   Class<?> c1 = BundleC.forName("bundleA.className");
   Class<?> c2 = BundleC.forName("bundleB.className");

I added the helper methods in my RS Base Classes Library class LangUtils where you can use it directly from (V1.2.2 – available very soon) in your OSGI projects.