暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

如何快速写一个违背双亲委托机制的classloader

肥宅英短 2018-05-30
150

        很多情况下,不得以必须写个classloader来满足需求。例如你一个工程里你想用相同的数据库的多个版本,自己制定了一个jar包目录,没有classloader管理等等。如果是一个遵循java已经规定好的机制的classloader(双亲委托以及加载依赖类的classloader继续加载剩下的类)。直接继承classloader就可以。(一般选择urlclassloder,他帮你实现了不少功能)。但是往往有需求需要加载两个相同的Jar包,例如web应用中的webappclassloader。不同的应用的相同war包都需要被同级的加载,优先级高于was的lib目录。

编写classloader

加入缓存

        classloader的本质是加载字节码到内存,而且相同字节码不能被重复加载,所以我们需要加入缓存,简单选择一个hashmap就可以

  1. Map<String,Class<?>> classMap =new HashMap<String,Class<?>>();

覆写关键方法

    不论你是class.forname,还是loadclass方法的调用,最后都是要调用loadClass的,所以这里一定要重写这个方法,这里就要加入破坏双亲委托机制的逻辑。

  1.    public Class<?> loadClass(String name) throws ClassNotFoundException {

  2.        Class<?> classLoaded = classMap.get(name);

  3.        if (classLoaded != null) {

  4.            return classLoaded;

  5.        }

  6.        Class<?> findClass = null;

  7.        try {

  8.            findClass = findClass(name);

  9.        } catch (Exception e) {

  10.            //还可以从父类查找,这个异常吞掉,如果没有父类会抛出

  11.        }

  12.        if (findClass != null) {

  13.            classMap.put(name, findClass);

  14.            return findClass;

  15.        }

  16.        return super.loadClass(name);

  17.    }

            逻辑很简单,先从缓存找是否已经加载了class,对已经加载的就直接返回,防止重复加载。此处调用了findClass方法,findClass是可以覆写的,这里为了简洁的实现,就不再覆写了。

            拿这样的classloader如果有重复的jar包可能是有问题的。问题就在他的findclass中。findClass找到资源后会通过defineClass(String name, Resource res)来加载类,这里最后会调用getPackage来获取包。下面是找的流程。

  1.    protected Package getPackage(String name) {

  2.        Package pkg;

  3.        synchronized (packages) {

  4.            pkg = packages.get(name);

  5.        }

  6.        if (pkg == null) {

  7.            if (parent != null) {

  8.                pkg = parent.getPackage(name);

  9.            } else {

  10.                pkg = Package.getSystemPackage(name);

  11.            }

  12.            if (pkg != null) {

  13.                synchronized (packages) {

  14.                    Package pkg2 = packages.get(name);

  15.                    if (pkg2 == null) {

  16.                        packages.put(name, pkg);

  17.                    } else {

  18.                        pkg = pkg2;

  19.                    }

  20.                }

  21.            }

  22.        }

  23.        return pkg;

  24.    }

           这里很明显的,先从已经加载的包中查找,如果没有就先从父classloader找,最后又进行了双亲委托机制。所以这个地方也需要覆写。

  1.    protected Package getPackage(String name) {

  2.        return null;

  3.    }

          比较暴力直接返回空。在后面的判断中,如果此处返回null,后续就会重新新建一个对象,然后放入一个缓存结构,还是一个hashmap

  1.    private final HashMap<String, Package> packages = new HashMap<>();

        为空的结果只是更新一下缓存。这里没有对类加载产生问题。

最终结果

  1. public class MyClassloader extends URLClassLoader {

  2.    public MyClassloader(URL[] urls) {

  3.        super(urls);

  4.    }


  5.    Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();


  6.    @Override

  7.    public Class<?> loadClass(String name) throws ClassNotFoundException {

  8.        Class<?> classLoaded = classMap.get(name);

  9.        if (classLoaded != null) {

  10.            return classLoaded;

  11.        }

  12.        Class<?> findClass = null;

  13.        try {

  14.            findClass = findClass(name);

  15.        } catch (Exception e) {

  16.            //还可以从父类查找,这个异常吞掉,如果没有父类会抛出

  17.        }

  18.        if (findClass != null) {

  19.            classMap.put(name, findClass);

  20.            return findClass;

  21.        }

  22.        return super.loadClass(name);

  23.    }


  24.    @Override

  25.    protected Package getPackage(String name) {

  26.        return null;

  27.    }

  28. }

        只要覆写两个方法就好。这样就能比较小的改造成一个破坏双亲委托机制的classloader。


文章转载自肥宅英短,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论