父子类加载器类解析

时间:2016-01-18 20:12:02

标签: java classloader urlclassloader contextclassloader

任务和初步调查

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

简短说明

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

我有:

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

尝试了方法

我创建了一个专用的child-first classLoader --InverseClassLoader,并创建了AppLaunchClassLoader(默认的Thread.currentThread()。GetContextClassLoader()classLoader)它的父级。使用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加载,该怎么办?有没有办法说所有'相关'类必须由同一个类加载器加载?
  4. 更新

    这是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;
    }
    

1 个答案:

答案 0 :(得分:0)

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

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

  

请记住,加载和定义类的类加载器是该类的内部JVM ID的一部分。   如果当前类加载器加载一个类X,该类随后执行,例如,对某些类型为Y的数据执行JNDI查找,   上下文加载器可以加载和定义Y.   此Y定义将与同名的定义不同,但当前加载程序可以看到。   输入晦涩的类强制转换和加载器约束违例异常。

下面是一个简单的演示程序,用于显示从另一个类加载器到接口的强制转换可以正常工作 (注意我在一个bin文件夹中使用了一个简单的Java项目,在同一个(测试)包中使用了你问题中的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看起来很像“儿童第一类加载器”,请参阅this question 对于讨论这种类加载方式的一些好答案。 子第一个类加载器可以用来分别加载“相关类”(来自你的第三个问题)。 您还可以在某些包中更新InvertedClassLoader到始终“自加载”类。 并记住“一旦类被类加载器加载,它就使用该类加载器来加载它需要的所有其他类” (引自this blog article)。