自定义类加载器,打破双亲委派机制

        java默认的类加载器有三个,分别是引导类加载器、扩展类加载器以及应用类加载器。其分别加载的类内容如下所示:

        引导类加载器: 加载jre下lib文件夹中的核心类。

        扩展类加载器: 加载jre下lib文件夹中ext扩展目录中的类。

        应用程序加载器:负责加载classpath路径下的类,即我们自己写的类。

        那么何为双亲委派机制呢,双亲委派机制就是在进行类加载的时候,如果当前类加载器存在父加载器,那么首相从父类加载器中获取/加载。如果父类加载过,则直接返回,若没有则尝试加载,此过程一直重复到引导类加载器。若父类加载器都没有加载过此类,并且都无法加载这个类,那么再回到本加载器进行加载。

        为什么要使用双亲委派机制呢?

        其原因主要有两点:

                1、避免重复加载

                        每次加载类的时候都会在父类中去判断一下是否加载过,若加载过直接返回不再                  加载

                2、java沙箱安全机制

                        避免java的核心类被篡改。比如我们想要加载一个跟java.lang.String同样的类                          就是非法操作

 

        为什么要打破双亲委派机制

        上面有讲到通过双亲委派机制,可以避免类被重复加载,相同包路径下的同名类不会被重复加载。但在某些场景下我们需要有这样的重复加载,比如我们使用tomcat容器部署java的war包时,若一个tomcat下部署多个war包,两个war包使用的第三方库版本不同,若使用默认的双亲委派机制,则同路径下的类只会被加载一次,那么就只会加载一个版本的类,因为版本差异,完全有可能导致某个war包出现异常。通过打破双亲委派机制,分别加载不同版本的类给不同的war包使用,以避免因版本差异产生的异常。

        自定义类加载器

        除了以上三个类加载器,我们还可以自定义类加载器。只需要继承ClassLoader类即可。通过源码可以看到ClassLoader的主要调用loadClass方法与findClass()方法。因此我们自定义类加载器只需要重写这两个方法即可。而findClass我们跟踪代码发现是空实现,其底层是通过类加载器加载文件的字节码然后调用defineClass()方法。下面就来看看一个简单的自定义类加载器实现。

        

package com.zj.study.jvm;

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * Description:
 * Created by zoujian ON 2021/11/15.
 */
public class MyClassLoader2 extends ClassLoader{

    private String classPath;

    public MyClassLoader2(String classPath){
        this.classPath = classPath;
    }



    public static void main(String[] args) throws Exception{
        MyClassLoader2 myClassLoader2 = new MyClassLoader2("d:/tmp");
        Class clazz = myClassLoader2.findClass("com.zj.study.User");
        Object obj = clazz.newInstance();
        Method method = clazz.getMethod("call", null);
        method.invoke(obj,null);

    }

    public byte[] getClassByte(String name) throws Exception{
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
        int lenth = fis.available();
        byte[] classByte = new byte[lenth];
        fis.read(classByte, 0, lenth);

        return classByte;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException{
        try{
            byte[] classByte = getClassByte(name);
            return defineClass(name, classByte, 0, classByte.length );
        }catch (Exception ee){
            ee.printStackTrace();
        }
        return null;
    }


}

        上面的代码块中主要实现了两个方法,getClassByte方法主要是通过类路径把类加载成字节流。findClass方法通过加载的类字节流通过defineClass方法加载类。main方法中通过加载好的类利用反射调用了提前编译好放在d盘下的user类的call方法。至此一个简单地自定义类加载器就实现好了。

        下面看看打破双亲委派机制的自定义加载器,再看实现之前,先看看ClassLoader的loadClass方法。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

        以上代码 截取自jdk1.8,我们可以看到里面对parent进行了判断,如果不为空则调用父类的loadclass()方法。否则就调用引导类加载器进行加载。这里的判断就是我们打破双亲委派机制的突破口。对于我们不需要进行双亲委派的类,我们可以不委托父类加载,而自行加载。废话不说,直接上demo:

        

package com.zj.study.jvm;

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * Description:
 * Created by zoujian ON 2021/11/12.
 */
public class MyClassLoader1 extends ClassLoader{


    private String classPath;

    public MyClassLoader1(String classPath){
        this.classPath = classPath;
    }

    private byte[] loadByte(String name) throws Exception{
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
        int length = fis.available();
        byte[] classData = new byte[length];
        fis.read(classData);
        fis.close();
        return classData;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException{
        try{
            byte[] classData = loadByte(name);
            return defineClass(name, classData, 0, classData.length);
        }catch (Exception ee){
            ee.printStackTrace();
        }
        return null;
    }

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if(c == null){
                long t1 = System.nanoTime();
                if(!name.startsWith("com.zj.study")){
                   c = this.getParent().loadClass(name) ;
                }else{
                    c = this.findClass(name);
                }
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }

            if (resolve) {
                resolveClass(c);
            }

            return c;
        }
    }




    public static void main(String[] args) throws Exception{
        MyClassLoader1 classLoader = new MyClassLoader1("d:/tmp");
        Class clazz = classLoader.loadClass("com.zj.study.User");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("call", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }


}

        该代码片段中,main、loadByte以及findClass都与自定义加载器一样。区别就是重新了父类的loadClass方法。在进行父加载器判断的时候,我们做了一些逻辑变更,如果是com.zj.study这个包下的类,那么不走双亲委派机制,而其它的类依然走双亲委派机制。注意:由于每个java类都有一个基础父类Object,而这些类只能通过双亲委派机制加载。

        以上就是打破双亲委派机制的全部内容了。如果发现有不对的地方,欢迎指正!

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值