URLClassLoader loads the class with the same name

foreword

           As the most commonly used and widely used class loader, URLClassLoader plays a pivotal role in the java world. As a coder, it is necessary to have a certain understanding of its functions and uses in order to roam freely in the java world. As the core element in java, the class loader can always find a solution when encountering some intractable diseases. I believe that friends often encounter the problem of jar package conflicts. In this case, the general solution is to remove other versions of jar packages and finally keep only one version. So can multiple versions coexist? The answer is yes. The problem still lies in the class loader. Due to the existence of the parent delegation mechanism, for jar packages containing the same class name, the class class instance will only select the jar package that is loaded first, and the latter will be ignored. If you want to achieve the purpose of coexistence of multiple versions of jar packages, you can only let these jar packages be loaded by different class loaders.

1. System class loader AppClassLoader and URLClassLoader

       Generally, the system class loader is AppClassLoader, of course, you can also pass the jvm option Djava.system.class.loader. The AppClassLoader class inherits from the URLClassLoader class. The path loaded by URLClassLoader includes the file directory, jar package, and network path. In most cases, we use the first two.

       Generally, the loading of the path is initialized by specifying the URL array in the class constructor of URLClassLoader. Of course, the way of reflection can also be used to dynamically load the path. There are also two reflection methods, one is to load through the addURL method of the member variable ucp of URLClassLoader, and the other is to load directly through the addURL of the URLClassLoader class itself. AppClassLoader can also be loaded through the appendToClassPathForInstrumentation(String) method

Of course, the essence is to call the addURL of URLClassLoader to load.

2. How to load the class with the same name

       1. Create a subclass of the URLClassLoader class, rewrite the loadClass method, and maintain a variable of the collection class.

When the class name to be loaded already exists in the collection variable, the step of loading from the parent loader is skipped.

             Because class loaders all inherit from ClassLoader, the following is the implementation of its loadClass method

      

      When loading fails in the parent loader, it will go to the findClass method, so we can remove the parent loader loading process in the overloaded loadClass method.

The complete code of the MyURLClassLoader class is as follows

package com.suntown.loader;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public abstract class MyURLClassLoader extends URLClassLoader {
    public MyURLClassLoader(URL[] urls, ClassLoader classLoader) {
        super(urls, classLoader);

        filter(urls);
    }

    public MyURLClassLoader(URL[] urls) {
        super(urls);

        filter(urls);
    }

    public MyURLClassLoader(URL[] urls, ClassLoader classLoader, URLStreamHandlerFactory urlStreamHandlerFactory) {
        super(urls, classLoader, urlStreamHandlerFactory);

        filter(urls);
    }

    protected void addURL(URL url) {
        super.addURL(url);
        filter(url);
    }

    private Set<String> classNameSet = new HashSet<String>();

    public abstract boolean isOverrideURL(URL url);

    private void filter(URL url){
        if(isOverrideURL(url)){
            try{
                File f = new File(url.getPath());
                if(f.exists()){
                    JarFile jarFile = new JarFile(f.getAbsolutePath());
                    Enumeration<JarEntry> et = jarFile.entries();
                    while (et.hasMoreElements()) {
                        JarEntry ele = et.nextElement();
                        if (ele.getName().endsWith(".class")) {
                            String name = ele.getName().substring(0, ele.getName().length() - 6).replace("/", ".");
                            classNameSet.add(name);
                        }
                    }
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }

    private void filter(URL[] urls){
        for(URL url : urls){
            filter(url);
        }
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        if(classNameSet.contains(name)){
            return super.findClass(name);
        }else{
            return super.loadClass(name);
        }
    }
}

3. Operation verification

Use AppClassLoader, URLClassLoader and MyURLClassLoader to load a class in the same jar package respectively

And print the ClassLoader to which the class belongs respectively.

The test code is as follows

        

package loader;

import sun.misc.URLClassPath;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class LoaderTest{
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        URLClassLoader userloader = new URLClassLoader(new URL[]{});
        URLClassLoader myloader = new MyURLClassLoader(new URL[]{}) {
            @Override
            public boolean isOverrideURL(URL url) {
                return true;
            }
        };

        Method mdAddURL = URLClassLoader.class.getDeclaredMethod("addURL",java.net.URL.class);
        mdAddURL.setAccessible(true);

        URL url = new File("f:\\git\\test\\ojdbc6.jar").toURI().toURL();
        mdAddURL.invoke(sysloader,new Object[]{url});
        mdAddURL.invoke(userloader,new Object[]{url});
        mdAddURL.invoke(myloader,new Object[]{url});

        Class c1 = sysloader.loadClass("oracle.sql.CHAR");
        System.out.println(c1.getClassLoader());

        Class c2 = userloader.loadClass("oracle.sql.CHAR");
        System.out.println(c2.getClassLoader());

        Class c3 = myloader.loadClass("oracle.sql.CHAR");
        System.out.println(c3.getClassLoader());
    }
}

The result of the operation is as follows

 

 

Guess you like

Origin blog.csdn.net/weixin_38526093/article/details/128986854