使用CustomClassLoader

时间:2017-12-13 13:51:25

标签: classloader urlclassloader

我经历了很多帖子和问题,但没有一个人有一个绝对的Java程序来实现它。

要求:由于某些原因,我的应用程序加载了Common-codec 1.3.jar,

以后,在同一个jvm中,进程需要使用不同版本的Common-code 1.10.jar。

但是由于先前的类已加载并且它们具有相同的包名称,因此使用Java程序重新加载它们并不会替换现有的类。

这是我用来重新加载(替换)现有密钥的代码(示例),但没有找到预期的运气。请让我知道这可以通过Java示例完成。

String pathToJar=/root/Desktop/commons-codec-1.10.jar";

            JarFile jarFile = null;
            try {
                jarFile = new JarFile(pathToJar);
            } catch (IOException e) {
                e.printStackTrace();
            }
            Enumeration<JarEntry> e = jarFile.entries();

            URL[] urls = new URL[0];
            try {
                urls = new URL[]{ new URL("jar:file:" + pathToJar+"!/") };
            } catch (MalformedURLException e1) {
                e1.printStackTrace();
            }
            URLClassLoader cl = URLClassLoader.newInstance(urls);

            while (e.hasMoreElements()) {
                JarEntry je = e.nextElement();
                if(je.isDirectory() || !je.getName().endsWith(".class")){
                    continue;
                }
                // -6 because of .class
                String className = je.getName().substring(0,je.getName().length()-6);
                className = className.replace(File.separatorChar, '.');
                String check="org.apache.commons.codec.binary.Base32";

                try {
                    Class c = cl.loadClass(className);   // Excepting it to replace old files,but thats not happening
                } catch (ClassNotFoundException e1) {
                    e1.printStackTrace();
                }

我被建议编写自定义类加载器并通过它可以卸载。 有些人可能会展示一些相关的代码或过程。

1 个答案:

答案 0 :(得分:1)

Class&#34;卸载&#34;只有在定义它的ClassClassLoader都符合垃圾回收条件时才会发生。此外,任何给定的加载器只能定义一个特定名称的类一次。因此(除了诸如检测或JDK实现内部的异乎寻常的变通方法之外),没有像#34;重新加载的类加载器那样的东西&#34; - 至少不是真正意义上的;为了获得 n 不同的&#34;版本&#34;必须在 n 不同defineClass(C)个对象上调用 C ClassLoader

让我们来看看以下简单的用例:我们有一个app.jar,(不出所料)包含一个声明main方法的类;和lib.jar的两个副本,每个副本都有一个类LibApiImpl,封装了一个只将版本相关的字符串打印到stdout的方法。我们的目标是仅仅参考&#34;库的两个副本&#34;来自main的类,看到输出两个不同的字符串。本文的其余部分仅展示了满足此要求的所有可能方法中的两种。

方法#1 - 手动实例化ClassLoader s

直接的解决方案是,每次需要加载不同的URLClassLoader时,只需创建一个新的LibApiImpl。不需要自定义类加载器实现,默认的父级优先委托模型同时为应用程序和库提供服务。有几点需要注意:

  • 库JAR 不得位于类路径上,以便可被默认应用程序类加载器发现。否则,应用程序类加载器将支持&#34;一个在另一个上,并且由于默认的委托模型,应用程序加载器的标准URLClassLoader子项将无法覆盖其父母对此事的看法。
  • 要访问库类,必须使用3参数Class::forName ,指定手动实例化的类加载器。单arg版本将委托给应用程序类加载器,(根据前一点)当然不知道是否存在相应的.class文件。
  • 如果要将反射获取的类转换为接口,就像本文中的情况一样,接口必须,而不是实现,则驻留在类路径上。

演示代码

package com.example.app;

import java.net.URL;
import java.net.URLClassLoader;

import com.example.lib.api.LibApi;

public class App {

    public static void main(String... args) throws Exception {
        try (URLClassLoader loader = new URLClassLoader(new URL[] { new URL("file:/path/to/lib1.jar") })) {
            ((LibApi) Class.forName("com.example.lib.impl.LibApiImpl", true, loader).newInstance()).printVersionInfo();
        }
        try (URLClassLoader loader = new URLClassLoader(new URL[] { new URL("file:/path/to/lib2.jar") })) {
            ((LibApi) Class.forName("com.example.lib.impl.LibApiImpl", true, loader).newInstance()).printVersionInfo();
        }
    }

}
package com.example.lib.api;

public interface LibApi {

    void printVersionInfo();

}
package com.example.lib.impl;

import com.example.lib.api.LibApi;

public class LibApiImpl implements LibApi {

    @Override
    public void printVersionInfo() {
        System.out.println("\n** lib " + getClass() + " / loaded by " + getClass().getClassLoader() + " **\n");
    }

}

包装

纠正com.example.app.App中的路径;然后产生以下4个JAR:

  • 包含app.jar包的com.example.app
  • 包含lib-api.jar包的com.example.lib.api
  • 两个&#34;版本&#34; (只导出两次),lib1.jarlib2.jar,每个都包含com.example.lib.impl包。

测试

运行如下:

java -cp '/path/to/app.jar:/path/to/lib-api.jar' com.example.app.App

示例输出:

** lib class com.example.lib.impl.LibApiImpl / loaded by java.net.URLClassLoader@55f96302 **


** lib class com.example.lib.impl.LibApiImpl / loaded by java.net.URLClassLoader@135fbaa4 **

方法#2 - 创建&#34;主 - 从&#34;装载机

有时&#34;父母优先&#34;模型不足。作为一个人为的例子,假设我们想要获得&#34; copy&#34;由应用程序类加载器的某个子级加载的 last LibApiImpl,而不必关心实际定义该副本的子项。换句话说,我们希望调用Class.forName("com.example.lib.impl.LibApiImpl")返回&#34;最新鲜的&#34; LibApiImpl版本。但是,除非我们使用自定义实现覆盖应用程序类加载器,或者更一般地,App类的类加载器,否则该调用将始终失败,因为在默认委托模型下,委派单向流动较低级别的装载机,而不是反之亦然。

下面给出的应用程序类加载器实现的行为如下(有关具体说明,请参阅Loaders类的Javadoc概述):有一个&#34; master&#34; loader,作为应用程序类加载器,可能有一个孩子,称为&#34; slave&#34;。 master负责从类路径(在这种情况下为app.jarlib-api.jar)中加载不可重新加载的应用程序类,而slave则负载加载可重新加载的非类路径的应用程序类({{1 }和lib1.jar)。两者之间的通信是双向的,并且最终将定义任何给定类的加载器,&#34; fixed&#34;或者&#34; reloadable&#34;,总是分别是主人和奴隶,无论是&#34;发起&#34;加载器,即应用程序名为lib2.jar的加载程序,或传递给loadClass。当然,这只不过是一个玩具实现,旨在(希望)说明一个不同的授权方案可能会是什么样子,并且可能存在我尚未想象的方式的缺陷。例如,实际的实现必须提供适当的并发性;提供Class::forName等的合规实现;解决代码可访问性,验证以及可能的代码库权限分配问题;并允许多个从属的可扩展性和配置,甚至可以将任意实现的其他子节点附加到主节点,同时保留明确定义的委托语义。当然,当有OSGi之类的东西(以及其他许多东西)时,编写正确的实现通常需要付出太大的努力。

演示代码

getResource
package com.example.app;

import java.net.URL;


import com.example.app.Loaders.MasterLoader;
import com.example.lib.api.LibApi;

public class App2 {

    public static void main(String... args) throws Exception {

        MasterLoader loader = (MasterLoader) ClassLoader.getSystemClassLoader();
        loader.setDebug(true);
        loader.refresh(new URL[] { new URL("file:/path/to/lib1.jar") });

        newLibApi().printVersionInfo();

        loader.refresh(new URL[] { new URL("file:/path/to/lib2.jar") });

        newLibApi().printVersionInfo();

    }

    static LibApi newLibApi() throws Exception {
        return (LibApi) Class.forName("com.example.lib.impl.LibApiImpl").newInstance();
    }

}

包装

再次纠正package com.example.app; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; /** * Contains a "Master (parent)/Slave (child)" <code>ClassLoader</code> "duo".<br/> * <br/> * The class loading "protocol" adhered by the respective {@code loadClass(String, boolean)} * implementations of the two is as follows: * <ol> * <li>If the name argument matches the name of the loader's class itself, return that class. * Otherwise proceed.</li> * <li>If a call to {@code findLoadedClass(name)} yields a non-null class, return that class. * Otherwise proceed.</li> * <li>Let <em>C</em> be either this loader's parent, if this is a "slave", or its child, if this is * a "master". If <em>C</em> is non-null, have it execute steps (1), (2) itself. If a class gets * produced, return that class. Otherwise (i.e., if there either is no result, or <em>C</em> is * null) proceed.</li> * <li>If the name argument refers to a potential bootstrap classpath class name, call * {@code loadClass(name)} on the default system classloader (the "master's" parent). If the call * succeeds, return that class. Otherwise proceed.</li> * <li>If the name argument refers to a .class file under this loader's search path, read it, define * and link a new class off of its contents, and return that "freshly-fefined" class. Otherwise * proceed.</li> * <li>Once again, let <em>C</em> be the loader specified in step (3). If non-null, have it execute * step (5) on itself. If a class gets produced, return that class. Otherwise fail.</li> * </ol> */ public class Loaders { private static class SlaveLoader extends URLClassLoader { static final Pattern BOOT_CLASS_PATH_RES_NAMES = Pattern.compile("((com\\.)?sun|java(x)?)\\..*"); static final URL[] EMPTY_SEARCH_PATH = new URL[0]; static final URL[] createSearchPath(String pathNames) { if (pathNames != null) { List<URL> searchPath = new ArrayList<>(); for (String pathName : pathNames.split(File.pathSeparator)) { try { searchPath.add(Paths.get(pathName).toUri().toURL()); } catch (MalformedURLException e) { e.printStackTrace(); } } return searchPath.toArray(new URL[0]); } return EMPTY_SEARCH_PATH; } static final byte[] readClassData(URL classResource) throws IOException { try (InputStream in = classResource.openStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { while (in.available() > 0) { out.write(in.read()); } return out.toByteArray(); } } final String loadClassOutcomeMsgFmt = "loadClass return '{'\n\tloader = {0}\n\ttarget = {1}\n\tresult : {2}\n'}'"; volatile boolean debug; SlaveLoader(URL[] searchPath, ClassLoader parent) { super(searchPath, parent); } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { validateName(name); Class<?> ret = loadFromCache(name); if (ret != null) { return ret; } MasterLoader parent = (MasterLoader) getParent(); if ((ret = parent.loadFromCache(name)) != null) { log(loadClassOutcomeMsgFmt, this, name, "3 - early return - pre-loaded/cached - via " + parent); return ret; } if ((ret = loadFromBootClasspath(name)) != null) { return ret; } if ((ret = loadFromSearchPath(name, resolve)) != null) { return ret; } if ((ret = parent.loadFromSearchPath(name, resolve)) != null) { log(loadClassOutcomeMsgFmt, this, name, "6 - common/non-reloadable classpath delegation - via " + parent); return ret; } if ((ret = parent.loadFromSearchPath(name, resolve)) != null) { return ret; } throw createCnfe(name, null); } void validateName(String name) throws ClassNotFoundException { log("loadClass entry '{'\n\tloader = {0}\n\ttarget = {1}\n'}'", this, name); if ((name == null) || name.trim().isEmpty()) { throw createCnfe(name, null); } } Class<?> loadFromCache(String name) { Class<?> ret = getClass(); if (ret.getName().equals(name)) { log(loadClassOutcomeMsgFmt, this, name, "1 - early return - own class"); return ret; } if ((ret = findLoadedClass(name)) != null) { log(loadClassOutcomeMsgFmt, this, name, "2 - early return - pre-loaded/cached"); return ret; } return null; } Class<?> loadFromBootClasspath(String name) { if (BOOT_CLASS_PATH_RES_NAMES.matcher(name).matches()) { ClassLoader defSysCl = ClassLoader.getSystemClassLoader().getParent(); try { Class<?> ret = ClassLoader.getSystemClassLoader().getParent().loadClass(name); log(loadClassOutcomeMsgFmt, this, name, "4 - bootstrap classpath delegation - via " + defSysCl); return ret; } catch (ClassNotFoundException cnfe) { } } return null; } Class<?> loadFromSearchPath(String name, boolean resolve) throws ClassNotFoundException { Class<?> ret = null; URL res = findResource(name.replace(".", "/") + ".class"); if (res != null) { byte[] b; try { b = readClassData(res); } catch (IOException ioe) { throw createCnfe(name, ioe); } ret = defineClass(name, b, 0, b.length); if (resolve) { resolveClass(ret); } log(loadClassOutcomeMsgFmt, this, name, "5 - freshly-defined from local search path"); return ret; } return null; } ClassNotFoundException createCnfe(String name, Throwable cause) throws ClassNotFoundException { return new ClassNotFoundException(MessageFormat.format("Class loading : {0} : {1} : FAILED", this, (name == null) ? "null" : name, cause)); } void log(String msg, Object... args) { if (debug) { System.out.println(MessageFormat.format("\n" + msg + "\n", args)); } } public void setDebug(boolean debug) { this.debug = debug; } @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } log("ClassLoader finalization : {0}", this); } } public static class MasterLoader extends SlaveLoader { static final URL[] DEFAULT_CLASS_PATH = createSearchPath(System.getProperty("java.class.path")); private URL[] reloadableSearchPath = EMPTY_SEARCH_PATH; private volatile SlaveLoader slave; public MasterLoader(ClassLoader parent) { super(DEFAULT_CLASS_PATH, parent); } public synchronized void refresh(URL[] reloadableSearchPath) { int len; if ((reloadableSearchPath != null) && ((len = reloadableSearchPath.length) > 0)) { List<URL> path = new ArrayList<>(len + 1); for (int i = 0; i < len; i++) { URL entry = reloadableSearchPath[i]; if (entry != null) { path.add(entry); } } this.reloadableSearchPath = (!path.isEmpty()) ? path.toArray(EMPTY_SEARCH_PATH) : EMPTY_SEARCH_PATH; } else { this.reloadableSearchPath = EMPTY_SEARCH_PATH; } if (slave != null) { try { slave.close(); } catch (IOException ioe) { } slave = null; /* * At least two calls to System::gc appear to be required in order for Class::forName to cease * returning cached classes previously defined by slave and for which the master served as an * intermediary, i.e., an "initiating loader". * * See also http://blog.hargrave.io/2007/09/classforname-caches-defined-class-in.html */ for (int i = 0; i < 2; i++) { System.gc(); try { Thread.sleep(100); } catch (InterruptedException ie) { } } } if (this.reloadableSearchPath != EMPTY_SEARCH_PATH) { log("Class loader search path refresh : {0}\n\tSearch path = {1}", this, Arrays.toString(this.reloadableSearchPath)); slave = new SlaveLoader(this.reloadableSearchPath, this); slave.setDebug(debug); } } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { validateName(name); Class<?> ret = loadFromCache(name); if (ret != null) { return ret; } if ((slave != null) && ((ret = slave.loadFromCache(name)) != null)) { log(loadClassOutcomeMsgFmt, this, name, "3 - early return - pre-loaded/cached - via " + slave); return ret; } if ((ret = loadFromBootClasspath(name)) != null) { return ret; } if ((ret = loadFromSearchPath(name, resolve)) != null) { return ret; } if ((slave != null) && ((ret = slave.loadFromSearchPath(name, resolve)) != null)) { log(loadClassOutcomeMsgFmt, this, name, "6 - reloadable classpath delegation - via " + ret.getClassLoader()); return ret; } throw createCnfe(name, null); } } } 中的路径;将com.example.app.App2App2添加到com.example.app.Loaders;和再出口。其他JAR应该保留在前一个例子中。

测试

运行如下:

app.jar

示例输出(java -cp '/path/to/app.jar:/path/to/lib-api.jar' \ '-Djava.system.class.loader=com.example.app.Loaders$MasterLoader' \ com.example.app.App2 debug省略):

loadClass