【问题标题】:How to custom a classloader如何自定义类加载器
【发布时间】:2014-05-22 10:06:57
【问题描述】:

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

我知道最好使用仪器 API 来实现相同的目的。但现在我担心的是我需要了解我失败的原因。

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

我的问题是,我可以加载我的替代类,一切似乎都很好......但是,我正在使用 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

【问题讨论】:

    标签: classloader


    【解决方案1】:

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

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

    所以下面是源代码:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-24
      • 1970-01-01
      • 2017-11-28
      • 1970-01-01
      相关资源
      最近更新 更多