类加载器---ClassLoader

简介:

类加载器就是加载其他类的类,它负责将字节码文件加载到内存,创建class 对象。在大部分应用编程中,我们需要自己实现ClassLoader.不过ClassLoader 一般是系统提供的,不需要自己实现。不过,通过创建自定义的ClassLoader,可以实现一些强大灵活的功能。比如:

(1) 热部署。在不重启Java程序的情况下,动态替换类的实现,比如java Web开发的jsp技术就利用自定义的ClassLoader 实现修改jsp代码立即生效。

(2) 应用模块化和相互隔离。不同的ClassLoader可以加载相同的类但互相隔离。

(3)从不同的地方灵活加载。系统默认一般是从本地的.class文件或jar包文件中加载字节码文件,通过自定义ClassLoader,我们可以从共享的web服务器,数据库,缓存服务器等其它地方加载字节码文件。

类加载的基本机制和过程:

类加载器有三个,分别是如下:
(1) 启动类加载器(Bootstrap ClassLoader):这个加载器是java 虚拟机实现的一部分,不是java语言实现的,一般是c++实现,它负责加载java的基础类,主要是<JAVA_HOME>/lib/rt.jar,我们常用的类库,String,集合都是在这个jar包中。

(2) 扩展类加载器(Extension ClassLoader):这个类加载器的实现类是sun.misc.launcher$ExtClassLoader,它加载java的一些扩展类,一般都是
<JAVA_HOME>/lib/ext目录中的jar包。

(3) 应用程序加载器(Application ClassLoader):这个加载器的实现类是sun.misc.launcher$AppClassLoader,它负责加载应用程序的类,包括自己写的和引入的第三方类库,即所有在类路径中指定的类。

这三个类的关系可以认为是父子关系,Application --> Extension -->bootstrap.注意不是继承关系,而是父子委派关系,子ClassLoader 有一个变量parent 指向父ClassLoader,子ClassLoader 加载类时,一般首先会通过父ClassLoader加载,具体流程如下:

yes
no
yes
no
is load?
loaded
return class
parent loader
load
children Loader
try to load

ClassLoader 常用方法:

方法返回值说明
getClassLoader()ClassLoader获取当前对象实际加载它的ClassLoader
getParent()ClassLoader获取父ClassLoader,父loader 是bootstrap ClassLoader 则返回null
getSystemClassLoaderClassLosder获取系统默认的系统加载器
loadClass(String name)Class<?> cls加载类对象,不过不会执行类的初始化代码

示例:

public class ClassLoaderDemo {
    public static void main(String[] args) {
        //getClassLoader() 获取当前类的类加载器,运行运用程序获取的类加载器一般都是Application ClassLoader
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        while(classLoader != null){
            System.out.println(classLoader.getClass().getName());
            //getParent() 获取父类类加载器
            classLoader = classLoader.getParent();
        }
        //如果ClassLoader 是 Bootstrap ClassLoader ,则返回null,因为Bootstrap ClassLoader 不是java语言写的,没有class
        System.out.println(String.class.getClassLoader());

        //ClassLoader的静态方法getSystemClassLoader(),用于获取默认的系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader.getClass().getName());
        try {
            Class<?> cls = systemClassLoader.loadClass("java.util.ArrayList");
            //由于是委派机制,所以这里返回的loader 不一定是调用的 systemClassLoader,这里加载的是ArrayList,所以返回的是null
            ClassLoader loader = cls.getClassLoader();
            System.out.println(loader);
          }catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
    public void say(){
        System.out.println("ClassLoad");
    }
}

探讨反射Class对象的两个forName()方法:

(1)forName(String name):使用系统类加载器加载,会执行类的初始化代码(static 语句块)

(2)forName(String name,boolean initialize,ClassLoader loader):执行了类加载器,它是通过loadclass()方法加载类对象的,不会执行类的初始化代码。

使用loadClass()方法为啥会不执行类 初始化代码呢,一起来看下面源码:

public class ClsForNameDemo {
    static {
        System.out.println("say hello!");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //这里会直接打印出 say hello!
        Class<?> cls =Class.forName("ClassLoader.ClsForNameDemo");
        System.out.println("------------");
        //按理说这里initialize参数是为true,为啥没有打印出say hello!
        Class<?> clsL = Class.forName("ClassLoader.ClsForNameDemo",true,ClassLoader.getSystemClassLoader());

        //下面这样也打印不出来,说明指定ClassLoad的forName方法 直接调用就是下面这样,下面来看下loadClass()
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        String className = ClsForNameDemo.class.getName();
        //默认的参数initialize就为false,调用父类的也是默认为false,
        Class<?> c = classLoader.loadClass(className);
    }
}
 //loadClass 会调用ClassLoader 的另外一个loadClass 方法
 public Class<?> loadClass(String name) throws ClassNotFoundException {
        return this.loadClass(name, false);
    }
 //继续往下看逻辑
  protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized(this.getClassLoadingLock(name)) {
        //首先检查类是否已经加载了
            Class<?> c = this.findLoadedClass(name);
            if (c == null) 
                long t0 = System.nanoTime();
                   //如果没有加载,先委派父ClassLoader或者Bootstrap ClassLoader去加载   
                try {
                    if (this.parent != null) {
                   //委派父ClassLoader,resolve参数固定为false
                        c = this.parent.loadClass(name, false);
                    } else {
                        c = this.findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException var10) {
                //没找到,就自己尝试加载
                }

                if (c == null) {
                    long t1 = System.nanoTime();
                    //自己去加载,findclass才是当前ClassLoader 的真正记载方法
                    c = this.findClass(name);
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }

            if (resolve) {
            //链接,执行static语句块
                this.resolveClass(c);
            }

            return c;
        }
    }   

类加载的应用:可配置的策略

/**
 * 类加载器的应用:可配置的策略
 *当一个接口有多个实现类时,适用于不同的场合,具体使用哪个在配置文件中配置,通过更改配置
 * 不用修改代码,就可以改变程序的行为,在设计模式中,这是一种策略模式
 * 下面是一个简单的例子
 */
public class ClassLoadApplication {

