【问题标题】:Parent-Child Classloader Class Resolution父子类加载器类解析
【发布时间】:2016-04-24 03:03:18
【问题描述】:

任务和初步调查

我尝试在一个 java swing 应用程序的缓存实例附近设置两个 Oracle Coherence。可以找到解决方案的想法here。我的情况有点复杂,这就是游戏开始的地方。

简短说明

就我而言,有一个帐户服务。它可以有两个端点:SIT 和 UAT。为了创建两个这样的服务,我需要加载 Coherence 的两个“实例”,以便用系统变量 (tangosol.coherence.cacheconfig) 覆盖端点。

我有:

  • app的主要代码位于mainapp.jar中;
  • 位于 account-interfaces.jar 中的 AccountService 接口;
  • AccountServiceImpl 类,位于 account-impl.jar 中并实现 AccountService 接口;
  • 我的主要应用程序具有以下结构
    bin: startup.bat, startup.sh
    conf: app.properties
    lib: mainapp.jar, account-interfaces.jar, account-impl.jar, coherence.jar
    

尝试的方法

我创建了一个专用的子优先类加载器 - InverseClassLoader,并将 AppLaunchClassLoader(默认的 Thread.currentThread().GetContextClassLoader() 类加载器)设为它的父级。使用 InverseClassLoader 我加载 AccountServiceImpl 类:

Class<AccountServiceImpl> acImplClass = contextClassLoader.selfLoad(AccountServiceImpl.class).loadClass(AccountServiceImpl.class);
Constructor<AccountServiceImpl> acConstructor =
acImplClass .getConstructor(String.class); 
AccountService acService = acConstructor .newInstance(serviceURL);

问题和疑问

  1. 我收到“AccountServiceImpl 无法转换为 AccountService”异常,这意味着这两个类由不同的类加载器加载。 但是那些类加载器是父子关系。所以我是对的,即使一个类是由父类(接口 - '抽象'类型)加载的,它也不能与子类加载器加载的类(具体实现)一起使用?那么为什么我们需要这种父子关系呢?
  2. 我在代码中指定了 AccountService 接口,它由默认的类加载器加载。我尝试将上面的代码包装为一个线程并将 InverseClassLoader 设置为上下文类加载器。没有改变。那么我不能使用这样的接口实现编码(如通常的编码)并且需要一直使用反射来调用具体方法,这对吗? (希望有解决方案);
  3. 比如说,我列出了由 InverseClassLoader 加载的 AccountService 和 AccountServiceImpl 类。如果我需要这两个可访问的其他类也由 InverseClassLoader 加载怎么办?有没有办法说所有“相关”类都必须由同一个类加载器加载?

更新

这里是 InverseClassLoader:

public class InvertedClassLoader extends URLClassLoader {

private final Set<String> classesToNotDelegate = new HashSet<>();

public InvertedClassLoader(URL... urls) {
    super(urls, Thread.currentThread().getContextClassLoader());
}

public InvertedClassLoader selfLoad(Class<?> classToNotDelegate) {
    classesToNotDelegate.add(classToNotDelegate.getName());
    return this;
}

@Override
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    if (shouldNotDelegate(className)) {
        System.out.println("CHILD LOADER: " + className);
        Class<?> clazz = findClass(className);
        if (resolve) {
            resolveClass(clazz);
        }
        return clazz;
    }
    else {
        System.out.println("PARENT LOADER: " + className);
        return super.loadClass(className, resolve);
    }
}

public <T> Class<T> loadClass(Class<? extends T> classToLoad) throws ClassNotFoundException {
    final Class<?> clazz = loadClass(classToLoad.getName());
    @SuppressWarnings("unchecked")
    final Class<T> castedClass = (Class<T>) clazz;
    return castedClass;
}

private boolean shouldNotDelegate(String className) {
    if (classesToNotDelegate.contains(className) || className.contains("tangosol") ) {
        return true;
    }
    return false;
}

【问题讨论】:

  • 根据您的代码很难弄清楚。您能否简要描述一下您正在尝试实施的设计理念?

标签: java classloader urlclassloader contextclassloader


【解决方案1】:

问题 1,第一部分我无法重现(见下文)。至于第 2 部分: 类加载器的层次结构是为了防止“X 不能转换为 X”异常。 但是如果你打破了父母第一的规则,你就会惹上麻烦。

关于问题 2:设置线程的上下文类加载器本身不会做任何事情,另请参阅 this article (javaworld.com) 了解更多背景信息。此外,关于第 1 期,第 2 部分,引用文章 这描述了如果当前类加载器之间没有父子关系会发生什么 和线程的上下文类加载器:

请记住,加载和定义类的类加载器是该类的内部 JVM ID 的一部分。 如果当前的类加载器加载了一个类 X,该类随后执行了一个 JNDI 查找 Y 类型的数据, 上下文加载器可以加载和定义 Y。 这个 Y 定义将不同于同名但被当前加载程序看到的定义。 输入不起眼的类强制转换和加载器约束违规异常。

下面是一个简单的演示程序,显示从另一个类加载器转换为接口可以工作 (请注意,我使用的是一个简单的 Java 项目,该项目的类位于 bin 文件夹中,而您的问题中的 InvertedClassLoader 位于同一个(测试)包中):

import java.io.File;

public class ChildFirstClassLoading {

    public static void main(String[] args) {

        InvertedClassLoader cl = null;
        try {
            File classesDir = new File(new File("./bin").getCanonicalPath());
            System.out.println("Classes dir: " + classesDir);
            cl = new InvertedClassLoader(classesDir.toURI().toURL());
            cl.selfLoad(CTest.class);
            System.out.println("InvertedClassLoader configured.");
            new CTest("Test 1").test();
            ITest t2 = cl.loadClass(CTest.class)
                    .getConstructor(String.class)
                    .newInstance("Test 2");
            t2.test();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cl != null) {
                try { cl.close(); } catch (Exception ignored) {}
            }
        }
    }

    public interface ITest {
        void test();
    }

    public static class CTest implements ITest {

        static {
            System.out.println("CTest initialized.");
        }

        private String s;
        public CTest(String s) {
            this.s = s;
        }
        public void test() {
            System.out.println(s);
        }
    }

}

如果您将ITest t2 = 更改为CTest t2 =,您将收到“CTest cannot be cast to CTest”异常, 但是使用该接口可以防止该异常。 由于这个小演示工作正常,我猜您的应用程序中还有更多的事情会以某种方式破坏类加载。 我建议你从类加载工作的情况开始,并继续添加代码,直到它破坏类加载。

InvertedClassLoader 看起来很像“child first classloader”,见this question 对于讨论这种类加载方式的一些好的答案。 子第一个类加载器可用于单独加载“相关类”(来自您的第三期)。 您还可以将InvertedClassLoader 更新为始终“自加载”某些包中的类。 请记住,“一旦类加载器加载了一个类,它就会使用该类加载器来加载它需要的所有其他类” (引自this blog article)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 2013-11-04
    • 1970-01-01
    • 2017-12-02
    • 1970-01-01
    • 2014-08-12
    • 1970-01-01
    相关资源
    最近更新 更多