美文网首页
Java自定义类加载器

Java自定义类加载器

作者: slowwalkerlcr | 来源:发表于2019-12-18 22:16 被阅读0次

    一、ClassLoader作用

    Java程序在运行的时候,JVM通过类加载机制(ClassLoader)把class文件加载到内存中,只有class文件被载入内存,才能被其他class引用,使程序正确运行起来

    二、ClassLoader的分类

    Java中的ClassLoader有三种:Bootstrap ClassLoader 、Extension ClassLoader、App ClassLoader。

    1.Bootstrap ClassLoader

    由C++写的,由JVM启动.

    启动类加载器,负责加载java基础类,对应的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

    2.Extension ClassLoader

    Java类,继承自URLClassLoader 扩展类加载器,

    对应的文件是 %JRE_HOME/lib/ext 目录下的jar和class等

    3.App ClassLoader

    Java类,继承自URLClassLoader 系统类加载器,

    对应的文件是应用程序classpath目录下的所有jar和class等

    三、ClassLoader的加载机制

    Java的加载机制是双亲委派机制来加载类,

    为什么要使用这种方式?这个是为了保证 如果加载的类是一个系统类,那么会优先由Bootstrap ClassLoader 、Extension ClassLoader先去加载,而不是使用我们自定义的ClassLoader去加载,保证系统的安全!

    这三种类加载器存在父子关系,App ClassLoader的父类加载器是Extension ClassLoader,Extension ClassLoader的父类加载器是Bootstrap ClassLoader,要注意的一点是,这里的父子并不是继承关系.

    public class ClassLoaderParentMain {
     
        public static void main(String[] args){
            ClassLoader ClassLoader1 = ClassLoaderParentMain.class.getClassLoader();
            ClassLoader ClassLoader2 = ClassLoader1.getParent();
            ClassLoader ClassLoader3 = ClassLoader2.getParent();
     
            System.out.println(ClassLoader1);
            System.out.println(ClassLoader2);
            System.out.println(ClassLoader3);
        }
    
    }
    

    输出的结果为:

    sun.misc.Launcher$AppClassLoader@14dad5dc
    sun.misc.Launcher$ExtClassLoader@27d6c5e0
    null
    Process finished with exit code 0
    

    当这三者中的某个ClassLoader要加载一个类时,会先委托它的父类加载器尝试加载,一直往上,如果最顶级的父类加载器没有找到该类,那么委托者则亲自到特定的地方加载

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)) { // 首先,检测是否已经加载 
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) { //父加载器不为空则调用父加载器的loadClass 
                            c = parent.loadClass(name, false);
                        } else { //父加载器为空则调用Bootstrap Classloader 
                            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(); //父加载器没有找到,则调用findclass 
                        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() 
                    resolveClass(c);
                }
                return c;
            }
        }
    

    先加载Extension ClassLoader ,如果没有加载到,那么使用Bootstrap ClassLoader去加载,如果都没有,那么使用App ClassLoader去加载。如果没找到,那么就抛出异常ClassNotFoundException.


    image.png

    只有被同一个类加载器实例加载并且文件名相同的class文件才被认为是同一个class.

    四、自定义ClassLoader

    1.为什么要自定义ClassLoader

    因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader.

    而且我们可以根据自己的需求,对class文件进行加密和解密。

    2.如何自定义ClassLoader

    • 2.1
      新建一个类继承自java.lang.ClassLoader,重写它的findClass方法。
    • 2.2
      将class字节码数组转换为Class类的实例
    • 2.3
      调用loadClass方法即可

    3.下面的代码就是自定义Class的例子

    先看代码结构图


    image.png
    • 1.删除LoadBean.java 并将LoadBean.class移到额外目录,我的是E:\myLib\LoadBean.class,删除之后的代码结果如下:
      image.png
      LoadBean.java的代码如下:
    package classloader;
    
    public class LoadBean {
    
        public void sayHello(String name){
            System.out.println("Hello :"+name);
        }
    }
    
    • 2.编写MyClassLoader继承ClassLoader,并重写findClass方法
    package classloader;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    public class MyClassLoader  extends ClassLoader  {
        //指定路径
        private String path ;
        public MyClassLoader(String classPath){
            path=classPath;
        }
    
        /**
         * 重写findClass方法
         * @param name 是我们这个类的全路径
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            Class log = null;
            // 获取该class文件字节码数组
            byte[] classData = getData();
    
            if (classData != null) {
                // 将class的字节码数组转换成Class类的实例
                log = defineClass(name, classData, 0, classData.length);
            }
            return log;
        }
    
        /**
         * 将class文件转化为字节码数组
         * @return
         */
        private byte[] getData() {
            File file = new File(path);
            if (file.exists()){
                FileInputStream
                        in = null;
                ByteArrayOutputStream out = null;
                try {
                    in = new FileInputStream(file);
                    out = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int size = 0;
                    while ((size = in.read(buffer)) != -1) {
                        out.write(buffer, 0, size);
                    }
    
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        in.close();
                    } catch (IOException e) {
    
                        e.printStackTrace();
                    }
                }
                return out.toByteArray();
            }else{
                return null;
            }
        }
    }
    
    

    可以在getData()方法里面做很多事情 ,比如加密解密之类的 都是可以的

      1. 在main函数入口 写配置参数
    package classloader;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class MainTest {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
            //这个是类class的真正路径(也就是我们需要加载的class文件)
            String classPath = "E:\\myLib\\LoadBean.class";
    
            MyClassLoader myClassLoader = new MyClassLoader(classPath);
            //类的全称
            String packageNamePath = "classloader.LoadBean";
    
            //加载Log这个class文件
            Class<?> loadbean = myClassLoader.loadClass(packageNamePath);
    
            System.out.println("类加载器是:" + Log.getClassLoader());
    
            //利用反射获取LoadBean的sayHello方法
            Method method = loadbean.getDeclaredMethod("sayHello", String.class);
            Object object = loadbean.newInstance();
            String name = "admin";
            method.invoke(object, name);
    
        }
    }
    
    

    -4 运行MainTest.java的main函数,运行结果如下

    image.png

    相关文章

      网友评论

          本文标题:Java自定义类加载器

          本文链接:https://www.haomeiwen.com/subject/xyhsnctx.html