Difference between Loading a class using ClassLoad

2020-01-27 00:51发布

问题:

Below are 2 code snippets

The first one uses ClassLoader class to load a specified class

ClassLoader cls = ClassLoader.getSystemClassLoader(); Class someClass = cls.loadClass("TargetClass");

The second one uses Class.forName() to load a specified class

Class cls = Class.forName("TargetClass");

What is the difference between the above said approaches. Which one serves for which purpose?

回答1:

Quick answer (without code samples)

With the explicit ClassLoader cls = <a ClassLoader>; approach you have the flexibility of loading the class from a ClassLoader that is not your default ClassLoader. In your case you're using the default System ClassLoader, so it gives the similar overall result (with an instantiation of the final object difference) as the Class.forName(String name) call, but you could reference another ClassLoader instead.

That said, you can also use Class.forName(String name, boolean initialize, ClassLoader loader) as long as you know what that ClassLoader is.

For example, your EAR based application has its own ClassLoader with a version of an XML Parsing library wrapped inside of it. Your code normally uses those classes, but in one instance you need to grab a deserialisation class from an earlier version of the library (that the Application Server happens to be holding in its overall ClassLoader). So you could reference that Application Server ClassLoader instead.

Unfortunately until we get project Jigsaw (JDK 8) this is used more often than we'd like :-)



回答2:

The other answers are very complete in that they explore other overloads of Class.forName(...), and talk about the possibility to use different ClassLoaders.

However they fail to answer your direct question: "What is the difference between the above said approaches?", which deals with one specific overload of Class.forName(...). And they miss one very important difference. Class initialization.

Consider the following class:

public class A {
  static { System.out.println("time = " + System.currentTimeMillis()); }
}

Now consider the following two methods:

public class Main1 {
  public static void main(String... args) throws Throwable {
    final Class<?> c = Class.forName("A");
  }
}

public class Main2 {
  public static void main(String... args) throws Throwable {
    ClassLoader.getSystemClassLoader().loadClass("A");
  }
}

The first class, Main1, when run, will produce an output such as

time = 1313614183558

The other, however, will produce no output at all. That means that the class A, although loaded, has not been initialized (ie, it's <clinit> has not been called). Actually, you can even query the class's members through reflection before the initialization!

Why would you care?

There are classes that performs some kind of important initialization or registration upon initialization.

For example, JDBC specifies interfaces that are implemented by different providers. To use MySQL, you usually do Class.forName("com.mysql.jdbc.Driver");. That is, you load and initialize the class. I've never seen that code, but obviously the static constructor of that class must register the class (or something else) somewhere with JDBC.

If you did ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");, you would not be able to use JDBC, since the class, altough loaded, has not been initialized (and then JDBC wouldn't know which implementation to use, just as if you had not loaded the class).

So, this is the difference between the two methods you asked.



回答3:

In your concrete case:

ClassLoader cls = ClassLoader.getSystemClassLoader();
Class someClass = cls.loadClass("TargetClass");

Above code will load TargetClass ALWAYS with system classloader.

Class cls = Class.forName("TargetClass");

The second code snippet will load (and initialise) TargetClass with the classloader that was used to load the class that is executing that line of code. If that class was loaded with system classloader, the two approaches are identical (except for class initialisation, as explained in an excellent answer by Bruno).

Which one to use? For loading and inspecting classes with reflection, I recommend to use specific class loader (ClassLoader.loadClass()) - it puts you in control and helps to avoid potentially obscure issues between different environments.

If you need to load AND initialise, use Class.forName(String, true, ClassLoader).

How to find the right class loader? It depends on your environment:

  • if you are running a command-line application, you could just use system classloader or the class loader that loaded your application classes (Class.getClassLoader()).
  • if you are running inside a managed environment (JavaEE, servlet container, etc) then the best would be to check current thread context class loader first and then fall back to options given in previous point.
  • or just use your own custom class loader (if you are into that sort of thing)

In general, the most fool-proof and tested would be to use ClassUtils.forName() from Spring (see JavaDoc).

More in-depth explanation:


The most common form of Class.forName(), the one that takes a single String parameter, always uses the caller's classloader. This is the classloader that loads the code executing the forName() method. By comparison, ClassLoader.loadClass() is an instance method and requires you to select a particular classloader, which may or may not be the loader that loads that calling code. If picking a specific loader to load the class is important to your design, you should use ClassLoader.loadClass() or the three-parameter version of forName() added in Java 2 Platform, Standard Edition (J2SE): Class.forName(String, boolean, ClassLoader).

Source: What is the difference between Class.forName() and ClassLoader.loadClass()?


Also, SPR-2611 highlights one interesting obscure corner case when using Class.forName(String, boolean, ClassLoader).

As seen in that Spring issue, using ClassLoader.loadClass() is the recommended approach (when you need to load classes from specific class loader).



回答4:

ClassLoader.loadClass() uses the specified classloader (the system classloader in your case), whereas Class.forName() uses classloader of the current class.

Class.forName() can be used when you don't care about particular classloader and want the same classloading behaviour as for statically referenced classes.



回答5:

From the API doc:

Invoking this method is equivalent to:

  Class.forName(className, true, currentLoader)

where currentLoader denotes the defining class loader of the current class.

So the main difference is in which classloader will be used (it may or may not be the same as the system classloader). The overloaded method would also allow you to specify the classloader to use explicitly.



回答6:

ClassLoader.loadClass() always loads the system classloader, whereas Class.forName() loades any class. Lets see this example,

package com;
public class TimeA {
      public static void main (String args[]) {
            try {
                final Class c = Class.forName("com.A");
                ClassLoader.getSystemClassLoader().loadClass("com.A");
            }catch(ClassNotFoundException ex) {
                System.out.println(ex.toString());
            }
      }
}

class A {
      static {
          System.out.println("time = " + System.currentTimeMillis()); 
      }
}

When yoy run this program, you would get an Exception at ClassLoader.getSystemClassLoader().loadClass("com.A");

The Output may be:

time = 1388864219803
java.lang.ClassNotFoundException: com.A


回答7:

The 2nd approach loads a class using a ClassLoader

 public static Class<?> forName(String className) 
                throws ClassNotFoundException {
        return forName0(className, true, ClassLoader.getCallerClassLoader());

This is what the JavaDoc says:

forName(String name, boolean initialize, ClassLoader loader)

The specified class loader is used to load the class or interface. If the parameter loader is null, the class is loaded through the bootstrap class loader.

So, the 2nd option uses the System ClassLoader (which is, in essence, what it does in the first option).



回答8:

there's also a difference when loading array-types. I think classloader.loadClass(clazz) cannot handle array-types, but Class.forName(clazz,true,classloader) can.



回答9:

But the static initilizer block is only executed when we use class.forname("...");

I have just tested.