How to solve JAR hell with a Parent Last Classload
7606 단어 자바ClassLoader
Java distinguishes class files from their fully qualified class names. So unless you use some technology like OSGi or your own Classloader hack, you can't effectively use two versions of the same library in your Java program. In this post, Ill give you a piece of code I've written to solve Jar Hell.
In Java the classes are loaded by a hierarchy of ClassLoaders. There are three types of Classloaders in the Classloader hierarchy.
Bootstrap Classloader loads the core java classes like java.lang.* when the JVM starts up. Extensions Classloader loads jar files in the extension directory (JAVA_HOME/jre/lib/ext). These are the extensions of standard core java classes. System Classloader is the one that loads your classes. When Java instantiates an Object, it searches for the Class which is loaded into the Runtime. This search happens in a funny way.
Say you are instantiating a java.lang.String object in your Main java class which is loaded by the System Classloader. The Class java.lang.String is looked for by calling the findClass(classname) method in that System Classloader. What the findClass(classname) method does is that it calls the findclass() method in its parent Classloader which is the Extensions Classloader . And the findclass() method in the Extensions Classloader delegates the call to the findclass() method of the Bootstrap Classloader which is its parent. If the parent has loaded the class already, the parent will return the Class and if the parent returns null, the child Classloader will try to load that class. This way Java makes sure that it loads the correct java.lang.String class and even if you have implemented your own String class with java.lang.String fully qualified class name, it wont be loaded since the Bootstrap Classloader will already have loaded that class.
If you instantiate an Object for your own Class say com.you.test.YourTestClass , even if the findClass() method of the System Classloader which loaded your Main class delegates it to its parents, the parent classloaders will not know about a class called com.you.test.YourTestClass , so it will come back to the System Classloader by returning null, and the object will be created from the class loaded from the System Classloader.
The problem with this approach is that if you have two classes with the same fully qualified class name saycom.you.test.YourClassCausingJarHell in two Jars, the System Classloader will return the one it has already loaded, resulting in a Jar Hell. To solve this we can use a ParentLastClassLoader like follows to load the classes from the jar files you specify irrespective of the fact that the Classloaders have already loaded a class with that fully qualified class name. The ParentLastClassloader will first try to load the Classes from the given Jars and it will delegate to its parents only if it can't find the Classes hence namedParentLast. Take a look at the implementation and note that it puts the already loaded classes in a data structure to use when needed.
public class ParentLastClassLoader extends ClassLoader {
private String[] jarFiles; //Paths to the jar files
private Hashtable classes = new Hashtable(); //used to cache already defined classes
public ParentLastClassLoader(ClassLoader parent, String[] paths)
{
super(parent);
this.jarFiles = paths;
}
@Override
public Class findClass(String name) throws ClassNotFoundException
{
System.out.println("Trying to find");
throw new ClassNotFoundException();
}
@Override
protected synchronized Class loadClass(String className, boolean resolve) throws ClassNotFoundException
{
System.out.println("Trying to load");
try
{
System.out.println("Loading class in Child : " + className);
byte classByte[];
Class result = null;
//checks in cached classes
result = (Class) classes.get(className);
if (result != null) {
return result;
}
for(String jarFile: jarFiles){
try {
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(className.replace(".","/") + ".class");
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = is.read();
}
classByte = byteStream.toByteArray();
result = defineClass(className, classByte, 0, classByte.length, null);
classes.put(className, result);
} catch (Exception e) {
continue;
}
}
result = (Class) classes.get(className);
if (result != null) {
return result;
}
else{
throw new ClassNotFoundException("Not found "+ className);
}
}
catch( ClassNotFoundException e ){
System.out.println("Delegating to parent : " + className);
// didn't find it, try the parent
return super.loadClass(className, resolve);
}
}
}
When instantiating this ParentLastClassLoader , the current SystemClassLoader which loaded your Main class will be set as its parent. So you can write your Main class like follows
public class MainClass {
public static void main(String [] args) {
//This will instantiate the object from the allready loaded class from the System Classloader
com.you.test.YourClassCausingJarHell yourClass = new com.you.test.YourClassCausingJarHell();
String[] pathsToJars = {"jar_path1", "jar_path2"};
ClassLoader loader = new ParentLastClassLoader(Thread.currentThread().getContextClassLoader(),
pathsToJars);
Class correctClass = loader.loadClass("com.you.test.YourClassCausingJarHell");
Method theMethod = correctClass.getMethod("theMethodYouWant");
//This calls the right method from the right class.
theMethod.invoke(correctClass.getConstructor().newInstance());
}
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.