(四)手写打破双亲委派 (类加载机制 第四篇)
作者:互联网
我们知道,在加载类的时候,会一级一级向上委托,判断是否已经加载,从自定义类加载器-》应用类加载器-》扩展类加载器-》启动类加载器,如果到最后都没有加载这个类,则回去加载自己的类。
双亲委托有个弊端:
不能向下委派,不能不委派
怎么打破双亲委派机制:(也就是能向下委派和不委派)
自定义类加载器(不委派)
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();
}
定义两个实现类分别为HelloSpiImpl01 和 HelloSpiImpl02
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
从结果来看,确实是用了我们自定义的类加载器进行加载。
标签:name,class,String,shendu,第四篇,手写,public,加载 来源: https://blog.csdn.net/weixin_30720461/article/details/121142749