美文网首页
自定义ClassLoader测试

自定义ClassLoader测试

作者: _palm | 来源:发表于2018-01-09 20:51 被阅读27次

    自定义ClassLoader直接继承ClassLoader抽象类, 覆写

    protected Class<?> findClass(String name) throws ClassNotFoundException
    

    方法, 实现自定义加载类. 在 #ClassLoader抽象类中, 给出了 class实例化方法:

    protected final Class<?> defineClass(byte[] b, int off, int len)
    

    这个方法目的是将 class字节流转为 class instance

    /**
    * Converts an array of bytes into an instance of class <tt>Class</tt>.
    * Before the <tt>Class</tt> can be used it must be resolved.
    *
    * <p> This method assigns a default {@link java.security.ProtectionDomain
    * <tt>ProtectionDomain</tt>} to the newly defined class.  The
    * <tt>ProtectionDomain</tt> is effectively granted the same set of
    * permissions returned when {@link
    * java.security.Policy#getPermissions(java.security.CodeSource)
    * <tt>Policy.getPolicy().getPermissions(new CodeSource(null, null))</tt>}
    * is invoked.  The default domain is created on the first invocation of
    * {@link #defineClass(String, byte[], int, int) <tt>defineClass</tt>},
    * and re-used on subsequent invocations.
    *
    * <p> To assign a specific <tt>ProtectionDomain</tt> to the class, use
    * the {@link #defineClass(String, byte[], int, int,
    * java.security.ProtectionDomain) <tt>defineClass</tt>} method that takes a
    * <tt>ProtectionDomain</tt> as one of its arguments.  </p>
    *
    * @param  name
    *        The expected <a href="#name">binary name</a> of the class, or
    *        <tt>null</tt> if not known
    *
    * @param  b
    *        The bytes that make up the class data.  The bytes in positions
    *        <tt>off</tt> through <tt>off+len-1</tt> should have the format
    *        of a valid class file as defined by
    *        <cite>The Java&trade; Virtual Machine Specification</cite>.
    *
    * @param  off
    *        The start offset in <tt>b</tt> of the class data
    *
    * @param  len
    *        The length of the class data
    *
    * @return  The <tt>Class</tt> object that was created from the specified
    *          class data.
    *
    * @throws  ClassFormatError
    *          If the data did not contain a valid class
    *
    * @throws  IndexOutOfBoundsException
    *          If either <tt>off</tt> or <tt>len</tt> is negative, or if
    *          <tt>off+len</tt> is greater than <tt>b.length</tt>.
    *
    * @throws  SecurityException
    *          If an attempt is made to add this class to a package that
    *          contains classes that were signed by a different set of
    *          certificates than this class (which is unsigned), or if
    *          <tt>name</tt> begins with "<tt>java.</tt>".
    *
    * @see  #loadClass(String, boolean)
    * @see  #resolveClass(Class)
    * @see  java.security.CodeSource
    * @see  java.security.SecureClassLoader
    *
    * @since  1.1
    */
    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }
    

    这里是从本地文件系统中通过文件的方式读取*.class文件, 将获取到的全部class文件字节流传递给 #defineClass方法, 实例化. 这之后我们就可以使用这个类对象了.

    public class MyClassLoader extends ClassLoader {
    
        private String dir ;
    
        public MyClassLoader(String dir) {
            //
            this.dir = dir ;
        }
    
        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException {
            //
    
            if (StringUtils.isBlank(name))
                throw new ClassNotFoundException() ;
    
            String path = this.getPath(name) ;
    
            byte[] ret = this.getClassData(path) ;
            if (ret == null)
                throw new ClassNotFoundException("读取".concat(path).concat("失败.")) ;
    
            return defineClass(name, ret,0, ret.length) ;
        }
    
        private byte[] getClassData(String path) {
            System.out.println("Path: - ".concat(path));
            try {
                InputStream inputStream = new FileInputStream(new File(path)) ;
                ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
                byte[] buffer = new byte[1024] ;
                while (inputStream.read(buffer) != -1) {
    //                baos.write(buffer);
                    baos.write(buffer, 0, buffer.length);
                }
    
                return baos.toByteArray() ;
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return null ;
        }
    
        private String getPath(String className) {
    
            return this.dir.concat(className.replace(".", File.separator)).concat(".class") ;
        }
    }
    
    

    随便定一个使用自定义类加载器加载的类:

    public class ABean {
    
        public ABean() {
            System.out.println("初始化ABean.");
        }
    
        @Override
        public String toString() {
            return "hello, myClassLoader...." ;
        }
    }
    

    测试类:

    public static void main(String[] args) {
            //JVM-classLoader test
            String dir = "/test/target/classess/test/jvm/",
            clsName = "test.jvm.ABean" ;
    
            MyClassLoader myClassLoader = new MyClassLoader(dir) ;
            try {
                Class cls = myClassLoader.loadClass(clsName) ;
                System.out.println(cls.newInstance().toString()) ;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    //输出

    初始化ABean.
    hello, myClassLoader....
    

    注意
    需要注意的是

    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    

    这个方法第一个参数是指: 类全名[Full-className] 否则会有异常: java.lang.NoClassDefFoundError 这里我根据方法文档, 大概是因为:

    * @throws  NoClassDefFoundError
    *          If <tt>name</tt> is not equal to the <a href="#name">binary
    *          name</a> of the class specified by <tt>b</tt>
    

    在读取class文件得到的byteStream名称为 test/jvm/ABean/class 但是传递给#defineClass方法的名称却是 ABean.

    以上代码测试环境为

    Linux arch 4.14.10-1-ARCH x86_64 GNU/Linux
    
    触发类加载有以下三种方式:
    • 命令行启动应用时候由JVM初始化加载
    • 通过Class.forName()方法动态加载
    • 通过ClassLoader.loadClass()方法动态加载
    类装载器把一个类装入JVM中,要经过以下步骤:
     - 装载:查找和导入Class文件;
     - 链接:把类的二进制数据合并到JRE中; 这个过程包括三个步骤:
          - 校验:检查载入Class文件数据的正确性;
          - 准备:给类的静态变量分配存储空间;
          - 解析:将符号引用转成直接引用;
     - 初始化:对类的静态变量,静态代码块执行初始化操作

    相关文章

      网友评论

          本文标题:自定义ClassLoader测试

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