Java类加载器(用户自定义类加载器实现)

java类加载器主要分为如下几种:

  • jvm提供的类加载器
    1. 根类加载器:底层实现,主要加载java核心类库(如:java.lang.*)
    2. 扩展类加载器:使用java代码实现,主要加载如:jre/lib/ext/ 下的扩展类库。(父类加载器为根类加载器)
    3. 系统类加载器(应用类加载器):使用java代码实现,加载classpath目录下的类。(父类加载器为扩展类加载器)
  • 用户自定义类加载器:去继承ClassLoader类实现自定义类加载器。 类加载器负责将java字节码文件加载到虚拟机内存中也就是类的生命周期的装载过程。(如下为类的生命周期)

类的生命周期图

下面是用户自定义类加载器的实现过程及代码:

实现一个用户自定义类加载器需要去继承ClassLoader类并重写findClass方法,代码如下

package com.space;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;public class MyClassLoader extends ClassLoader {

    private String path="/home/luciel/";    //默认加载路径

    private String name;                    //类加载器名称

    private final String  filetype=".class"; //文件类型


    public MyClassLoader(String name) {        // TODO Auto-generated constructor stub
        super();        this.name=name;
    }    public MyClassLoader(ClassLoader parent,String name){        super(parent);        this.name=name;
    }    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {        // TODO Auto-generated method stub
        byte[] b=loadClassData(name);        return defineClass(name, b, 0, b.length);
    }    private byte[] loadClassData(String name) {        byte[] data=null;
        InputStream in=null;
        name=name.replace('.', '/');
        ByteArrayOutputStream out=new ByteArrayOutputStream();        try {
            in=new FileInputStream(new File(path+name+filetype));            int len=0;            while(-1!=(len=in.read())){
                out.write(len);
            }
            data=out.toByteArray();
        } catch (FileNotFoundException e) {            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{            try {
                in.close();
                out.close();
            } catch (IOException e) {                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }        return data;
    }    public String getPath() {        return path;
    }    public void setPath(String path) {        this.path = path;
    }    @Override
    public String toString() {        // TODO Auto-generated method stub
        return this.name;
    }

}
public MyClassLoader(String name) {        // TODO Auto-generated constructor stub
        super();        this.name=name;
 }

这个构造方法中去调用ClassLoader无参构造方法从ClassLoader源码中可以得出:调用此构造方法会让系统类加载器成为该类加载器的父加载器。(注意:此处父类加载器不一定是继承关系,只是一种包装关系)。

在重写findClass方法时参照java API中实现一个网络类加载器的例子,API例子如下:

class NetworkClassLoader extends ClassLoader {
    String host;    int port;    public Class findClass(String name) {     byte[] b = loadClassData(name);     return defineClass(name, b, 0, b.length);
    }    private byte[] loadClassData(String name) {     // load the class data from the connection
      . . .
    }
}

下面是测试类以及main方法类的测试代码:

1 package com.space;2 3 public class Color {4     public Color() {5         // TODO Auto-generated constructor stub6         System.out.println("Color is loaded by "+this.getClass().getClassLoader());7         8     }9 }
 1 package com.space; 2 
 3 public class Red extends Color{
 4     
 5     public Red() { 6         // TODO Auto-generated constructor stub
 7         System.out.println("Red is loaded by "+this.getClass().getClassLoader()); 8         
 9     }10 11 }
 1 package com.space; 2 
 3 public class TestMyClassLoader {
 4     
 5     public static void main(String[] args) throws Exception { 6         
 7         MyClassLoader loader1=new MyClassLoader("loader1"); 8         
 9         loader1.setPath("/home/luciel/test1/");10         11         MyClassLoader loader2=new MyClassLoader(loader1, "loader2");12         13         loader2.setPath("/home/luciel/test2/");14         15         MyClassLoader loader3=new MyClassLoader(null, "loader3");16         17         loader3.setPath("/home/luciel/test3/");18         19         loadClassByMyClassLoader("com.space.Red",loader2);20         21         loadClassByMyClassLoader("com.space.Red",loader3);22     }23     24     private static void loadClassByMyClassLoader(String name,ClassLoader loader) throws Exception{25         26         Class<?> c=loader.loadClass(name);27         Object obj=c.newInstance();28     }29 30 }

按照main方法中给三个类加载器传入的路径创建相应的环境并将com.space.Red、com.space.Color的class类拷贝到

/home/luciel/test1/和/home/luciel/test2/以及/home/luciel/test3/目录中去将com.space.TestMyClassLoader类和com.space.MyClassLoader拷贝

/home/luciel/main/ 中去并在该目录下执行

最终运行结果如下显示:

[root@localhost main]# java com.space.TestMyClassLoaderColor is loaded by loader1
Red is loaded by loader1
Color is loaded by loader3
Red is loaded by loader3

loadClassByMyClassLoader(“com.space.Red”,loader2);如测试代码中 我们调用了loader2去加载Red类但Red类却打印出由loader1加载,这是由于类加载器秉承的是父委托机制loader2在创建时包装了loader1为其父类加载器,而loader1创建时由于调用的是没有传入父类加载器的构造方法,因此它的父加载器为系统类加载器。因此几个加载器的关系如下:

由于loader1的路径下有Red类class文件所以loader1可以加载,因此载Red类构造方法中打印的类加载器为loader1.

我门看似只去加载了Red类但运行结果却将Color父类加载了,而且Color类的加载在Red类之前,那是由于Red类 主动使用 了Color类,因此在初始化Red类之前必须先初始化Color类,要初始化就必须先加载,所以先打印出了Color类的输出信息。(关于类的主动使用大家如果不清楚可以查查,一共有6种)

loadClassByMyClassLoader(“com.space.Red”,loader3);再分析第二个测试代码,由于loader3创建时传入的父类加载器为 null,看下面关于ClassLoader类源码部分代码或查看java API

/**
     * Returns the parent class loader for delegation. Some implementations may
     * use <tt>null</tt> to represent the bootstrap class loader. This method
     * will return <tt>null</tt> in such implementations if this class loader's
     * parent is the bootstrap class loader.
     *
     * <p> If a security manager is present, and the invoker's class loader is
     * not <tt>null</tt> and is not an ancestor of this class loader, then this
     * method invokes the security manager's {@link
     * SecurityManager#checkPermission(java.security.Permission)
     * <tt>checkPermission</tt>} method with a {@link
     * RuntimePermission#RuntimePermission(String)
     * <tt>RuntimePermission("getClassLoader")</tt>} permission to verify
     * access to the parent class loader is permitted.  If not, a
     * <tt>SecurityException</tt> will be thrown.  </p>
     *
     * @return  The parent <tt>ClassLoader</tt>
     *
     * @throws  SecurityException
     *          If a security manager exists and its <tt>checkPermission</tt>
     *          method doesn't allow access to this class loader's parent class
     *          loader.
     *
     * @since  1.2
     */
    @CallerSensitive
    public final ClassLoader getParent() {        if (parent == null)            return null;
        SecurityManager sm = System.getSecurityManager();        if (sm != null) {
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }        return parent;
    }

getParent方法的说明如下部分文字:

Returns the parent class loader for delegation. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class loader’s * parent is the bootstrap class loader.

意思是说我们可以使用null表示 the bootstrap class loader(根类加载器)那么loader3的父类加载器就是 根类加载器 ,而根类加载器只会去加载那些系统核心类库,显然我们的Red和Color类不属于此范围,而就只能让loader3加载,loader3的加载路径下有这两个类对应的字节码可以成功加载,所以大引出Red和Color类的类加载器为loader3

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2016-11-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Alice

demo3同通讯录展示的方式分组排序

按A-Z顺序分组展示 有些项目中会需要这样的需求。形成类似于上述的界面。类似于通讯录里边的排序。实现的效果:所有的数据展示的时候,能够分组展示。顺序按照A-Z的...

2299
来自专栏coder修行路

Go实现短url项目

首先说一下这种业务的应用场景: 把一个长url转换为一个短url网址 主要用于微博,二维码,等有字数限制的场景 主要实现的功能分析: 把长url的地址转换为短u...

4505
来自专栏三流程序员的挣扎

Flutter 学习记3 - Widget 框架

通过 widgets 构建 UI,描述当前的配置和状态,当状态改变时,框架找出前后的变化,以确定底层 Render Tree 要做的最小更改,在内部变成另一个状...

1381
来自专栏空帆船w

Android 编码规范

小驼峰命名(lowerCamelCase):除第一个单词以外,每一个单词的第一个字母大写。

2623
来自专栏JMCui

Netty 系列六(编解码器).

    网络传输的单位是字节,如何将应用程序的数据转换为字节,以及将字节转换为应用程序的数据,就要说到到我们该篇介绍的编码器和解码器。

1111
来自专栏林德熙的博客

WPF 开发

如果使用NamedPipeServerStream、Mutex做单实例,需要传入字符串,这时如果传入一个固定的字符串,会在多用户的时候无法使用。

2421
来自专栏Java Web

初学Java Web(6)——JSP学习总结

为什么要学习 JSP Servlet 的短板: Servlet 的出现,是为了解决动态输出网页的问题。 虽然这样做目的能达到,但是存在一些缺陷: 在 Servl...

4687
来自专栏finleyMa

PHP7 新语法总结,更新7.2注意事项

太空船操作符用于比较两个表达式。当$a小于、等于或大于$b时它分别返回-1、0或1

4242
来自专栏跟着阿笨一起玩NET

C# Eval在aspx页面中的用法及作用

2742
来自专栏Web行业观察

一句话判断IE浏览器

if(window.addEventListener){ alert("not ie"); }else if(window.attachEvent){ ...

1533

扫码关注云+社区

领取腾讯云代金券