    public static <T> T createService(String fileName) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        Properties properties = new Properties();
        properties.load(new FileInputStream(fileName));
        String className = properties.getProperty("service");
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class<?> cls = classLoader.loadClass(className);
        return (T)cls.newInstance();
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
         Iservice iservice = createService("C:\\Users\\96979\\Desktop\\gittest\\test1\\src\\main\\resources\\config\\config.properties");
         iservice.action();
    }
}

自定义ClassLoader:

package ClassLoader;

import java.io.*;

/**
 * 自定义ClassLoader
 * 自定义的Classloader 可以实现Tomcat应用隔离,支持JSP,OSGI实现动态模块化的基础
 * 自定义ClassLoader需重写findclass,使用自己的逻辑寻找class文件字节码的字节形式
 * 然后使用defineClass(String name, byte[] b, int off, int len) 方法来转换为class 对象
 */
public class MineDefineClassLoader extends ClassLoader {

    private static final String BASE_DIR = "ClassLoader/application/Test/";

    public MineDefineClassLoader() {
    }

    /**
     * 获取指定文件夹下的Class对象
     * @param name
     * @return
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = name.replaceAll("\\.","/");
        fileName = BASE_DIR + fileName + ".class";
        Class<?> cls = null;
        try {
            byte[] bytes = MineDefineClassLoader.readFileToByteArray(fileName);
            System.out.println(bytes.length);
            //将字节码转为Class对象
            cls = defineClass("ClassLoader.application.Test", bytes, 0, bytes.length);
        }catch (IOException e){
            e.printStackTrace();
        }
        return cls;
    }

    /**
     * 指定父类类加载器
     * @param parent
     */
    protected MineDefineClassLoader(ClassLoader parent) {
        super(parent);
    }



    public static  byte[] readFileToByteArray(String fileName) throws IOException {
        InputStream inputStream = new FileInputStream(fileName);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try{
            copy(inputStream,byteArrayOutputStream);
            return  byteArrayOutputStream.toByteArray();
        }finally {
            inputStream.close();
        }
    }

    public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
        byte [] buf = new byte[4096];
        int byteRead = 0;
        while((byteRead = inputStream.read()) != -1){
            outputStream.write(buf,0,byteRead);
        }
    }

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        MineDefineClassLoader mineDefineClassLoader = new MineDefineClassLoader(ClassLoader.getSystemClassLoader().getParent().getParent());

        Class cls = mineDefineClassLoader.findClass("ClassLoaderDemo");
        ClassLoaderDemo classLoaderDemo =(ClassLoaderDemo) cls.newInstance();
        classLoaderDemo.say();

        //自定义ClassLoader 可以创建多个,对于同一个类,每个自定义的ClassLoader 对象都就可以加载一次,
        //得到同一个类的不同class对象
        MineDefineClassLoader mineDefineClassLoader1 = new MineDefineClassLoader();
        String className = "Test";
        Class<?> cls1 = mineDefineClassLoader1.loadClass(className);
        MineDefineClassLoader mineDefineClassLoader2 = new MineDefineClassLoader();
        Class<?> cls2 = mineDefineClassLoader2.loadClass(className);
        if (cls1 != cls2){
            System.out.println("different classes");
        }
    }
}

通过自定义的ClassLoader,可以通过创建一个新的自定义loader就可以加载一次Class,而且得到的Class对象是最新的,从而实现动态更新,实现热部署。

总结:

本文介绍了Java中的类加载机制,包括Java加载类的基本过程,类ClassLoader的用法,以及如何创建自定义的 ClassLoader.通过本文,希望能帮助小伙伴更好的掌握java动态特性的理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值