(四)手写打破双亲委派 (类加载机制 第四篇)

我们知道,在加载类的时候,会一级一级向上委托,判断是否已经加载,从自定义类加载器-》应用类加载器-》扩展类加载器-》启动类加载器,如果到最后都没有加载这个类,则回去加载自己的类。

双亲委托有个弊端:

不能向下委派,不能不委派

怎么打破双亲委派机制:(也就是能向下委派和不委派)

自定义类加载器(不委派)

spi机制(向下委派)

用spi来打破双亲委派

SPI是什么?

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。

SPI使用的场景

API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。

SPI (Service Provider Interface)调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件。

以Driver接口为例,DriverManager通过Bootstrap ClassLoader加载进来的,而com.mysql.jdbc.Driver是通过Application ClassLoader加载进来的。由于双亲委派模型,父加载器是拿不到通过子加载器加载的类的。这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派。

手写一个SPI案例

定义一个接口 HelloSpi

package com.shendu.spi;

public interface HelloSpi {

    public String getName();
}

定义两个实现类分别为HelloSpiImpl01HelloSpiImpl02


public class HelloSpiImpl01 implements HelloSpi {
    @Override
    public String getName() {
        return "helloSpiImp01";
    }
}

public class HelloSpiImpl02 implements HelloSpi {
    @Override
    public String getName() {
        return "helloSpiImp02";
    }
}

META-INF\services\ 定义接口的全路径文件为com.shendu.spi.HelloSpi并且在文件中写上实现类的全路径

com.shendu.spi.HelloSpiImpl01
com.shendu.spi.HelloSpiImpl02

定义测试类为

public class SPITest {
    public static void main(String[] args) {
        ServiceLoader<HelloSpi> helloSpis = ServiceLoader.load(HelloSpi.class);

        for (HelloSpi hello :
                helloSpis) {
            System.out.println(hello.getName());
        }
    }
}

打印输出:

helloSpiImp01
helloSpiImp02

Process finished with exit code 0

这里只是一个小小的案例,体会一下SPI的魅力,它可以让父类委托子类去加载不在自己目录下的类,这样就打破的常规的双亲委派机制。

著名的dubbo就是基于SPI原生做了增强。 以后 笔者会出一篇源码级别的SPI讲解,请大家敬请期待。

重写loadClass()方法来打破双亲委派

我们继续回顾一下loader方法的源码

//双亲委派模型的工作过程源码
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } 
        catch (ClassNotFoundException e) {
            //父类加载器无法完成类加载请求
        }
 
        if (c == null) {
            //子加载器进行类加载 
            c = findClass(name);
        }
    } 
    if (resolve) {
        //判断是否需要链接过程,参数传入
        resolveClass(c);
    }
    return c;
}

这个方法是双亲委派的核心方法 我们只需要覆写这个方法,加入我们自己的加载逻辑就可以打破双亲委托。

重写loadClass方法的案例

1 定义自定义加载器

package com.shendu;

import sun.misc.PerfCounter;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

public class MyClassLoader02 extends ClassLoader {

    private String classPath;

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

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File(classPath);
        try {
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }


    private byte[] getClassBytes(File file) throws IOException {

        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);

        while (true) {
            int i = fc.read(by);
            if (i == 0 || i == -1)
                break;
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return baos.toByteArray();
    }


    @Override
    public 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 {
                    //重点重点重点:  这个就是加入了我自己的逻辑,只要在com.shendu下面的类,都是通过我自定义加载器进行加载
                    if (name.startsWith("com.shendu")) {
                        c = findClass(name);
                    } else {
                        c = this.getParent().loadClass(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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

定义 user类

package com.shendu;
public class User {

    private String name ;

    private Integer age ;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

定义测试类

   
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //实例化自定义加载器
        MyClassLoader02 myClassLoader = new          MyClassLoader02("D:\\tcl_ouyang\\demo_coding\\jvmclassload\\target\\classes\\com\\shendu\\User.class");
		//加载user类
        Class<?> user = myClassLoader.loadClass("com.shendu.User");

    	//通过反射对user属性进行赋值
        Method setName = user.getMethod("setName", String.class);
        Method setAge = user.getMethod("setAge", Integer.class);
        Object o = user.newInstance();
        setName.invoke(o, "shendu");
        setAge.invoke(o, 18);
		
        System.out.println(o);
        System.out.println(o.getClass().getClassLoader());

    }

打印如下:

User{name='shendu', age=18}
com.shendu.MyClassLoader02@4554617c

从结果来看,确实是用了我们自定义的类加载器进行加载。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值