如何自定义类加载器

时间:2014-05-22 10:06:58

标签: classloader

我尝试使用客户类加载器实现此功能:我在alternatives.jar文件中有一些类文件,它们提供的实现与正常实现不同。也就是说,这个jar中的每个类都有另一个版本,在其他jar文件中 - 也会加载到类路径中。

我知道使用仪器API实现相同目的会更好。但现在我担心的是我需要理解为什么我失败了。

所以这是我的方法: 1.定义一个AlternativeClassLoader.java,在这个文件中,我重写了findClass方法。因此,如果可以从alternatives.jar中找到类名,则使用alternatives.jar中的版本。 2.在构造函数中,我调用了super(null),因此所有这些类加载工作都将由我的类加载器执行,而不是系统的。 3.这(似乎是真的)也要求我加载其他类(如果它们不是系统一)。所以我必须解析classpath,找到类路径指示的所有类。

我的问题是,我可以加载我的替代类,一切似乎都很好......但是,我使用的是slf4j,它会出现以下错误:

Failed to auto configure default logger context
Reported exception:
ch.qos.logback.core.joran.spi.JoranException: Parser configuration error occurred

Failed to instantiate [ch.qos.logback.classic.LoggerContext]

报告的例外:

java.lang.ExceptionInInitializerError
        at java.util.ResourceBundle.getLoader(ResourceBundle.java:431)
        at java.util.ResourceBundle.getBundle(ResourceBundle.java:841)

我怀疑这是由我糟糕的类加载器实现引起的。有人会帮帮我吗?非常感谢!

这是我的类加载器:

public class AlternativeClassLoader extends ClassLoader {
    private static final String ALTERNATIVE_JAR_PROPERTY = "alternativejar";
    private static final Logger logger = LoggerFactory
            .getLogger(AlternativeClassLoader.class);

    private Map<String, Class<?>> clzCache = new HashMap<String, Class<?>>();
    private Map<String, String> others = new HashMap<String, String>();

    private Set<String> alternativesRegistry;
    private JarFile altjar;

    public AlternativeClassLoader(ClassLoader parent) {
        /*
         * pass null so I can incept all class loading except system's. By doing
         * this you'll need to override findClass
         */
        super(null);
        registerAlternatives();
        registerOthers();
    }

    /**
     * This method will parse classpath and get all non-system class name, and
     * build classname - jar_file_path/file_system_path mappings
     */
    private void registerOthers() {
        String[] paths = System.getProperty("java.class.path").split(":");
        URL[] urls = new URL[paths.length];

        for (String path : paths) {
            if (path.endsWith("*.jar")) {
                registerClass(path, others);
            } else {
                File f = new File(path);
                if (!f.isDirectory())
                    continue;

                File[] classFiles = f.listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File arg0) {
                        if (arg0.getName().endsWith(".class")) {
                            return true;
                        } else {
                            return false;
                        }
                    }
                });

