java自定义类加载器如何实现类隔离
作者:快乐崇拜234
网上java自定义类加载器很多容易找到,但是都是加载的单个类,如果被加载的类,有引用了其他类怎么办呢?接下来看一下如何来处理这种情况
有时候一个项目中可能会引用不同版本的第三方依赖,比如笔者在升级hbase系统时,代理层就同时用到了1.X和2.X版本的hbase-client的jar包。
当时是使用的阿里的SOFAArk来实现的。
它的本质就是是哟个类加载来实现的,接下来就通过一个小例子来通过自定义类加载器实现类隔离。
下面实例实现了在同一个应用里加载了两个类名报名完全相同的Hello 和Dog类:
自定义类加载器
准备
准备工作,在D盘的a,b两个目录下准备Hello.class 和 Dog.class 两个文件,可以使用javac编译。
//a文件夹: public class Hello { private String name; public Hello(String name) { this.name = name; } public void sayHello() { Dog d =new Dog(); d.hi(); System.out.println("a hello " + name + " "+ this.getClass().getClassLoader()); } } public class Dog { public void hi() { System.out.println("a hi ... "+ this.getClass().getClassLoader()); } } //b文件夹 public class Hello { private String name; public Hello(String name) { this.name = name; } public void sayHello() { Dog d =new Dog(); d.hi(); System.out.println("b hello " + name + " "+ this.getClass().getClassLoader()); } } public class Dog { public void hi() { System.out.println("b hi ... "+ this.getClass().getClassLoader()); } }
通过URLClassLoader来实现【推荐】
类加载器会主动加载被引用的类。比如类A依赖引用了类B ,则只需要主动加载类A即可,类B会自动加载,而且加载类B的类加载器与类A相同。
/** * URLClassLoader * 推荐此方法 */ @Test public void test0() { try { System.out.println( this.getClass().getClassLoader()); URLClassLoader diskLoader = new URLClassLoader(new URL[]{new URL("file:/D:/liubenlong/a/")});//最后面的斜杠需要添加 URLClassLoader diskLoader1 = new URLClassLoader(new URL[]{new URL("file:/D:/liubenlong/b/")}); //加载class文件 Class clz = diskLoader.loadClass("Hello"); Constructor constructor = clz.getConstructor(String.class); Object obj = constructor.newInstance("tom"); /** * 类Hello引用了类Dog,类加载器会主动加载被引用的类。 * 注意一般是我们使用 URLClassLoader 实现自定义的类加载器。如果使用classLoader,则需要重写findClass方法来实现类字节码的加载 */ Method method = clz.getMethod("sayHello", null); //通过反射调用Test类的say方法 method.invoke(obj, null); Class clz1 = diskLoader1.loadClass("Hello"); Constructor constructor1 = clz1.getConstructor(String.class); Object obj1 = constructor1.newInstance("cat"); Method method1 = clz1.getMethod("sayHello", null); //通过反射调用Test类的say方法 method1.invoke(obj1, null); } catch (Exception e) { e.printStackTrace(); } }
输出
jdk.internal.loader.ClassLoaders$AppClassLoader@1f89ab83
a hi ... java.net.URLClassLoader@3891771e
a hello tom java.net.URLClassLoader@3891771e
b hi ... java.net.URLClassLoader@78ac1102
b hello cat java.net.URLClassLoader@78ac1102
通过继承ClassLoader实现
网上说可以自定义类加载器实现,接下来我们看一下(jdk11直接忽略,因为编译不通过,下面是在jdk8版本测试)。
package 自定义类加载器; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; /** * 自定义ClassLoader */ public class MyClassLoader extends ClassLoader { protected Class<?> findClass(String path, String name) { try { FileInputStream in = new FileInputStream(path + name + ".class"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int len = -1; while ((len = in.read(buf)) != -1) { baos.write(buf, 0, len); } in.close(); byte[] classBytes = baos.toByteArray(); return defineClass(name, classBytes, 0, classBytes.length); } catch (Exception e) { e.printStackTrace(); } return null; } }
编写测试类
package 自定义类加载器; import org.junit.Test; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class ClassLoaderTest { /** * 集成ClassLoader,重写findClass实现自定义类加载器 */ @Test public void test1() { try { MyClassLoader diskLoader = new MyClassLoader(); MyClassLoader diskLoader1 = new MyClassLoader(); //依赖的类需要提前加载,不是应该自动加载的吗? diskLoader.findClass("D:\\liubenlong\\a\\", "Dog"); diskLoader1.findClass("D:\\liubenlong\\b\\", "Dog"); //加载class文件 Class clz = diskLoader.findClass("D:\\liubenlong\\a\\", "Hello"); Constructor constructor = clz.getConstructor(String.class); Object obj = constructor.newInstance("tom"); Method method = clz.getMethod("sayHello", null); //通过反射调用Test类的say方法 method.invoke(obj, null); Class clz1 = diskLoader1.findClass("D:\\liubenlong\\b\\", "Hello"); Constructor constructor1 = clz1.getConstructor(String.class); Object obj1 = constructor1.newInstance("cat"); Method method1 = clz1.getMethod("sayHello", null); //通过反射调用Test类的say方法 method1.invoke(obj1, null); } catch (Exception e) { e.printStackTrace(); } } }
运行结果:
a hi ...
a hello tom
b hi ...
b hello cat
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。