0

任务和初步调查

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

简短的介绍

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

我有:

  • 应用程序的主要代码位于 mainapp.jar 中;
  • 位于 account-interfaces.jar 中的AccountService接口;
  • 位于 account-impl.jar 中并实现AccountService接口的AccountServiceImpl类;
  • 我的主要应用程序具有以下结构
    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”异常,这意味着这两个类由不同的类加载器加载。但是那些类加载器是父子关系。所以即使一个类是由父类(接口 - '抽象'类型)加载的,它也不能与子类加载器加载的类(具体 impl)一起使用,这对吗?那么为什么我们需要这种父子关系呢?
  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;
}
4

1 回答 1

0

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

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

请记住,加载和定义类的类加载器是该类的内部 JVM ID 的一部分。如果当前类加载器加载一个类 X,该类随后执行,例如,对 Y 类型的某些数据进行 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 无法转换为 CTest”异常,但使用该接口可以防止该异常。由于这个小演示工作正常,我猜您的应用程序中还有更多的事情会以某种方式破坏类加载。我建议您从类加载工作的情况下工作并继续添加代码,直到它破坏类加载。

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

于 2016-01-20T03:48:56.277 回答