                for (File file : classFiles) {
                    String fileName = file.getName();
                    String className = fileName.substring(0,
                            fileName.lastIndexOf("."));

                    others.put(className, file.getPath());
                }
            }
        }

        showRegistry(
                "Me will also be responsible for loading the following classes:",
                others);
    }

    private void registerClass(String path, Map<String, String> registry) {
        try {
            JarInputStream jis = new JarInputStream(new FileInputStream(path));
            for (JarEntry entry = jis.getNextJarEntry(); entry != null; entry = jis
                    .getNextJarEntry()) {
                if (entry.getName().endsWith(".class") && !entry.isDirectory()) {
                    StringBuilder className = new StringBuilder();
                    for (String part : entry.getName().split("/")) {
                        if (className.length() != 0)
                            className.append(".");
                        className.append(part);

                        if (part.endsWith(".class"))
                            className.setLength(className.length()
                                    - ".class".length());
                    }

                    registry.put(className.toString(), path);
                }
            }
        } catch (Exception e) {
            e.printStackTrace(System.out);
            logger.error(
                    "Failed when read/parse jar {}. Your class file may not been replaced by alternative implementation",
                    path, e);
        }
    }

    /**
     * Try to find alternative class implementation from jar file specified by
     * ALTERNATIVE_JAR_PROPERTY. If it's not specified, then use same jar file
     * where this classloader is loaded.
     */
    private void registerAlternatives() {
        String jarFilePath = System.getProperty(ALTERNATIVE_JAR_PROPERTY);

        if (jarFilePath == null || jarFilePath.isEmpty()) {
            URL url = getClass().getProtectionDomain().getCodeSource()
                    .getLocation();
            System.out.println(url + ":" + url.toString());
            jarFilePath = url.getPath();
        }

        try {
            altjar = new JarFile(jarFilePath);
        } catch (IOException e) {
            logger.error("cannot read jar {}", jarFilePath);
            return;
        }

        Map<String, String> registry = new HashMap<String, String>();
        registerClass(jarFilePath, registry);

        alternativesRegistry = registry.keySet();

        showRegistry("===Found the following alternative class:===", registry);
    }

    private void showRegistry(String string, Map<String, String> registry) {
        System.out.println(string);

        for (String clzName : registry.keySet()) {
            System.out.printf("Class:%30s ->%s\n", clzName,
                    registry.get(clzName));
        }
    }

    private Class<?> myLoadClass(String name) throws IOException,
    ClassFormatError {
        logger.debug("myload class {}", name);
        System.out.printf("myload class %s\n", name);

        if (alternativesRegistry.contains(name) && altjar != null) {
            JarEntry entry = altjar.getJarEntry(name + ".class");
            InputStream is = altjar.getInputStream(entry);
            return readClassData(name, is);
        }

        String path = others.get(name);

        if (path == null || path.isEmpty()) {
            return null;
        }

        if (path.endsWith(".jar")) {
            JarFile jar = new JarFile(path);
            JarEntry entry = jar.getJarEntry(name + ".class");
            InputStream is = jar.getInputStream(entry);
            return readClassData(name, is);
        } else {// it's a folder, need to read clz from .class file
            System.out.printf("file path for %s is %s\n", name, path);
            InputStream is = new FileInputStream(new File(path));
            return readClassData(name, is);
        }
    }

    private Class<?> readClassData(String name, InputStream is)
            throws IOException, ClassFormatError {
        byte[] buffer = new byte[4096];
        ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length);
        int len = is.read(buffer);
        while (len > 0) {
            out.write(buffer, 0, len);
            len = is.read(buffer);
        }

        Class<?> clz = defineClass(name, out.toByteArray(), 0, out.size());

        if (clz != null) {
            System.out.printf("loaded %s by me\n", name);
            clzCache.put(name, clz);
        }
        return clz;
    }

    protected Class<?> findCachedClass(String name)
            throws ClassNotFoundException {
        Class<?> clz = clzCache.get(name);

        if (clz == null) {
            clz = findLoadedClass(name);
        }

        return clz;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass: " + name);

        Class<?> cls = findCachedClass(name);

        if (cls == null) {
            try {
                cls = myLoadClass(name);
            } catch (ClassFormatError | IOException e) {
                logger.error("failed to load class {}", name, e);
                System.out.printf("failed to load class %s\n", name);
                e.printStackTrace();
            }
        }

        return cls;
    }
}

我试图覆盖findResource(),但它从未被调用。

这就是我使用类加载器的方法:

java -Djava.system.class.loader=AlternativeClassLoader -classpath=.:./alternatives.jar:./slf4j-xxx.jar Test

1 个答案:

答案 0 :(得分:0)

好的,我解决了这个问题。棘手的是:

  1. 切勿使用java以外的任何其他软件包。*。否则,它将导致递归加载...... IllegalState错误。
  2. 在您的类加载器构造函数中,加载所有替代类并缓存它们。
  3. 在你的构造函数中,调用super(null)以外的super(parent),然后你不需要做所有类加载的东西,父类加载器可以为你做。
  4. 在覆盖findClass()中,如果可以从缓存中找到该类(意味着它们具有替代实现),则返回它,否则让super.findClass为您完成其余的工作。
  5. 所以以下是源代码:

    public class AlternativeClassLoader extends ClassLoader {
        private static final String ALTERNATIVE_JAR_PROPERTY = "alternativejar";
        private Map<String, Class<?>> clzCache = new HashMap<String, Class<?>>();
    
        public AlternativeClassLoader(ClassLoader parent) {
            super(parent);
            loadAlternativeClasses();
        }
        private void loadAlternativeClasses() {
            String jarFilePath = System.getProperty(ALTERNATIVE_JAR_PROPERTY);
            if (jarFilePath == null || jarFilePath.isEmpty()){
                URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
                System.out.println(url + ":" + url.toString());
                jarFilePath = url.getPath();
            }
    
            JarInputStream jis;
            try {
                jis = new JarInputStream(new FileInputStream(jarFilePath));
                JarEntry entry;
                while ((entry = jis.getNextJarEntry()) != null){
                    String className = entry.getName();
                    className = className.substring(0, className.length() - ".class".length());
                    System.out.printf("loading class from %s: %s\n", jarFilePath, className);
                    readClassData(className, jis);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }   private Class<?> readClassData(String name, InputStream is) throws IOException,
                ClassFormatError {
            byte[] buffer = new byte[4096];
            ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length);
            int len = is.read(buffer);
            while (len > 0) {
                out.write(buffer, 0, len);
                len = is.read(buffer);
            }
            Class<?> clz = defineClass(name, out.toByteArray(), 0, out.size());
            if (clz != null) {
                System.out.printf("loaded %s by myself\n", name);
                clzCache.put(name, clz);
            }
            return clz;
        }
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            System.out.println("findClass: " + name);
            Class<?> cls = clzCache.get(name);
            if (cls == null)
                cls = super.findClass(name);
    
            return cls;
        } 
    }