Find a way out of the ClassLoader maze
System, current, context? Which ClassLoader should you use?
<!--<BLOCKQUOTE><STRONG>Summary</STRONG><BR>--><!-- REPLACE SUMMARY --><!--</BLOCKQUOTE>-->
<!-- REPLACE AUTHOR -->
Printer-friendly version | Mail this to a friend
Page 2 of 2
<!-- START BIG AD (336x280) jw-articles-336x280.txt -->
<!-- END BIG AD (336x280) -->
What is a Java programmer to do?
If your implementation is confined to a certain framework with articulated resource loading rules, stick to them. Hopefully, the burden of making them work will be on whoever has to implement the framework (such as an application server vendor, although they don't always get it right either). For example, always use Class.getResource()
in a Web application or an Enterprise JavaBean.
In other situations, you might consider using a solution I have found useful in personal work. The following class serves as a global decision point for acquiring the best classloader to use at any given time in the application (all classes shown in this article are available with the download):
public abstract class ClassLoaderResolver
{
/**
* This method selects the best classloader instance to be used for
* class/resource loading by whoever calls this method. The decision
* typically involves choosing between the caller's current, thread context,
* system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}
* instance established by the last call to {@link #setStrategy}.
*
* @return classloader to be used by the caller ['null' indicates the
* primordial loader]
*/
public static synchronized ClassLoader getClassLoader ()
{
final Class caller = getCallerClass (0);
final ClassLoadContext ctx = new ClassLoadContext (caller);
return s_strategy.getClassLoader (ctx);
}
public static synchronized IClassLoadStrategy getStrategy ()
{
return s_strategy;
}
public static synchronized IClassLoadStrategy setStrategy (final IClassLoadStrategy strategy)
{
final IClassLoadStrategy old = s_strategy;
s_strategy = strategy;
return old;
}
/**
* A helper class to get the call context. It subclasses SecurityManager
* to make getClassContext() accessible. An instance of CallerResolver
* only needs to be created, not installed as an actual security
* manager.
*/
private static final class CallerResolver extends SecurityManager
{
protected Class [] getClassContext ()
{
return super.getClassContext ();
}
} // End of nested class
/*
* Indexes into the current method call context with a given
* offset.
*/
private static Class getCallerClass (final int callerOffset)
{
return CALLER_RESOLVER.getClassContext () [CALL_CONTEXT_OFFSET +
callerOffset];
}
private static IClassLoadStrategy s_strategy; // initialized in <clinit>
private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if this class is redesigned
private static final CallerResolver CALLER_RESOLVER; // set in <clinit>
static
{
try
{
// This can fail if the current SecurityManager does not allow
// RuntimePermission ("createSecurityManager"):
CALLER_RESOLVER = new CallerResolver ();
}
catch (SecurityException se)
{
throw new RuntimeException ("ClassLoaderResolver: could not create CallerResolver: " + se);
}
s_strategy = new DefaultClassLoadStrategy ();
}
} // End of class.
You acquire a classloader reference by calling the ClassLoaderResolver.getClassLoader()
static method and use the result to load classes and resources via the normal java.lang.ClassLoader
API. Alternatively, you can use this ResourceLoader
API as a drop-in replacement for java.lang.ClassLoader
:
public abstract class ResourceLoader
{
/**
* @see java.lang.ClassLoader#loadClass(java.lang.String)
*/
public static Class loadClass (final String name)
throws ClassNotFoundException
{
final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
return Class.forName (name, false, loader);
}
/**
* @see java.lang.ClassLoader#getResource(java.lang.String)
*/
public static URL getResource (final String name)
{
final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
if (loader != null)
return loader.getResource (name);
else
return ClassLoader.getSystemResource (name);
}
... more methods ...
} // End of class
The decision of what constitutes the best classloader to use is factored out into a pluggable component implementing the IClassLoadStrategy
interface:
public interface IClassLoadStrategy
{
ClassLoader getClassLoader (ClassLoadContext ctx);
} // End of interface
To help IClassLoadStrategy
make its decision, it is given a ClassLoadContext
object:
public class ClassLoadContext
{
public final Class getCallerClass ()
{
return m_caller;
}
ClassLoadContext (final Class caller)
{
m_caller = caller;
}
private final Class m_caller;
} // End of class
ClassLoadContext.getCallerClass()
returns the class whose code calls into ClassLoaderResolver
or ResourceLoader
. This is so that the strategy implementation can figure out the caller's classloader (the context loader is always available as Thread.currentThread().getContextClassLoader()
). Note that the caller is determined statically; thus, my API does not require existing business methods to be augmented with extra Class
parameters and is suitable for static methods and initializers as well. You can augment this context object with other attributes that make sense in your deployment situation.
All of this should look like a familiar Strategy design pattern to you. The idea is that decisions like "always context loader" or "always current loader" get separated from the rest of your implementation logic. It is hard to know ahead of time which strategy will be the right one, and with this design, you can always change the decision later.
I have a default strategy implementation that should work correctly in 95 percent of real-life situations:
public class DefaultClassLoadStrategy implements IClassLoadStrategy
{
public ClassLoader getClassLoader (final ClassLoadContext ctx)
{
final ClassLoader callerLoader = ctx.getCallerClass ().getClassLoader ();
final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();
ClassLoader result;
// If 'callerLoader' and 'contextLoader' are in a parent-child
// relationship, always choose the child:
if (isChild (contextLoader, callerLoader))
result = callerLoader;
else if (isChild (callerLoader, contextLoader))
result = contextLoader;
else
{
// This else branch could be merged into the previous one,
// but I show it here to emphasize the ambiguous case:
result = contextLoader;
}
final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();
// Precaution for when deployed as a bootstrap or extension class:
if (isChild (result, systemLoader))
result = systemLoader;
return result;
}
... more methods ...
} // End of class
The logic above should be easy to follow. If the caller's current and context classloaders are in a parent-child relationship, I always choose the child. The set of resources visible to a child loader is normally a superset of classes visible to its parent, so this feels like the right decision as long as everybody plays by J2SE delegation rules.
It is when the current and the context classloaders are siblings that the right decision is impossible. Ideally, no Java runtime should ever create this ambiguity. When it happens, my code chooses the context loader: a decision based on personal experience of when things work correctly most of the time. Feel free to change that code branch to suit your taste. It is possible that the context loader is a better choice for framework components, and the current loader is better for business logic.
Finally, a simple check ensures that the selected classloader is not a parent of the system classloader. This is a good thing to do if you are developing code that might be deployed as an extension library.
Note that I intentionally do not look at the name of resources or classes that will be loaded. If nothing else, the experience with Java XML APIs becoming part of the J2SE core should have taught you that filtering by class names is a bad idea. Nor do I trial load classes to see which classloader succeeds first. Examining classloader parent-child relationships is a fundamentally better and more predictable approach.
Although Java resource loading remains an esoteric topic, J2SE relies on various load strategies more and more with every major platform upgrade. Java will be in serious trouble if this area is not given some significantly better design considerations. Whether you agree or not, I would appreciate your feedback and any interesting pointers from your personal design experience.
<!-- PAGECOUNT 2 -->
Page 1
Find a way out of the ClassLoader maze Page 2 What is a Java programmer to do?
<!-- REPLACE TALKBACK -->
Printer-friendly version | Mail this to a friend
About the author
Vladimir Roubtsov has programmed in a variety of languages for more than 13 years, including Java since 1995. Currently, he develops enterprise software as a senior engineer for Trilogy in Austin, Texas.
分享到:
相关推荐
- **加载类**:编译完成后,使用自定义ClassLoader的findClass方法加载编译后的类。 **3. 示例代码框架** ```java public class AutoCompileClassLoader extends ClassLoader { // 定义加载类的方法 @Override ...
4. 考虑Java 2版本的兼容性:学习如何修改你的ClassLoader以适应Java 2及以上版本的特性,比如支持Service Provider Interface (SPI)。 在深入学习之前,你需要理解类的生命周期,以及类加载、链接和初始化的概念。...
创建自定义Classloader需要继承java.lang.ClassLoader类,并重写其关键方法,如`findClass(String name)`或`loadClass(String name)`。这两个方法分别用于查找指定类的字节码和实际加载类。在`findClass`中,我们...
2. **`findClass`**: 当`loadClass`方法无法加载类时,会调用此方法来查找并处理类。 3. **`findLoadedClass`**: 查找已经加载过的类,以避免重复加载。 4. **`defineClass`**: 将字节流转换为类的实例,完成类的...
- 自定义ClassLoader通常需要重写`loadClass()`方法,该方法在找不到类时调用`findClass()`进行实际的加载操作。 - 在`ClassLoaderDemo`这个例子中,可能就展示了如何创建一个自定义的ClassLoader,从非标准位置...
1. 如果WebApp ClassLoader的缓存中没有类A,则会查找System ClassPath,未找到A。 2. 接下来查找Application Class Path,如果在其中找到了A(如在wsdl4j.jar中),则加载该类。 3. 如果Application Class Path也...
2. 对`loadClass()` 和 `findClass()` 的覆写,实现自定义加载逻辑。 3. 文件I/O操作,读取并加载指定目录下的.class文件。 4. 使用`Class.forName()` 或 `Class.getDeclaredMethods()`等反射API来动态调用加载的类...
2. 类的可见性:不同的ClassLoader加载的类相互之间默认是不可见的,除非使用` ProtectionDomain`进行设置。 3. 性能:频繁的类加载会影响性能,因此应尽量减少不必要的类加载操作。 在实际应用中,我们可以通过...
本文档提到的“编译时类加载器”(The Compiling ClassLoader)实际上是指在加载类的同时执行编译操作的类加载器。这种类型的类加载器可以实现在运行时自动编译源代码,并将生成的字节码加载到JVM中。这种能力对于...
自定义ClassLoader通常需要重写findClass()或loadClass()方法,以控制类的加载行为。 理解ClassLoader的工作原理对于排查类冲突、处理依赖关系以及优化大型J2EE应用的性能具有重要意义。开发者可以通过日志输出、...
自定义ClassLoader需要继承`java.lang.ClassLoader`类,并重写`findClass()`或`loadClass()`方法。通过这两个方法,你可以控制类的加载来源和方式。 在实际开发中,理解ClassLoader机制可以帮助解决一些问题,例如...
《深入理解ClassLoader工作机制》 Java虚拟机(JVM)中的ClassLoader是负责加载类到内存中的核心组件。它不仅承担着将字节码转换为可执行对象的重任,还参与了类生命周期的各个阶段,包括加载、验证、准备、解析、...
java虚拟机的运行机理的详细介绍 Inside the Java Virtual Machine Bill Venners $39.95 0-07-913248-0 Inside the Java Virtual ... Slices of Pi: A Simulation of the Java Virtual Machine Index About the Author
2. OSGi的ClassLoader支持动态加载和卸载bundle,当bundle被激活或停用时,对应的类加载器可以按需加载或释放类,提高了系统的灵活性和可维护性。 3. OSGi的ClassLoader还支持类的重用,如果两个bundle引用了相同的...
首先,ClassLoader可以分为三种基本类型:Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader。Bootstrap ClassLoader是JVM启动时的第一个ClassLoader,负责加载JDK的`<JAVA_HOME>\lib`目录下...
`附录A.Java 2 SDK版原始码概观.pdf`可能提供了Java 2 SDK源码的概述,这对于理解ClassLoader的工作方式非常有帮助,因为你可以看到其内部的实现细节。 `CH_00-导读.pdf`则可能是整个系列的引导章节,介绍了接下来...